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.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

Parser.php

Zuletzt modifiziert: 09.10.2024, 12:56 - Dateigröße: 30.04 KiB


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", '&#10;', \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  }