Verzeichnisstruktur phpBB-3.2.0
- Veröffentlicht
- 06.01.2017
So funktioniert es
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
Parser.php
0001 <?php
0002
0003 /*
0004 * @package s9e\TextFormatter
0005 * @copyright Copyright (c) 2010-2016 The s9e Authors
0006 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
0007 */
0008 namespace s9e\TextFormatter;
0009 use InvalidArgumentException;
0010 use RuntimeException;
0011 use s9e\TextFormatter\Parser\Logger;
0012 use s9e\TextFormatter\Parser\Tag;
0013 class Parser
0014 {
0015 const RULE_AUTO_CLOSE = 1;
0016 const RULE_AUTO_REOPEN = 2;
0017 const RULE_BREAK_PARAGRAPH = 4;
0018 const RULE_CREATE_PARAGRAPHS = 8;
0019 const RULE_DISABLE_AUTO_BR = 16;
0020 const RULE_ENABLE_AUTO_BR = 32;
0021 const RULE_IGNORE_TAGS = 64;
0022 const RULE_IGNORE_TEXT = 128;
0023 const RULE_IGNORE_WHITESPACE = 256;
0024 const RULE_IS_TRANSPARENT = 512;
0025 const RULE_PREVENT_BR = 1024;
0026 const RULE_SUSPEND_AUTO_BR = 2048;
0027 const RULE_TRIM_FIRST_LINE = 4096;
0028 const RULES_AUTO_LINEBREAKS = 2096;
0029 const RULES_INHERITANCE = 32;
0030 const WHITESPACE = '
0031 ';
0032 protected $cntOpen;
0033 protected $cntTotal;
0034 protected $context;
0035 protected $currentFixingCost;
0036 protected $currentTag;
0037 protected $isRich;
0038 protected $logger;
0039 public $maxFixingCost = 1000;
0040 protected $namespaces;
0041 protected $openTags;
0042 protected $output;
0043 protected $pos;
0044 protected $pluginParsers = array();
0045 protected $pluginsConfig;
0046 public $registeredVars = array();
0047 protected $rootContext;
0048 protected $tagsConfig;
0049 protected $tagStack;
0050 protected $tagStackIsSorted;
0051 protected $text;
0052 protected $textLen;
0053 protected $uid = 0;
0054 protected $wsPos;
0055 public function __construct(array $config)
0056 {
0057 $this->pluginsConfig = $config['plugins'];
0058 $this->registeredVars = $config['registeredVars'];
0059 $this->rootContext = $config['rootContext'];
0060 $this->tagsConfig = $config['tags'];
0061 $this->__wakeup();
0062 }
0063 public function __sleep()
0064 {
0065 return array('pluginsConfig', 'registeredVars', 'rootContext', 'tagsConfig');
0066 }
0067 public function __wakeup()
0068 {
0069 $this->logger = new Logger;
0070 }
0071 protected function reset($text)
0072 {
0073 $text = \preg_replace('/\\r\\n?/', "\n", $text);
0074 $text = \preg_replace('/[\\x00-\\x08\\x0B\\x0C\\x0E-\\x1F]+/S', '', $text);
0075 $this->logger->clear();
0076 $this->cntOpen = array();
0077 $this->cntTotal = array();
0078 $this->currentFixingCost = 0;
0079 $this->currentTag = \null;
0080 $this->isRich = \false;
0081 $this->namespaces = array();
0082 $this->openTags = array();
0083 $this->output = '';
0084 $this->pos = 0;
0085 $this->tagStack = array();
0086 $this->tagStackIsSorted = \false;
0087 $this->text = $text;
0088 $this->textLen = \strlen($text);
0089 $this->wsPos = 0;
0090 $this->context = $this->rootContext;
0091 $this->context['inParagraph'] = \false;
0092 ++$this->uid;
0093 }
0094 protected function setTagOption($tagName, $optionName, $optionValue)
0095 {
0096 if (isset($this->tagsConfig[$tagName]))
0097 {
0098 $tagConfig = $this->tagsConfig[$tagName];
0099 unset($this->tagsConfig[$tagName]);
0100 $tagConfig[$optionName] = $optionValue;
0101 $this->tagsConfig[$tagName] = $tagConfig;
0102 }
0103 }
0104 public function disableTag($tagName)
0105 {
0106 $this->setTagOption($tagName, 'isDisabled', \true);
0107 }
0108 public function enableTag($tagName)
0109 {
0110 if (isset($this->tagsConfig[$tagName]))
0111 unset($this->tagsConfig[$tagName]['isDisabled']);
0112 }
0113 public function getLogger()
0114 {
0115 return $this->logger;
0116 }
0117 public function getText()
0118 {
0119 return $this->text;
0120 }
0121 public function parse($text)
0122 {
0123 $this->reset($text);
0124 $uid = $this->uid;
0125 $this->executePluginParsers();
0126 $this->processTags();
0127 $this->finalizeOutput();
0128 if ($this->uid !== $uid)
0129 throw new RuntimeException('The parser has been reset during execution');
0130 if ($this->currentFixingCost > $this->maxFixingCost)
0131 $this->logger->warn('Fixing cost limit exceeded');
0132 return $this->output;
0133 }
0134 public function setTagLimit($tagName, $tagLimit)
0135 {
0136 $this->setTagOption($tagName, 'tagLimit', $tagLimit);
0137 }
0138 public function setNestingLimit($tagName, $nestingLimit)
0139 {
0140 $this->setTagOption($tagName, 'nestingLimit', $nestingLimit);
0141 }
0142 public static function executeAttributePreprocessors(Tag $tag, array $tagConfig)
0143 {
0144 if (!empty($tagConfig['attributePreprocessors']))
0145 foreach ($tagConfig['attributePreprocessors'] as $_5f417eec)
0146 {
0147 list($attrName, $regexp, $map) = $_5f417eec;
0148 if (!$tag->hasAttribute($attrName))
0149 continue;
0150 self::executeAttributePreprocessor($tag, $attrName, $regexp, $map);
0151 }
0152 return \true;
0153 }
0154 protected static function executeAttributePreprocessor(Tag $tag, $attrName, $regexp, $map)
0155 {
0156 $attrValue = $tag->getAttribute($attrName);
0157 $captures = self::getNamedCaptures($attrValue, $regexp, $map);
0158 foreach ($captures as $k => $v)
0159 if ($k === $attrName || !$tag->hasAttribute($k))
0160 $tag->setAttribute($k, $v);
0161 }
0162 protected static function getNamedCaptures($attrValue, $regexp, $map)
0163 {
0164 if (!\preg_match($regexp, $attrValue, $m))
0165 return array();
0166 $values = array();
0167 foreach ($map as $i => $k)
0168 if (isset($m[$i]) && $m[$i] !== '')
0169 $values[$k] = $m[$i];
0170 return $values;
0171 }
0172 protected static function executeFilter(array $filter, array $vars)
0173 {
0174 $callback = $filter['callback'];
0175 $params = (isset($filter['params'])) ? $filter['params'] : array();
0176 $args = array();
0177 foreach ($params as $k => $v)
0178 if (\is_numeric($k))
0179 $args[] = $v;
0180 elseif (isset($vars[$k]))
0181 $args[] = $vars[$k];
0182 elseif (isset($vars['registeredVars'][$k]))
0183 $args[] = $vars['registeredVars'][$k];
0184 else
0185 $args[] = \null;
0186 return \call_user_func_array($callback, $args);
0187 }
0188 public static function filterAttributes(Tag $tag, array $tagConfig, array $registeredVars, Logger $logger)
0189 {
0190 if (empty($tagConfig['attributes']))
0191 {
0192 $tag->setAttributes(array());
0193 return \true;
0194 }
0195 foreach ($tagConfig['attributes'] as $attrName => $attrConfig)
0196 if (isset($attrConfig['generator']))
0197 $tag->setAttribute(
0198 $attrName,
0199 self::executeFilter(
0200 $attrConfig['generator'],
0201 array(
0202 'attrName' => $attrName,
0203 'logger' => $logger,
0204 'registeredVars' => $registeredVars
0205 )
0206 )
0207 );
0208 foreach ($tag->getAttributes() as $attrName => $attrValue)
0209 {
0210 if (!isset($tagConfig['attributes'][$attrName]))
0211 {
0212 $tag->removeAttribute($attrName);
0213 continue;
0214 }
0215 $attrConfig = $tagConfig['attributes'][$attrName];
0216 if (!isset($attrConfig['filterChain']))
0217 continue;
0218 $logger->setAttribute($attrName);
0219 foreach ($attrConfig['filterChain'] as $filter)
0220 {
0221 $attrValue = self::executeFilter(
0222 $filter,
0223 array(
0224 'attrName' => $attrName,
0225 'attrValue' => $attrValue,
0226 'logger' => $logger,
0227 'registeredVars' => $registeredVars
0228 )
0229 );
0230 if ($attrValue === \false)
0231 {
0232 $tag->removeAttribute($attrName);
0233 break;
0234 }
0235 }
0236 if ($attrValue !== \false)
0237 $tag->setAttribute($attrName, $attrValue);
0238 $logger->unsetAttribute();
0239 }
0240 foreach ($tagConfig['attributes'] as $attrName => $attrConfig)
0241 if (!$tag->hasAttribute($attrName))
0242 if (isset($attrConfig['defaultValue']))
0243 $tag->setAttribute($attrName, $attrConfig['defaultValue']);
0244 elseif (!empty($attrConfig['required']))
0245 return \false;
0246 return \true;
0247 }
0248 protected function filterTag(Tag $tag)
0249 {
0250 $tagName = $tag->getName();
0251 $tagConfig = $this->tagsConfig[$tagName];
0252 $isValid = \true;
0253 if (!empty($tagConfig['filterChain']))
0254 {
0255 $this->logger->setTag($tag);
0256 $vars = array(
0257 'logger' => $this->logger,
0258 'openTags' => $this->openTags,
0259 'parser' => $this,
0260 'registeredVars' => $this->registeredVars,
0261 'tag' => $tag,
0262 'tagConfig' => $tagConfig,
0263 'text' => $this->text
0264 );
0265 foreach ($tagConfig['filterChain'] as $filter)
0266 if (!self::executeFilter($filter, $vars))
0267 {
0268 $isValid = \false;
0269 break;
0270 }
0271 $this->logger->unsetTag();
0272 }
0273 return $isValid;
0274 }
0275 protected function finalizeOutput()
0276 {
0277 $this->outputText($this->textLen, 0, \true);
0278 do
0279 {
0280 $this->output = \preg_replace('(<([^ />]+)></\\1>)', '', $this->output, -1, $cnt);
0281 }
0282 while ($cnt > 0);
0283 if (\strpos($this->output, '</i><i>') !== \false)
0284 $this->output = \str_replace('</i><i>', '', $this->output);
0285 $this->output = Utils::encodeUnicodeSupplementaryCharacters($this->output);
0286 $tagName = ($this->isRich) ? 'r' : 't';
0287 $tmp = '<' . $tagName;
0288 foreach (\array_keys($this->namespaces) as $prefix)
0289 $tmp .= ' xmlns:' . $prefix . '="urn:s9e:TextFormatter:' . $prefix . '"';
0290 $this->output = $tmp . '>' . $this->output . '</' . $tagName . '>';
0291 }
0292 protected function outputTag(Tag $tag)
0293 {
0294 $this->isRich = \true;
0295 $tagName = $tag->getName();
0296 $tagPos = $tag->getPos();
0297 $tagLen = $tag->getLen();
0298 $tagFlags = $tag->getFlags();
0299 if ($tagFlags & self::RULE_IGNORE_WHITESPACE)
0300 {
0301 $skipBefore = 1;
0302 $skipAfter = ($tag->isEndTag()) ? 2 : 1;
0303 }
0304 else
0305 $skipBefore = $skipAfter = 0;
0306 $closeParagraph = \false;
0307 if ($tag->isStartTag())
0308 {
0309 if ($tagFlags & self::RULE_BREAK_PARAGRAPH)
0310 $closeParagraph = \true;
0311 }
0312 else
0313 $closeParagraph = \true;
0314 $this->outputText($tagPos, $skipBefore, $closeParagraph);
0315 $tagText = ($tagLen)
0316 ? \htmlspecialchars(\substr($this->text, $tagPos, $tagLen), \ENT_NOQUOTES, 'UTF-8')
0317 : '';
0318 if ($tag->isStartTag())
0319 {
0320 if (!($tagFlags & self::RULE_BREAK_PARAGRAPH))
0321 $this->outputParagraphStart($tagPos);
0322 $colonPos = \strpos($tagName, ':');
0323 if ($colonPos)
0324 $this->namespaces[\substr($tagName, 0, $colonPos)] = 0;
0325 $this->output .= '<' . $tagName;
0326 $attributes = $tag->getAttributes();
0327 \ksort($attributes);
0328 foreach ($attributes as $attrName => $attrValue)
0329 $this->output .= ' ' . $attrName . '="' . \str_replace("\n", ' ', \htmlspecialchars($attrValue, \ENT_COMPAT, 'UTF-8')) . '"';
0330 if ($tag->isSelfClosingTag())
0331 if ($tagLen)
0332 $this->output .= '>' . $tagText . '</' . $tagName . '>';
0333 else
0334 $this->output .= '/>';
0335 elseif ($tagLen)
0336 $this->output .= '><s>' . $tagText . '</s>';
0337 else
0338 $this->output .= '>';
0339 }
0340 else
0341 {
0342 if ($tagLen)
0343 $this->output .= '<e>' . $tagText . '</e>';
0344 $this->output .= '</' . $tagName . '>';
0345 }
0346 $this->pos = $tagPos + $tagLen;
0347 $this->wsPos = $this->pos;
0348 while ($skipAfter && $this->wsPos < $this->textLen && $this->text[$this->wsPos] === "\n")
0349 {
0350 --$skipAfter;
0351 ++$this->wsPos;
0352 }
0353 }
0354 protected function outputText($catchupPos, $maxLines, $closeParagraph)
0355 {
0356 if ($closeParagraph)
0357 if (!($this->context['flags'] & self::RULE_CREATE_PARAGRAPHS))
0358 $closeParagraph = \false;
0359 else
0360 $maxLines = -1;
0361 if ($this->pos >= $catchupPos)
0362 {
0363 if ($closeParagraph)
0364 $this->outputParagraphEnd();
0365 return;
0366 }
0367 if ($this->wsPos > $this->pos)
0368 {
0369 $skipPos = \min($catchupPos, $this->wsPos);
0370 $this->output .= \substr($this->text, $this->pos, $skipPos - $this->pos);
0371 $this->pos = $skipPos;
0372 if ($this->pos >= $catchupPos)
0373 {
0374 if ($closeParagraph)
0375 $this->outputParagraphEnd();
0376 return;
0377 }
0378 }
0379 if ($this->context['flags'] & self::RULE_IGNORE_TEXT)
0380 {
0381 $catchupLen = $catchupPos - $this->pos;
0382 $catchupText = \substr($this->text, $this->pos, $catchupLen);
0383 if (\strspn($catchupText, " \n\t") < $catchupLen)
0384 $catchupText = '<i>' . $catchupText . '</i>';
0385 $this->output .= $catchupText;
0386 $this->pos = $catchupPos;
0387 if ($closeParagraph)
0388 $this->outputParagraphEnd();
0389 return;
0390 }
0391 $ignorePos = $catchupPos;
0392 $ignoreLen = 0;
0393 while ($maxLines && --$ignorePos >= $this->pos)
0394 {
0395 $c = $this->text[$ignorePos];
0396 if (\strpos(self::WHITESPACE, $c) === \false)
0397 break;
0398 if ($c === "\n")
0399 --$maxLines;
0400 ++$ignoreLen;
0401 }
0402 $catchupPos -= $ignoreLen;
0403 if ($this->context['flags'] & self::RULE_CREATE_PARAGRAPHS)
0404 {
0405 if (!$this->context['inParagraph'])
0406 {
0407 $this->outputWhitespace($catchupPos);
0408 if ($catchupPos > $this->pos)
0409 $this->outputParagraphStart($catchupPos);
0410 }
0411 $pbPos = \strpos($this->text, "\n\n", $this->pos);
0412 while ($pbPos !== \false && $pbPos < $catchupPos)
0413 {
0414 $this->outputText($pbPos, 0, \true);
0415 $this->outputParagraphStart($catchupPos);
0416 $pbPos = \strpos($this->text, "\n\n", $this->pos);
0417 }
0418 }
0419 if ($catchupPos > $this->pos)
0420 {
0421 $catchupText = \htmlspecialchars(
0422 \substr($this->text, $this->pos, $catchupPos - $this->pos),
0423 \ENT_NOQUOTES,
0424 'UTF-8'
0425 );
0426 if (($this->context['flags'] & self::RULES_AUTO_LINEBREAKS) === self::RULE_ENABLE_AUTO_BR)
0427 $catchupText = \str_replace("\n", "<br/>\n", $catchupText);
0428 $this->output .= $catchupText;
0429 }
0430 if ($closeParagraph)
0431 $this->outputParagraphEnd();
0432 if ($ignoreLen)
0433 $this->output .= \substr($this->text, $catchupPos, $ignoreLen);
0434 $this->pos = $catchupPos + $ignoreLen;
0435 }
0436 protected function outputBrTag(Tag $tag)
0437 {
0438 $this->outputText($tag->getPos(), 0, \false);
0439 $this->output .= '<br/>';
0440 }
0441 protected function outputIgnoreTag(Tag $tag)
0442 {
0443 $tagPos = $tag->getPos();
0444 $tagLen = $tag->getLen();
0445 $ignoreText = \substr($this->text, $tagPos, $tagLen);
0446 $this->outputText($tagPos, 0, \false);
0447 $this->output .= '<i>' . \htmlspecialchars($ignoreText, \ENT_NOQUOTES, 'UTF-8') . '</i>';
0448 $this->isRich = \true;
0449 $this->pos = $tagPos + $tagLen;
0450 }
0451 protected function outputParagraphStart($maxPos)
0452 {
0453 if ($this->context['inParagraph']
0454 || !($this->context['flags'] & self::RULE_CREATE_PARAGRAPHS))
0455 return;
0456 $this->outputWhitespace($maxPos);
0457 if ($this->pos < $this->textLen)
0458 {
0459 $this->output .= '<p>';
0460 $this->context['inParagraph'] = \true;
0461 }
0462 }
0463 protected function outputParagraphEnd()
0464 {
0465 if (!$this->context['inParagraph'])
0466 return;
0467 $this->output .= '</p>';
0468 $this->context['inParagraph'] = \false;
0469 }
0470 protected function outputVerbatim(Tag $tag)
0471 {
0472 $flags = $this->context['flags'];
0473 $this->context['flags'] = $tag->getFlags();
0474 $this->outputText($this->currentTag->getPos() + $this->currentTag->getLen(), 0, \false);
0475 $this->context['flags'] = $flags;
0476 }
0477 protected function outputWhitespace($maxPos)
0478 {
0479 if ($maxPos > $this->pos)
0480 {
0481 $spn = \strspn($this->text, self::WHITESPACE, $this->pos, $maxPos - $this->pos);
0482 if ($spn)
0483 {
0484 $this->output .= \substr($this->text, $this->pos, $spn);
0485 $this->pos += $spn;
0486 }
0487 }
0488 }
0489 public function disablePlugin($pluginName)
0490 {
0491 if (isset($this->pluginsConfig[$pluginName]))
0492 {
0493 $pluginConfig = $this->pluginsConfig[$pluginName];
0494 unset($this->pluginsConfig[$pluginName]);
0495 $pluginConfig['isDisabled'] = \true;
0496 $this->pluginsConfig[$pluginName] = $pluginConfig;
0497 }
0498 }
0499 public function enablePlugin($pluginName)
0500 {
0501 if (isset($this->pluginsConfig[$pluginName]))
0502 $this->pluginsConfig[$pluginName]['isDisabled'] = \false;
0503 }
0504 protected function executePluginParser($pluginName)
0505 {
0506 $pluginConfig = $this->pluginsConfig[$pluginName];
0507 if (isset($pluginConfig['quickMatch']) && \strpos($this->text, $pluginConfig['quickMatch']) === \false)
0508 return;
0509 $matches = array();
0510 if (isset($pluginConfig['regexp']))
0511 {
0512 $matches = $this->getMatches($pluginConfig['regexp'], $pluginConfig['regexpLimit']);
0513 if (empty($matches))
0514 return;
0515 }
0516 \call_user_func($this->getPluginParser($pluginName), $this->text, $matches);
0517 }
0518 protected function executePluginParsers()
0519 {
0520 foreach ($this->pluginsConfig as $pluginName => $pluginConfig)
0521 if (empty($pluginConfig['isDisabled']))
0522 $this->executePluginParser($pluginName);
0523 }
0524 protected function getMatches($regexp, $limit)
0525 {
0526 $cnt = \preg_match_all($regexp, $this->text, $matches, \PREG_SET_ORDER | \PREG_OFFSET_CAPTURE);
0527 if ($cnt > $limit)
0528 $matches = \array_slice($matches, 0, $limit);
0529 return $matches;
0530 }
0531 protected function getPluginParser($pluginName)
0532 {
0533 if (!isset($this->pluginParsers[$pluginName]))
0534 {
0535 $pluginConfig = $this->pluginsConfig[$pluginName];
0536 $className = (isset($pluginConfig['className']))
0537 ? $pluginConfig['className']
0538 : 's9e\\TextFormatter\\Plugins\\' . $pluginName . '\\Parser';
0539 $this->pluginParsers[$pluginName] = array(new $className($this, $pluginConfig), 'parse');
0540 }
0541 return $this->pluginParsers[$pluginName];
0542 }
0543 public function registerParser($pluginName, $parser, $regexp = \null, $limit = \PHP_INT_MAX)
0544 {
0545 if (!\is_callable($parser))
0546 throw new InvalidArgumentException('Argument 1 passed to ' . __METHOD__ . ' must be a valid callback');
0547 if (!isset($this->pluginsConfig[$pluginName]))
0548 $this->pluginsConfig[$pluginName] = array();
0549 if (isset($regexp))
0550 {
0551 $this->pluginsConfig[$pluginName]['regexp'] = $regexp;
0552 $this->pluginsConfig[$pluginName]['regexpLimit'] = $limit;
0553 }
0554 $this->pluginParsers[$pluginName] = $parser;
0555 }
0556 protected function closeAncestor(Tag $tag)
0557 {
0558 if (!empty($this->openTags))
0559 {
0560 $tagName = $tag->getName();
0561 $tagConfig = $this->tagsConfig[$tagName];
0562 if (!empty($tagConfig['rules']['closeAncestor']))
0563 {
0564 $i = \count($this->openTags);
0565 while (--$i >= 0)
0566 {
0567 $ancestor = $this->openTags[$i];
0568 $ancestorName = $ancestor->getName();
0569 if (isset($tagConfig['rules']['closeAncestor'][$ancestorName]))
0570 {
0571 $this->tagStack[] = $tag;
0572 $this->addMagicEndTag($ancestor, $tag->getPos());
0573 return \true;
0574 }
0575 }
0576 }
0577 }
0578 return \false;
0579 }
0580 protected function closeParent(Tag $tag)
0581 {
0582 if (!empty($this->openTags))
0583 {
0584 $tagName = $tag->getName();
0585 $tagConfig = $this->tagsConfig[$tagName];
0586 if (!empty($tagConfig['rules']['closeParent']))
0587 {
0588 $parent = \end($this->openTags);
0589 $parentName = $parent->getName();
0590 if (isset($tagConfig['rules']['closeParent'][$parentName]))
0591 {
0592 $this->tagStack[] = $tag;
0593 $this->addMagicEndTag($parent, $tag->getPos());
0594 return \true;
0595 }
0596 }
0597 }
0598 return \false;
0599 }
0600 protected function createChild(Tag $tag)
0601 {
0602 $tagConfig = $this->tagsConfig[$tag->getName()];
0603 if (isset($tagConfig['rules']['createChild']))
0604 {
0605 $priority = -1000;
0606 $tagPos = $this->pos + \strspn($this->text, " \n\r\t", $this->pos);
0607 foreach ($tagConfig['rules']['createChild'] as $tagName)
0608 $this->addStartTag($tagName, $tagPos, 0, ++$priority);
0609 }
0610 }
0611 protected function fosterParent(Tag $tag)
0612 {
0613 if (!empty($this->openTags))
0614 {
0615 $tagName = $tag->getName();
0616 $tagConfig = $this->tagsConfig[$tagName];
0617 if (!empty($tagConfig['rules']['fosterParent']))
0618 {
0619 $parent = \end($this->openTags);
0620 $parentName = $parent->getName();
0621 if (isset($tagConfig['rules']['fosterParent'][$parentName]))
0622 {
0623 if ($parentName !== $tagName && $this->currentFixingCost < $this->maxFixingCost)
0624 {
0625 $child = $this->addCopyTag($parent, $tag->getPos() + $tag->getLen(), 0, $tag->getSortPriority() + 1);
0626 $tag->cascadeInvalidationTo($child);
0627 }
0628 $this->tagStack[] = $tag;
0629 $this->addMagicEndTag($parent, $tag->getPos(), $tag->getSortPriority() - 1);
0630 $this->currentFixingCost += 4;
0631 return \true;
0632 }
0633 }
0634 }
0635 return \false;
0636 }
0637 protected function requireAncestor(Tag $tag)
0638 {
0639 $tagName = $tag->getName();
0640 $tagConfig = $this->tagsConfig[$tagName];
0641 if (isset($tagConfig['rules']['requireAncestor']))
0642 {
0643 foreach ($tagConfig['rules']['requireAncestor'] as $ancestorName)
0644 if (!empty($this->cntOpen[$ancestorName]))
0645 return \false;
0646 $this->logger->err('Tag requires an ancestor', array(
0647 'requireAncestor' => \implode(',', $tagConfig['rules']['requireAncestor']),
0648 'tag' => $tag
0649 ));
0650 return \true;
0651 }
0652 return \false;
0653 }
0654 protected function addMagicEndTag(Tag $startTag, $tagPos, $prio = 0)
0655 {
0656 $tagName = $startTag->getName();
0657 if ($startTag->getFlags() & self::RULE_IGNORE_WHITESPACE)
0658 $tagPos = $this->getMagicPos($tagPos);
0659 $endTag = $this->addEndTag($tagName, $tagPos, 0, $prio);
0660 $endTag->pairWith($startTag);
0661 return $endTag;
0662 }
0663 protected function getMagicPos($tagPos)
0664 {
0665 while ($tagPos > $this->pos && \strpos(self::WHITESPACE, $this->text[$tagPos - 1]) !== \false)
0666 --$tagPos;
0667 return $tagPos;
0668 }
0669 protected function isFollowedByClosingTag(Tag $tag)
0670 {
0671 return (empty($this->tagStack)) ? \false : \end($this->tagStack)->canClose($tag);
0672 }
0673 protected function processTags()
0674 {
0675 if (empty($this->tagStack))
0676 return;
0677 foreach (\array_keys($this->tagsConfig) as $tagName)
0678 {
0679 $this->cntOpen[$tagName] = 0;
0680 $this->cntTotal[$tagName] = 0;
0681 }
0682 do
0683 {
0684 while (!empty($this->tagStack))
0685 {
0686 if (!$this->tagStackIsSorted)
0687 $this->sortTags();
0688 $this->currentTag = \array_pop($this->tagStack);
0689 $this->processCurrentTag();
0690 }
0691 foreach ($this->openTags as $startTag)
0692 $this->addMagicEndTag($startTag, $this->textLen);
0693 }
0694 while (!empty($this->tagStack));
0695 }
0696 protected function processCurrentTag()
0697 {
0698 if (($this->context['flags'] & self::RULE_IGNORE_TAGS)
0699 && !$this->currentTag->canClose(\end($this->openTags))
0700 && !$this->currentTag->isSystemTag())
0701 $this->currentTag->invalidate();
0702 $tagPos = $this->currentTag->getPos();
0703 $tagLen = $this->currentTag->getLen();
0704 if ($this->pos > $tagPos && !$this->currentTag->isInvalid())
0705 {
0706 $startTag = $this->currentTag->getStartTag();
0707 if ($startTag && \in_array($startTag, $this->openTags, \true))
0708 {
0709 $this->addEndTag(
0710 $startTag->getName(),
0711 $this->pos,
0712 \max(0, $tagPos + $tagLen - $this->pos)
0713 )->pairWith($startTag);
0714 return;
0715 }
0716 if ($this->currentTag->isIgnoreTag())
0717 {
0718 $ignoreLen = $tagPos + $tagLen - $this->pos;
0719 if ($ignoreLen > 0)
0720 {
0721 $this->addIgnoreTag($this->pos, $ignoreLen);
0722 return;
0723 }
0724 }
0725 $this->currentTag->invalidate();
0726 }
0727 if ($this->currentTag->isInvalid())
0728 return;
0729 if ($this->currentTag->isIgnoreTag())
0730 $this->outputIgnoreTag($this->currentTag);
0731 elseif ($this->currentTag->isBrTag())
0732 {
0733 if (!($this->context['flags'] & self::RULE_PREVENT_BR))
0734 $this->outputBrTag($this->currentTag);
0735 }
0736 elseif ($this->currentTag->isParagraphBreak())
0737 $this->outputText($this->currentTag->getPos(), 0, \true);
0738 elseif ($this->currentTag->isVerbatim())
0739 $this->outputVerbatim($this->currentTag);
0740 elseif ($this->currentTag->isStartTag())
0741 $this->processStartTag($this->currentTag);
0742 else
0743 $this->processEndTag($this->currentTag);
0744 }
0745 protected function processStartTag(Tag $tag)
0746 {
0747 $tagName = $tag->getName();
0748 $tagConfig = $this->tagsConfig[$tagName];
0749 if ($this->cntTotal[$tagName] >= $tagConfig['tagLimit'])
0750 {
0751 $this->logger->err(
0752 'Tag limit exceeded',
0753 array(
0754 'tag' => $tag,
0755 'tagName' => $tagName,
0756 'tagLimit' => $tagConfig['tagLimit']
0757 )
0758 );
0759 $tag->invalidate();
0760 return;
0761 }
0762 if (!$this->filterTag($tag))
0763 {
0764 $tag->invalidate();
0765 return;
0766 }
0767 if ($this->fosterParent($tag) || $this->closeParent($tag) || $this->closeAncestor($tag))
0768 return;
0769 if ($this->cntOpen[$tagName] >= $tagConfig['nestingLimit'])
0770 {
0771 $this->logger->err(
0772 'Nesting limit exceeded',
0773 array(
0774 'tag' => $tag,
0775 'tagName' => $tagName,
0776 'nestingLimit' => $tagConfig['nestingLimit']
0777 )
0778 );
0779 $tag->invalidate();
0780 return;
0781 }
0782 if (!$this->tagIsAllowed($tagName))
0783 {
0784 $msg = 'Tag is not allowed in this context';
0785 $context = array('tag' => $tag, 'tagName' => $tagName);
0786 if ($tag->getLen() > 0)
0787 $this->logger->warn($msg, $context);
0788 else
0789 $this->logger->debug($msg, $context);
0790 $tag->invalidate();
0791 return;
0792 }
0793 if ($this->requireAncestor($tag))
0794 {
0795 $tag->invalidate();
0796 return;
0797 }
0798 if ($tag->getFlags() & self::RULE_AUTO_CLOSE
0799 && !$tag->getEndTag()
0800 && !$this->isFollowedByClosingTag($tag))
0801 {
0802 $newTag = new Tag(Tag::SELF_CLOSING_TAG, $tagName, $tag->getPos(), $tag->getLen());
0803 $newTag->setAttributes($tag->getAttributes());
0804 $newTag->setFlags($tag->getFlags());
0805 $tag = $newTag;
0806 }
0807 if ($tag->getFlags() & self::RULE_TRIM_FIRST_LINE
0808 && !$tag->getEndTag()
0809 && \substr($this->text, $tag->getPos() + $tag->getLen(), 1) === "\n")
0810 $this->addIgnoreTag($tag->getPos() + $tag->getLen(), 1);
0811 $this->outputTag($tag);
0812 $this->pushContext($tag);
0813 $this->createChild($tag);
0814 }
0815 protected function processEndTag(Tag $tag)
0816 {
0817 $tagName = $tag->getName();
0818 if (empty($this->cntOpen[$tagName]))
0819 return;
0820 $closeTags = array();
0821 $i = \count($this->openTags);
0822 while (--$i >= 0)
0823 {
0824 $openTag = $this->openTags[$i];
0825 if ($tag->canClose($openTag))
0826 break;
0827 $closeTags[] = $openTag;
0828 ++$this->currentFixingCost;
0829 }
0830 if ($i < 0)
0831 {
0832 $this->logger->debug('Skipping end tag with no start tag', array('tag' => $tag));
0833 return;
0834 }
0835 $keepReopening = (bool) ($this->currentFixingCost < $this->maxFixingCost);
0836 $reopenTags = array();
0837 foreach ($closeTags as $openTag)
0838 {
0839 $openTagName = $openTag->getName();
0840 if ($keepReopening)
0841 if ($openTag->getFlags() & self::RULE_AUTO_REOPEN)
0842 $reopenTags[] = $openTag;
0843 else
0844 $keepReopening = \false;
0845 $tagPos = $tag->getPos();
0846 if ($openTag->getFlags() & self::RULE_IGNORE_WHITESPACE)
0847 $tagPos = $this->getMagicPos($tagPos);
0848 $endTag = new Tag(Tag::END_TAG, $openTagName, $tagPos, 0);
0849 $endTag->setFlags($openTag->getFlags());
0850 $this->outputTag($endTag);
0851 $this->popContext();
0852 }
0853 $this->outputTag($tag);
0854 $this->popContext();
0855 if (!empty($closeTags) && $this->currentFixingCost < $this->maxFixingCost)
0856 {
0857 $ignorePos = $this->pos;
0858 $i = \count($this->tagStack);
0859 while (--$i >= 0 && ++$this->currentFixingCost < $this->maxFixingCost)
0860 {
0861 $upcomingTag = $this->tagStack[$i];
0862 if ($upcomingTag->getPos() > $ignorePos
0863 || $upcomingTag->isStartTag())
0864 break;
0865 $j = \count($closeTags);
0866 while (--$j >= 0 && ++$this->currentFixingCost < $this->maxFixingCost)
0867 if ($upcomingTag->canClose($closeTags[$j]))
0868 {
0869 \array_splice($closeTags, $j, 1);
0870 if (isset($reopenTags[$j]))
0871 \array_splice($reopenTags, $j, 1);
0872 $ignorePos = \max(
0873 $ignorePos,
0874 $upcomingTag->getPos() + $upcomingTag->getLen()
0875 );
0876 break;
0877 }
0878 }
0879 if ($ignorePos > $this->pos)
0880 $this->outputIgnoreTag(new Tag(Tag::SELF_CLOSING_TAG, 'i', $this->pos, $ignorePos - $this->pos));
0881 }
0882 foreach ($reopenTags as $startTag)
0883 {
0884 $newTag = $this->addCopyTag($startTag, $this->pos, 0);
0885 $endTag = $startTag->getEndTag();
0886 if ($endTag)
0887 $newTag->pairWith($endTag);
0888 }
0889 }
0890 protected function popContext()
0891 {
0892 $tag = \array_pop($this->openTags);
0893 --$this->cntOpen[$tag->getName()];
0894 $this->context = $this->context['parentContext'];
0895 }
0896 protected function pushContext(Tag $tag)
0897 {
0898 $tagName = $tag->getName();
0899 $tagFlags = $tag->getFlags();
0900 $tagConfig = $this->tagsConfig[$tagName];
0901 ++$this->cntTotal[$tagName];
0902 if ($tag->isSelfClosingTag())
0903 return;
0904 $allowed = array();
0905 if ($tagFlags & self::RULE_IS_TRANSPARENT)
0906 foreach ($this->context['allowed'] as $k => $v)
0907 $allowed[] = $tagConfig['allowed'][$k] & $v;
0908 else
0909 foreach ($this->context['allowed'] as $k => $v)
0910 $allowed[] = $tagConfig['allowed'][$k] & (($v & 0xFF00) | ($v >> 8));
0911 $flags = $tagFlags | ($this->context['flags'] & self::RULES_INHERITANCE);
0912 if ($flags & self::RULE_DISABLE_AUTO_BR)
0913 $flags &= ~self::RULE_ENABLE_AUTO_BR;
0914 ++$this->cntOpen[$tagName];
0915 $this->openTags[] = $tag;
0916 $this->context = array(
0917 'allowed' => $allowed,
0918 'flags' => $flags,
0919 'inParagraph' => \false,
0920 'parentContext' => $this->context
0921 );
0922 }
0923 protected function tagIsAllowed($tagName)
0924 {
0925 $n = $this->tagsConfig[$tagName]['bitNumber'];
0926 return (bool) ($this->context['allowed'][$n >> 3] & (1 << ($n & 7)));
0927 }
0928 public function addStartTag($name, $pos, $len, $prio = 0)
0929 {
0930 return $this->addTag(Tag::START_TAG, $name, $pos, $len, $prio);
0931 }
0932 public function addEndTag($name, $pos, $len, $prio = 0)
0933 {
0934 return $this->addTag(Tag::END_TAG, $name, $pos, $len, $prio);
0935 }
0936 public function addSelfClosingTag($name, $pos, $len, $prio = 0)
0937 {
0938 return $this->addTag(Tag::SELF_CLOSING_TAG, $name, $pos, $len, $prio);
0939 }
0940 public function addBrTag($pos, $prio = 0)
0941 {
0942 return $this->addTag(Tag::SELF_CLOSING_TAG, 'br', $pos, 0, $prio);
0943 }
0944 public function addIgnoreTag($pos, $len, $prio = 0)
0945 {
0946 return $this->addTag(Tag::SELF_CLOSING_TAG, 'i', $pos, \min($len, $this->textLen - $pos), $prio);
0947 }
0948 public function addParagraphBreak($pos, $prio = 0)
0949 {
0950 return $this->addTag(Tag::SELF_CLOSING_TAG, 'pb', $pos, 0, $prio);
0951 }
0952 public function addCopyTag(Tag $tag, $pos, $len, $prio = \null)
0953 {
0954 if (!isset($prio))
0955 $prio = $tag->getSortPriority();
0956 $copy = $this->addTag($tag->getType(), $tag->getName(), $pos, $len, $prio);
0957 $copy->setAttributes($tag->getAttributes());
0958 return $copy;
0959 }
0960 protected function addTag($type, $name, $pos, $len, $prio)
0961 {
0962 $tag = new Tag($type, $name, $pos, $len, $prio);
0963 if (isset($this->tagsConfig[$name]))
0964 $tag->setFlags($this->tagsConfig[$name]['rules']['flags']);
0965 if (!isset($this->tagsConfig[$name]) && !$tag->isSystemTag())
0966 $tag->invalidate();
0967 elseif (!empty($this->tagsConfig[$name]['isDisabled']))
0968 {
0969 $this->logger->warn(
0970 'Tag is disabled',
0971 array(
0972 'tag' => $tag,
0973 'tagName' => $name
0974 )
0975 );
0976 $tag->invalidate();
0977 }
0978 elseif ($len < 0 || $pos < 0 || $pos + $len > $this->textLen)
0979 $tag->invalidate();
0980 else
0981 $this->insertTag($tag);
0982 return $tag;
0983 }
0984 protected function insertTag(Tag $tag)
0985 {
0986 if (!$this->tagStackIsSorted)
0987 $this->tagStack[] = $tag;
0988 else
0989 {
0990 $i = \count($this->tagStack);
0991 while ($i > 0 && self::compareTags($this->tagStack[$i - 1], $tag) > 0)
0992 {
0993 $this->tagStack[$i] = $this->tagStack[$i - 1];
0994 --$i;
0995 }
0996 $this->tagStack[$i] = $tag;
0997 }
0998 }
0999 public function addTagPair($name, $startPos, $startLen, $endPos, $endLen, $prio = 0)
1000 {
1001 $endTag = $this->addEndTag($name, $endPos, $endLen, -$prio);
1002 $startTag = $this->addStartTag($name, $startPos, $startLen, $prio);
1003 $startTag->pairWith($endTag);
1004 return $startTag;
1005 }
1006 public function addVerbatim($pos, $len, $prio = 0)
1007 {
1008 return $this->addTag(Tag::SELF_CLOSING_TAG, 'v', $pos, $len, $prio);
1009 }
1010 protected function sortTags()
1011 {
1012 \usort($this->tagStack, __CLASS__ . '::compareTags');
1013 $this->tagStackIsSorted = \true;
1014 }
1015 protected static function compareTags(Tag $a, Tag $b)
1016 {
1017 $aPos = $a->getPos();
1018 $bPos = $b->getPos();
1019 if ($aPos !== $bPos)
1020 return $bPos - $aPos;
1021 if ($a->getSortPriority() !== $b->getSortPriority())
1022 return $b->getSortPriority() - $a->getSortPriority();
1023 $aLen = $a->getLen();
1024 $bLen = $b->getLen();
1025 if (!$aLen || !$bLen)
1026 {
1027 if (!$aLen && !$bLen)
1028 {
1029 $order = array(
1030 Tag::END_TAG => 0,
1031 Tag::SELF_CLOSING_TAG => 1,
1032 Tag::START_TAG => 2
1033 );
1034 return $order[$b->getType()] - $order[$a->getType()];
1035 }
1036 return ($aLen) ? -1 : 1;
1037 }
1038 return $aLen - $bLen;
1039 }
1040 }