'addTargetedRule', 'allowDescendant' => 'addTargetedRule', 'autoClose' => 'addBooleanRule', 'autoReopen' => 'addBooleanRule', 'breakParagraph' => 'addBooleanRule', 'closeAncestor' => 'addTargetedRule', 'closeParent' => 'addTargetedRule', 'createChild' => 'addTargetedRule', 'createParagraphs' => 'addBooleanRule', 'denyChild' => 'addTargetedRule', 'denyDescendant' => 'addTargetedRule', 'disableAutoLineBreaks' => 'addBooleanRule', 'enableAutoLineBreaks' => 'addBooleanRule', 'fosterParent' => 'addTargetedRule', 'ignoreSurroundingWhitespace' => 'addBooleanRule', 'ignoreTags' => 'addBooleanRule', 'ignoreText' => 'addBooleanRule', 'isTransparent' => 'addBooleanRule', 'preventLineBreaks' => 'addBooleanRule', 'requireParent' => 'addTargetedRule', 'requireAncestor' => 'addTargetedRule', 'suspendAutoLineBreaks' => 'addBooleanRule', 'trimFirstLine' => 'addBooleanRule' ]; /** * Add a rule to this set * * @param string $methodName Rule name * @param array $args Arguments used to add given rule * @return self */ public function __call($methodName, array $args) { if (!isset($this->rules[$methodName])) { throw new BadMethodCallException("Undefined method '" . $methodName . "'"); } array_unshift($args, $methodName); call_user_func_array([$this, $this->rules[$methodName]], $args); return $this; } //========================================================================== // ArrayAccess methods //========================================================================== /** * Test whether a rule category exists * * @param string $k Rule name, e.g. "allowChild" or "isTransparent" */ public function offsetExists($k): bool { return isset($this->items[$k]); } /** * Return the content of a rule category * * @param string $k Rule name, e.g. "allowChild" or "isTransparent" * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($k) { return $this->items[$k]; } /** * Not supported */ public function offsetSet($k, $v): void { throw new RuntimeException('Not supported'); } /** * Clear a subset of the rules * * @see clear() * * @param string $k Rule name, e.g. "allowChild" or "isTransparent" */ public function offsetUnset($k): void { $this->remove($k); } //========================================================================== // Generic methods //========================================================================== /** * {@inheritdoc} */ public function asConfig() { $config = $this->items; // Remove rules that are not needed at parsing time. All of those are resolved when building // the allowed bitfields unset($config['allowChild']); unset($config['allowDescendant']); unset($config['denyChild']); unset($config['denyDescendant']); unset($config['requireParent']); // Pack boolean rules into a bitfield $bitValues = [ 'autoClose' => Parser::RULE_AUTO_CLOSE, 'autoReopen' => Parser::RULE_AUTO_REOPEN, 'breakParagraph' => Parser::RULE_BREAK_PARAGRAPH, 'createParagraphs' => Parser::RULE_CREATE_PARAGRAPHS, 'disableAutoLineBreaks' => Parser::RULE_DISABLE_AUTO_BR, 'enableAutoLineBreaks' => Parser::RULE_ENABLE_AUTO_BR, 'ignoreSurroundingWhitespace' => Parser::RULE_IGNORE_WHITESPACE, 'ignoreTags' => Parser::RULE_IGNORE_TAGS, 'ignoreText' => Parser::RULE_IGNORE_TEXT, 'isTransparent' => Parser::RULE_IS_TRANSPARENT, 'preventLineBreaks' => Parser::RULE_PREVENT_BR, 'suspendAutoLineBreaks' => Parser::RULE_SUSPEND_AUTO_BR, 'trimFirstLine' => Parser::RULE_TRIM_FIRST_LINE ]; $bitfield = 0; foreach ($bitValues as $ruleName => $bitValue) { if (!empty($config[$ruleName])) { $bitfield |= $bitValue; } unset($config[$ruleName]); } // In order to speed up lookups, we use the tag names as keys foreach (['closeAncestor', 'closeParent', 'fosterParent'] as $ruleName) { if (isset($config[$ruleName])) { $targets = array_fill_keys($config[$ruleName], 1); $config[$ruleName] = new Dictionary($targets); } } // Add the bitfield to the config $config['flags'] = $bitfield; return $config; } /** * Merge a set of rules into this collection * * @param array|Ruleset $rules 2D array of rule definitions, or instance of Ruleset * @param bool $overwrite Whether to overwrite scalar rules (e.g. boolean rules) */ public function merge($rules, $overwrite = true) { if (!is_array($rules) && !($rules instanceof self)) { throw new InvalidArgumentException('merge() expects an array or an instance of Ruleset'); } foreach ($rules as $action => $value) { if (is_array($value)) { foreach ($value as $tagName) { $this->$action($tagName); } } elseif ($overwrite || !isset($this->items[$action])) { $this->$action($value); } } } /** * Remove a specific rule, or all the rules of a given type * * @param string $type Type of rules to clear * @param string $tagName Name of the target tag, or none to remove all rules of given type * @return void */ public function remove($type, $tagName = null) { if (preg_match('(^default(?:Child|Descendant)Rule)', $type)) { throw new InvalidArgumentException('Cannot remove ' . $type); } if (isset($tagName)) { $tagName = TagName::normalize($tagName); if (isset($this->items[$type])) { // Compute the difference between current list and our one tag name $this->items[$type] = array_diff( $this->items[$type], [$tagName] ); if (empty($this->items[$type])) { // If the list is now empty, keep it neat and unset it unset($this->items[$type]); } else { // If the list still have names, keep it neat and rearrange keys $this->items[$type] = array_values($this->items[$type]); } } } else { unset($this->items[$type]); } } //========================================================================== // Rules //========================================================================== /** * Add a boolean rule * * @param string $ruleName Name of the rule * @param bool $bool Whether to enable or disable the rule * @return self */ protected function addBooleanRule($ruleName, $bool = true) { if (!is_bool($bool)) { throw new InvalidArgumentException($ruleName . '() expects a boolean'); } $this->items[$ruleName] = $bool; } /** * Add a targeted rule * * @param string $ruleName Name of the rule * @param string $tagName Name of the target tag * @return self */ protected function addTargetedRule($ruleName, $tagName) { $this->items[$ruleName][] = TagName::normalize($tagName); } }