[ Index ]

PHP Cross Reference of phpBB-3.2.11-deutsch

title

Body

[close]

/vendor/s9e/text-formatter/src/ -> Configurator.php (source)

   1  <?php
   2  
   3  /*
   4  * @package   s9e\TextFormatter
   5  * @copyright Copyright (c) 2010-2019 The s9e Authors
   6  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
   7  */
   8  namespace s9e\TextFormatter;
   9  use InvalidArgumentException;
  10  use RuntimeException;
  11  use s9e\TextFormatter\Configurator\BundleGenerator;
  12  use s9e\TextFormatter\Configurator\Collections\AttributeFilterCollection;
  13  use s9e\TextFormatter\Configurator\Collections\PluginCollection;
  14  use s9e\TextFormatter\Configurator\Collections\Ruleset;
  15  use s9e\TextFormatter\Configurator\Collections\TagCollection;
  16  use s9e\TextFormatter\Configurator\ConfigProvider;
  17  use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
  18  use s9e\TextFormatter\Configurator\Helpers\RulesHelper;
  19  use s9e\TextFormatter\Configurator\JavaScript;
  20  use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
  21  use s9e\TextFormatter\Configurator\Rendering;
  22  use s9e\TextFormatter\Configurator\RulesGenerator;
  23  use s9e\TextFormatter\Configurator\TemplateChecker;
  24  use s9e\TextFormatter\Configurator\TemplateNormalizer;
  25  use s9e\TextFormatter\Configurator\UrlConfig;
  26  class Configurator implements ConfigProvider
  27  {
  28      public $attributeFilters;
  29      public $bundleGenerator;
  30      public $javascript;
  31      public $plugins;
  32      public $registeredVars;
  33      public $rendering;
  34      public $rootRules;
  35      public $rulesGenerator;
  36      public $tags;
  37      public $templateChecker;
  38      public $templateNormalizer;
  39  	public function __construct()
  40      {
  41          $this->attributeFilters   = new AttributeFilterCollection;
  42          $this->bundleGenerator    = new BundleGenerator($this);
  43          $this->plugins            = new PluginCollection($this);
  44          $this->registeredVars     = ['urlConfig' => new UrlConfig];
  45          $this->rendering          = new Rendering($this);
  46          $this->rootRules          = new Ruleset;
  47          $this->rulesGenerator     = new RulesGenerator;
  48          $this->tags               = new TagCollection;
  49          $this->templateChecker    = new TemplateChecker;
  50          $this->templateNormalizer = new TemplateNormalizer;
  51      }
  52  	public function __get($k)
  53      {
  54          if (\preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
  55              return (isset($this->plugins[$k]))
  56                   ? $this->plugins[$k]
  57                   : $this->plugins->load($k);
  58          if (isset($this->registeredVars[$k]))
  59              return $this->registeredVars[$k];
  60          throw new RuntimeException("Undefined property '" . __CLASS__ . '::$' . $k . "'");
  61      }
  62  	public function __isset($k)
  63      {
  64          if (\preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
  65              return isset($this->plugins[$k]);
  66          return isset($this->registeredVars[$k]);
  67      }
  68  	public function __set($k, $v)
  69      {
  70          if (\preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
  71              $this->plugins[$k] = $v;
  72          else
  73              $this->registeredVars[$k] = $v;
  74      }
  75  	public function __unset($k)
  76      {
  77          if (\preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $k))
  78              unset($this->plugins[$k]);
  79          else
  80              unset($this->registeredVars[$k]);
  81      }
  82  	public function enableJavaScript()
  83      {
  84          if (!isset($this->javascript))
  85              $this->javascript = new JavaScript($this);
  86      }
  87  	public function finalize()
  88      {
  89          $return = [];
  90          $this->plugins->finalize();
  91          foreach ($this->tags as $tag)
  92              $this->templateNormalizer->normalizeTag($tag);
  93          $return['renderer'] = $this->rendering->getRenderer();
  94          $this->addTagRules();
  95          $config = $this->asConfig();
  96          if (isset($this->javascript))
  97              $return['js'] = $this->javascript->getParser(ConfigHelper::filterConfig($config, 'JS'));
  98          $config = ConfigHelper::filterConfig($config, 'PHP');
  99          ConfigHelper::optimizeArray($config);
 100          $return['parser'] = new Parser($config);
 101          return $return;
 102      }
 103  	public function loadBundle($bundleName)
 104      {
 105          if (!\preg_match('#^[A-Z][A-Za-z0-9]+$#D', $bundleName))
 106              throw new InvalidArgumentException("Invalid bundle name '" . $bundleName . "'");
 107          $className = __CLASS__ . '\\Bundles\\' . $bundleName;
 108          $bundle = new $className;
 109          $bundle->configure($this);
 110      }
 111  	public function saveBundle($className, $filepath, array $options = [])
 112      {
 113          $file = "<?php\n\n" . $this->bundleGenerator->generate($className, $options);
 114          return (\file_put_contents($filepath, $file) !== \false);
 115      }
 116  	public function asConfig()
 117      {
 118          $properties = \get_object_vars($this);
 119          unset($properties['attributeFilters']);
 120          unset($properties['bundleGenerator']);
 121          unset($properties['javascript']);
 122          unset($properties['rendering']);
 123          unset($properties['rulesGenerator']);
 124          unset($properties['registeredVars']);
 125          unset($properties['templateChecker']);
 126          unset($properties['templateNormalizer']);
 127          unset($properties['stylesheet']);
 128          $config    = ConfigHelper::toArray($properties);
 129          $bitfields = RulesHelper::getBitfields($this->tags, $this->rootRules);
 130          $config['rootContext'] = $bitfields['root'];
 131          $config['rootContext']['flags'] = $config['rootRules']['flags'];
 132          $config['registeredVars'] = ConfigHelper::toArray($this->registeredVars, \true);
 133          $config += [
 134              'plugins' => [],
 135              'tags'    => []
 136          ];
 137          $config['tags'] = \array_intersect_key($config['tags'], $bitfields['tags']);
 138          foreach ($bitfields['tags'] as $tagName => $tagBitfields)
 139              $config['tags'][$tagName] += $tagBitfields;
 140          unset($config['rootRules']);
 141          return $config;
 142      }
 143  	protected function addTagRules()
 144      {
 145          $rules = $this->rulesGenerator->getRules($this->tags);
 146          $this->rootRules->merge($rules['root'], \false);
 147          foreach ($rules['tags'] as $tagName => $tagRules)
 148              $this->tags[$tagName]->rules->merge($tagRules, \false);
 149      }
 150  }
 151  
 152  /*
 153  * @package   s9e\TextFormatter
 154  * @copyright Copyright (c) 2010-2019 The s9e Authors
 155  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 156  */
 157  namespace s9e\TextFormatter\Configurator;
 158  use s9e\TextFormatter\Configurator;
 159  use s9e\TextFormatter\Configurator\RendererGenerators\PHP;
 160  class BundleGenerator
 161  {
 162      protected $configurator;
 163      public $serializer = 'serialize';
 164      public $unserializer = 'unserialize';
 165  	public function __construct(Configurator $configurator)
 166      {
 167          $this->configurator = $configurator;
 168      }
 169  	public function generate($className, array $options = [])
 170      {
 171          $options += ['autoInclude' => \true];
 172          $objects  = $this->configurator->finalize();
 173          $parser   = $objects['parser'];
 174          $renderer = $objects['renderer'];
 175          $namespace = '';
 176          if (\preg_match('#(.*)\\\\([^\\\\]+)$#', $className, $m))
 177          {
 178              $namespace = $m[1];
 179              $className = $m[2];
 180          }
 181          $php = [];
 182          $php[] = '/**';
 183          $php[] = '* @package   s9e\TextFormatter';
 184          $php[] = '* @copyright Copyright (c) 2010-2019 The s9e Authors';
 185          $php[] = '* @license   http://www.opensource.org/licenses/mit-license.php The MIT License';
 186          $php[] = '*/';
 187          if ($namespace)
 188          {
 189              $php[] = 'namespace ' . $namespace . ';';
 190              $php[] = '';
 191          }
 192          $php[] = 'abstract class ' . $className . ' extends \\s9e\\TextFormatter\\Bundle';
 193          $php[] = '{';
 194          $php[] = '    /**';
 195          $php[] = '    * @var s9e\\TextFormatter\\Parser Singleton instance used by parse()';
 196          $php[] = '    */';
 197          $php[] = '    protected static $parser;';
 198          $php[] = '';
 199          $php[] = '    /**';
 200          $php[] = '    * @var s9e\\TextFormatter\\Renderer Singleton instance used by render()';
 201          $php[] = '    */';
 202          $php[] = '    protected static $renderer;';
 203          $php[] = '';
 204          $events = [
 205              'beforeParse'
 206                  => 'Callback executed before parse(), receives the original text as argument',
 207              'afterParse'
 208                  => 'Callback executed after parse(), receives the parsed text as argument',
 209              'beforeRender'
 210                  => 'Callback executed before render(), receives the parsed text as argument',
 211              'afterRender'
 212                  => 'Callback executed after render(), receives the output as argument',
 213              'beforeUnparse'
 214                  => 'Callback executed before unparse(), receives the parsed text as argument',
 215              'afterUnparse'
 216                  => 'Callback executed after unparse(), receives the original text as argument'
 217          ];
 218          foreach ($events as $eventName => $eventDesc)
 219              if (isset($options[$eventName]))
 220              {
 221                  $php[] = '    /**';
 222                  $php[] = '    * @var ' . $eventDesc;
 223                  $php[] = '    */';
 224                  $php[] = '    public static $' . $eventName . ' = ' . \var_export($options[$eventName], \true) . ';';
 225                  $php[] = '';
 226              }
 227          $php[] = '    /**';
 228          $php[] = '    * Return a new instance of s9e\\TextFormatter\\Parser';
 229          $php[] = '    *';
 230          $php[] = '    * @return s9e\\TextFormatter\\Parser';
 231          $php[] = '    */';
 232          $php[] = '    public static function getParser()';
 233          $php[] = '    {';
 234          if (isset($options['parserSetup']))
 235          {
 236              $php[] = '        $parser = ' . $this->exportObject($parser) . ';';
 237              $php[] = '        ' . $this->exportCallback($namespace, $options['parserSetup'], '$parser') . ';';
 238              $php[] = '';
 239              $php[] = '        return $parser;';
 240          }
 241          else
 242              $php[] = '        return ' . $this->exportObject($parser) . ';';
 243          $php[] = '    }';
 244          $php[] = '';
 245          $php[] = '    /**';
 246          $php[] = '    * Return a new instance of s9e\\TextFormatter\\Renderer';
 247          $php[] = '    *';
 248          $php[] = '    * @return s9e\\TextFormatter\\Renderer';
 249          $php[] = '    */';
 250          $php[] = '    public static function getRenderer()';
 251          $php[] = '    {';
 252          if (!empty($options['autoInclude'])
 253           && $this->configurator->rendering->engine instanceof PHP
 254           && isset($this->configurator->rendering->engine->lastFilepath))
 255          {
 256              $className = \get_class($renderer);
 257              $filepath  = \realpath($this->configurator->rendering->engine->lastFilepath);
 258              $php[] = '        if (!class_exists(' . \var_export($className, \true) . ', false)';
 259              $php[] = '         && file_exists(' . \var_export($filepath, \true) . '))';
 260              $php[] = '        {';
 261              $php[] = '            include ' . \var_export($filepath, \true) . ';';
 262              $php[] = '        }';
 263              $php[] = '';
 264          }
 265          if (isset($options['rendererSetup']))
 266          {
 267              $php[] = '        $renderer = ' . $this->exportObject($renderer) . ';';
 268              $php[] = '        ' . $this->exportCallback($namespace, $options['rendererSetup'], '$renderer') . ';';
 269              $php[] = '';
 270              $php[] = '        return $renderer;';
 271          }
 272          else
 273              $php[] = '        return ' . $this->exportObject($renderer) . ';';
 274          $php[] = '    }';
 275          $php[] = '}';
 276          return \implode("\n", $php);
 277      }
 278  	protected function exportCallback($namespace, callable $callback, $argument)
 279      {
 280          if (\is_array($callback) && \is_string($callback[0]))
 281              $callback = $callback[0] . '::' . $callback[1];
 282          if (!\is_string($callback))
 283              return 'call_user_func(' . \var_export($callback, \true) . ', ' . $argument . ')';
 284          if ($callback[0] !== '\\')
 285              $callback = '\\' . $callback;
 286          if (\substr($callback, 0, 2 + \strlen($namespace)) === '\\' . $namespace . '\\')
 287              $callback = \substr($callback, 2 + \strlen($namespace));
 288          return $callback . '(' . $argument . ')';
 289      }
 290  	protected function exportObject($obj)
 291      {
 292          $str = \call_user_func($this->serializer, $obj);
 293          $str = \var_export($str, \true);
 294          return $this->unserializer . '(' . $str . ')';
 295      }
 296  }
 297  
 298  /*
 299  * @package   s9e\TextFormatter
 300  * @copyright Copyright (c) 2010-2019 The s9e Authors
 301  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 302  */
 303  namespace s9e\TextFormatter\Configurator;
 304  interface ConfigProvider
 305  {
 306  	public function asConfig();
 307  }
 308  
 309  /*
 310  * @package   s9e\TextFormatter
 311  * @copyright Copyright (c) 2010-2019 The s9e Authors
 312  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 313  */
 314  namespace s9e\TextFormatter\Configurator;
 315  interface FilterableConfigValue
 316  {
 317  	public function filterConfig($target);
 318  }
 319  
 320  /*
 321  * @package   s9e\TextFormatter
 322  * @copyright Copyright (c) 2010-2019 The s9e Authors
 323  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 324  */
 325  namespace s9e\TextFormatter\Configurator\Helpers;
 326  use DOMAttr;
 327  use RuntimeException;
 328  abstract class AVTHelper
 329  {
 330  	public static function parse($attrValue)
 331      {
 332          \preg_match_all('(\\{\\{|\\{(?:[^\'"}]|\'[^\']*\'|"[^"]*")+\\}|\\{|[^{]++)', $attrValue, $matches);
 333          $tokens  = [];
 334          foreach ($matches[0] as $str)
 335              if ($str === '{{' || $str === '{')
 336                  $tokens[] = ['literal', '{'];
 337              elseif ($str[0] === '{')
 338                  $tokens[] = ['expression', \substr($str, 1, -1)];
 339              else
 340                  $tokens[] = ['literal', \str_replace('}}', '}', $str)];
 341          return $tokens;
 342      }
 343  	public static function replace(DOMAttr $attribute, callable $callback)
 344      {
 345          $tokens = self::parse($attribute->value);
 346          foreach ($tokens as $k => $token)
 347              $tokens[$k] = $callback($token);
 348          $attribute->value = \htmlspecialchars(self::serialize($tokens), \ENT_NOQUOTES, 'UTF-8');
 349      }
 350  	public static function serialize(array $tokens)
 351      {
 352          $attrValue = '';
 353          foreach ($tokens as $token)
 354              if ($token[0] === 'literal')
 355                  $attrValue .= \preg_replace('([{}])', '$0$0', $token[1]);
 356              elseif ($token[0] === 'expression')
 357                  $attrValue .= '{' . $token[1] . '}';
 358              else
 359                  throw new RuntimeException('Unknown token type');
 360          return $attrValue;
 361      }
 362  	public static function toXSL($attrValue)
 363      {
 364          $xsl = '';
 365          foreach (self::parse($attrValue) as $_f6b3b659)
 366          {
 367              list($type, $content) = $_f6b3b659;
 368              if ($type === 'expression')
 369                  $xsl .= '<xsl:value-of select="' . \htmlspecialchars($content, \ENT_COMPAT, 'UTF-8') . '"/>';
 370              elseif (\trim($content) !== $content)
 371                  $xsl .= '<xsl:text>' . \htmlspecialchars($content, \ENT_NOQUOTES, 'UTF-8') . '</xsl:text>';
 372              else
 373                  $xsl .= \htmlspecialchars($content, \ENT_NOQUOTES, 'UTF-8');
 374          }
 375          return $xsl;
 376      }
 377  }
 378  
 379  /*
 380  * @package   s9e\TextFormatter
 381  * @copyright Copyright (c) 2010-2019 The s9e Authors
 382  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 383  */
 384  namespace s9e\TextFormatter\Configurator\Helpers;
 385  class CharacterClassBuilder
 386  {
 387      protected $chars;
 388      public $delimiter = '/';
 389      protected $ranges;
 390  	public function fromList(array $chars)
 391      {
 392          $this->chars = $chars;
 393          $this->unescapeLiterals();
 394          \sort($this->chars);
 395          $this->storeRanges();
 396          $this->reorderDash();
 397          $this->fixCaret();
 398          $this->escapeSpecialChars();
 399          return $this->buildCharacterClass();
 400      }
 401  	protected function buildCharacterClass()
 402      {
 403          $str = '[';
 404          foreach ($this->ranges as $_b7914274)
 405          {
 406              list($start, $end) = $_b7914274;
 407              if ($end > $start + 2)
 408                  $str .= $this->chars[$start] . '-' . $this->chars[$end];
 409              else
 410                  $str .= \implode('', \array_slice($this->chars, $start, $end + 1 - $start));
 411          }
 412          $str .= ']';
 413          return $str;
 414      }
 415  	protected function escapeSpecialChars()
 416      {
 417          $specialChars = ['\\', ']', $this->delimiter];
 418          foreach (\array_intersect($this->chars, $specialChars) as $k => $v)
 419              $this->chars[$k] = '\\' . $v;
 420      }
 421  	protected function fixCaret()
 422      {
 423          $k = \array_search('^', $this->chars, \true);
 424          if ($this->ranges[0][0] !== $k)
 425              return;
 426          if (isset($this->ranges[1]))
 427          {
 428              $range           = $this->ranges[0];
 429              $this->ranges[0] = $this->ranges[1];
 430              $this->ranges[1] = $range;
 431          }
 432          else
 433              $this->chars[$k] = '\\^';
 434      }
 435  	protected function reorderDash()
 436      {
 437          $dashIndex = \array_search('-', $this->chars, \true);
 438          if ($dashIndex === \false)
 439              return;
 440          $k = \array_search([$dashIndex, $dashIndex], $this->ranges, \true);
 441          if ($k > 0)
 442          {
 443              unset($this->ranges[$k]);
 444              \array_unshift($this->ranges, [$dashIndex, $dashIndex]);
 445          }
 446          $commaIndex = \array_search(',', $this->chars);
 447          $range      = [$commaIndex, $dashIndex];
 448          $k          = \array_search($range, $this->ranges, \true);
 449          if ($k !== \false)
 450          {
 451              $this->ranges[$k] = [$commaIndex, $commaIndex];
 452              \array_unshift($this->ranges, [$dashIndex, $dashIndex]);
 453          }
 454      }
 455  	protected function storeRanges()
 456      {
 457          $values = [];
 458          foreach ($this->chars as $char)
 459              if (\strlen($char) === 1)
 460                  $values[] = \ord($char);
 461              else
 462                  $values[] = \false;
 463          $i = \count($values) - 1;
 464          $ranges = [];
 465          while ($i >= 0)
 466          {
 467              $start = $i;
 468              $end   = $i;
 469              while ($start > 0 && $values[$start - 1] === $values[$end] - ($end + 1 - $start))
 470                  --$start;
 471              $ranges[] = [$start, $end];
 472              $i = $start - 1;
 473          }
 474          $this->ranges = \array_reverse($ranges);
 475      }
 476  	protected function unescapeLiterals()
 477      {
 478          foreach ($this->chars as $k => $char)
 479              if ($char[0] === '\\' && \preg_match('(^\\\\[^a-z]$)Di', $char))
 480                  $this->chars[$k] = \substr($char, 1);
 481      }
 482  }
 483  
 484  /*
 485  * @package   s9e\TextFormatter
 486  * @copyright Copyright (c) 2010-2019 The s9e Authors
 487  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 488  */
 489  namespace s9e\TextFormatter\Configurator\Helpers;
 490  use RuntimeException;
 491  use Traversable;
 492  use s9e\TextFormatter\Configurator\ConfigProvider;
 493  use s9e\TextFormatter\Configurator\FilterableConfigValue;
 494  use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
 495  abstract class ConfigHelper
 496  {
 497  	public static function filterConfig(array $config, $target = 'PHP')
 498      {
 499          $filteredConfig = [];
 500          foreach ($config as $name => $value)
 501          {
 502              if ($value instanceof FilterableConfigValue)
 503              {
 504                  $value = $value->filterConfig($target);
 505                  if (!isset($value))
 506                      continue;
 507              }
 508              if (\is_array($value))
 509                  $value = self::filterConfig($value, $target);
 510              $filteredConfig[$name] = $value;
 511          }
 512          return $filteredConfig;
 513      }
 514  	public static function generateQuickMatchFromList(array $strings)
 515      {
 516          foreach ($strings as $string)
 517          {
 518              $stringLen  = \strlen($string);
 519              $substrings = [];
 520              for ($len = $stringLen; $len; --$len)
 521              {
 522                  $pos = $stringLen - $len;
 523                  do
 524                  {
 525                      $substrings[\substr($string, $pos, $len)] = 1;
 526                  }
 527                  while (--$pos >= 0);
 528              }
 529              if (isset($goodStrings))
 530              {
 531                  $goodStrings = \array_intersect_key($goodStrings, $substrings);
 532                  if (empty($goodStrings))
 533                      break;
 534              }
 535              else
 536                  $goodStrings = $substrings;
 537          }
 538          if (empty($goodStrings))
 539              return \false;
 540          return \strval(\key($goodStrings));
 541      }
 542  	public static function optimizeArray(array &$config, array &$cache = [])
 543      {
 544          foreach ($config as $k => &$v)
 545          {
 546              if (!\is_array($v))
 547                  continue;
 548              self::optimizeArray($v, $cache);
 549              $cacheKey = \serialize($v);
 550              if (!isset($cache[$cacheKey]))
 551                  $cache[$cacheKey] = $v;
 552              $config[$k] =& $cache[$cacheKey];
 553          }
 554          unset($v);
 555      }
 556  	public static function toArray($value, $keepEmpty = \false, $keepNull = \false)
 557      {
 558          $array = [];
 559          foreach ($value as $k => $v)
 560          {
 561              $isDictionary = $v instanceof Dictionary;
 562              if ($v instanceof ConfigProvider)
 563                  $v = $v->asConfig();
 564              elseif ($v instanceof Traversable || \is_array($v))
 565                  $v = self::toArray($v, $keepEmpty, $keepNull);
 566              elseif (\is_scalar($v) || \is_null($v))
 567                  ;
 568              else
 569              {
 570                  $type = (\is_object($v))
 571                        ? 'an instance of ' . \get_class($v)
 572                        : 'a ' . \gettype($v);
 573                  throw new RuntimeException('Cannot convert ' . $type . ' to array');
 574              }
 575              if (!isset($v) && !$keepNull)
 576                  continue;
 577              if (!$keepEmpty && $v === [])
 578                  continue;
 579              $array[$k] = ($isDictionary) ? new Dictionary($v) : $v;
 580          }
 581          return $array;
 582      }
 583  }
 584  
 585  /*
 586  * @package   s9e\TextFormatter
 587  * @copyright Copyright (c) 2010-2019 The s9e Authors
 588  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 589  */
 590  namespace s9e\TextFormatter\Configurator\Helpers;
 591  use DOMElement;
 592  use DOMXPath;
 593  class ElementInspector
 594  {
 595      protected static $htmlElements = [
 596          'a'=>['c'=>"\17\0\0\0\0\1",'c3'=>'@href','ac'=>"\0",'dd'=>"\10\0\0\0\0\1",'t'=>1,'fe'=>1],
 597          'abbr'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 598          'address'=>['c'=>"\3\40",'ac'=>"\1",'dd'=>"\200\44",'b'=>1,'cp'=>['p']],
 599          'article'=>['c'=>"\3\4",'ac'=>"\1",'dd'=>"\0\0\0\0\10",'b'=>1,'cp'=>['p']],
 600          'aside'=>['c'=>"\3\4",'ac'=>"\1",'dd'=>"\0\0\0\0\10",'b'=>1,'cp'=>['p']],
 601          'audio'=>['c'=>"\57",'c3'=>'@controls','c1'=>'@controls','ac'=>"\0\0\0\104",'ac26'=>'not(@src)','dd'=>"\0\0\0\0\0\2",'dd41'=>'@src','t'=>1],
 602          'b'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
 603          'base'=>['c'=>"\100",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
 604          'bdi'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 605          'bdo'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 606          'blockquote'=>['c'=>"\23",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 607          'body'=>['c'=>"\20\0\2",'ac'=>"\1",'dd'=>"\0",'b'=>1],
 608          'br'=>['c'=>"\5",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
 609          'button'=>['c'=>"\17\1",'ac'=>"\4",'dd'=>"\10"],
 610          'caption'=>['c'=>"\0\2",'ac'=>"\1",'dd'=>"\0\0\0\200",'b'=>1],
 611          'cite'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 612          'code'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
 613          'col'=>['c'=>"\0\0\10",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
 614          'colgroup'=>['c'=>"\0\2",'ac'=>"\0\0\10",'ac19'=>'not(@span)','dd'=>"\0",'nt'=>1,'e'=>1,'e?'=>'@span','b'=>1],
 615          'data'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 616          'datalist'=>['c'=>"\5",'ac'=>"\4\0\200\10",'dd'=>"\0"],
 617          'dd'=>['c'=>"\0\100\100",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['dd','dt']],
 618          'del'=>['c'=>"\5",'ac'=>"\0",'dd'=>"\0",'t'=>1],
 619          'details'=>['c'=>"\33",'ac'=>"\1\0\0\2",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 620          'dfn'=>['c'=>"\7\0\0\0\40",'ac'=>"\4",'dd'=>"\0\0\0\0\40"],
 621          'dialog'=>['c'=>"\21",'ac'=>"\1",'dd'=>"\0",'b'=>1],
 622          'div'=>['c'=>"\3\100",'ac'=>"\1\0\300",'ac0'=>'not(ancestor::dl)','dd'=>"\0",'b'=>1,'cp'=>['p']],
 623          'dl'=>['c'=>"\3",'c1'=>'dt and dd','ac'=>"\0\100\200",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['p']],
 624          'dt'=>['c'=>"\0\100\100",'ac'=>"\1",'dd'=>"\200\4\0\40",'b'=>1,'cp'=>['dd','dt']],
 625          'em'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
 626          'embed'=>['c'=>"\57",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
 627          'fieldset'=>['c'=>"\23\1",'ac'=>"\1\0\0\20",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 628          'figcaption'=>['c'=>"\0\0\0\0\0\4",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 629          'figure'=>['c'=>"\23",'ac'=>"\1\0\0\0\0\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 630          'footer'=>['c'=>"\3\40",'ac'=>"\1",'dd'=>"\0\0\0\0\10",'b'=>1,'cp'=>['p']],
 631          'form'=>['c'=>"\3\0\0\0\20",'ac'=>"\1",'dd'=>"\0\0\0\0\20",'b'=>1,'cp'=>['p']],
 632          'h1'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 633          'h2'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 634          'h3'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 635          'h4'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 636          'h5'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 637          'h6'=>['c'=>"\203",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 638          'head'=>['c'=>"\0\0\2",'ac'=>"\100",'dd'=>"\0",'nt'=>1,'b'=>1],
 639          'header'=>['c'=>"\3\40\0\40",'ac'=>"\1",'dd'=>"\0\0\0\0\10",'b'=>1,'cp'=>['p']],
 640          'hr'=>['c'=>"\1",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1,'cp'=>['p']],
 641          'html'=>['c'=>"\0",'ac'=>"\0\0\2",'dd'=>"\0",'nt'=>1,'b'=>1],
 642          'i'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
 643          'iframe'=>['c'=>"\57",'ac'=>"\4",'dd'=>"\0"],
 644          'img'=>['c'=>"\57\20\4",'c3'=>'@usemap','ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
 645          'input'=>['c'=>"\17\20",'c3'=>'@type!="hidden"','c12'=>'@type!="hidden" or @type="hidden"','c1'=>'@type!="hidden"','ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
 646          'ins'=>['c'=>"\7",'ac'=>"\0",'dd'=>"\0",'t'=>1],
 647          'kbd'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 648          'label'=>['c'=>"\17\20\0\0\4",'ac'=>"\4",'dd'=>"\0\200\0\0\4"],
 649          'legend'=>['c'=>"\0\0\0\20",'ac'=>"\204",'dd'=>"\0",'b'=>1],
 650          'li'=>['c'=>"\0\0\0\0\200",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['li']],
 651          'link'=>['c'=>"\105",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1],
 652          'main'=>['c'=>"\3\0\0\0\10",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 653          'mark'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 654          'media element'=>['c'=>"\0\0\0\0\0\2",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'b'=>1],
 655          'meta'=>['c'=>"\100",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
 656          'meter'=>['c'=>"\7\200\0\0\2",'ac'=>"\4",'dd'=>"\0\0\0\0\2"],
 657          'nav'=>['c'=>"\3\4",'ac'=>"\1",'dd'=>"\0\0\0\0\10",'b'=>1,'cp'=>['p']],
 658          'object'=>['c'=>"\47\1",'ac'=>"\0\0\0\0\1",'dd'=>"\0",'t'=>1],
 659          'ol'=>['c'=>"\3",'c1'=>'li','ac'=>"\0\0\200\0\200",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['p']],
 660          'optgroup'=>['c'=>"\0\0\1",'ac'=>"\0\0\200\10",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['optgroup','option']],
 661          'option'=>['c'=>"\0\0\1\10",'ac'=>"\0",'dd'=>"\0",'b'=>1,'cp'=>['option']],
 662          'output'=>['c'=>"\7\1",'ac'=>"\4",'dd'=>"\0"],
 663          'p'=>['c'=>"\3",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 664          'param'=>['c'=>"\0\0\0\0\1",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
 665          'picture'=>['c'=>"\45",'ac'=>"\0\0\204",'dd'=>"\0",'nt'=>1],
 666          'pre'=>['c'=>"\3",'ac'=>"\4",'dd'=>"\0",'pre'=>1,'b'=>1,'cp'=>['p']],
 667          'progress'=>['c'=>"\7\200\0\1",'ac'=>"\4",'dd'=>"\0\0\0\1"],
 668          'q'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 669          'rb'=>['c'=>"\0\10",'ac'=>"\4",'dd'=>"\0",'b'=>1],
 670          'rp'=>['c'=>"\0\10\40",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['rp','rt']],
 671          'rt'=>['c'=>"\0\10\40",'ac'=>"\4",'dd'=>"\0",'b'=>1,'cp'=>['rp','rt']],
 672          'rtc'=>['c'=>"\0\10",'ac'=>"\4\0\40",'dd'=>"\0",'b'=>1],
 673          'ruby'=>['c'=>"\7",'ac'=>"\4\10",'dd'=>"\0"],
 674          's'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
 675          'samp'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 676          'script'=>['c'=>"\105\0\200",'ac'=>"\0",'dd'=>"\0",'to'=>1],
 677          'section'=>['c'=>"\3\4",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['p']],
 678          'select'=>['c'=>"\17\1",'ac'=>"\0\0\201",'dd'=>"\0",'nt'=>1],
 679          'small'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
 680          'source'=>['c'=>"\0\0\4\4",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
 681          'span'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 682          'strong'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
 683          'style'=>['c'=>"\101",'ac'=>"\0",'dd'=>"\0",'to'=>1,'b'=>1],
 684          'sub'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 685          'summary'=>['c'=>"\0\0\0\2",'ac'=>"\204",'dd'=>"\0",'b'=>1],
 686          'sup'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 687          'table'=>['c'=>"\3\0\0\200",'ac'=>"\0\2\200",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['p']],
 688          'tbody'=>['c'=>"\0\2",'ac'=>"\0\0\200\0\100",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['tbody','td','th','thead','tr']],
 689          'td'=>['c'=>"\20\0\20",'ac'=>"\1",'dd'=>"\0",'b'=>1,'cp'=>['td','th']],
 690          'template'=>['c'=>"\0\0\10",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'b'=>1],
 691          'textarea'=>['c'=>"\17\1",'ac'=>"\0",'dd'=>"\0",'pre'=>1,'to'=>1],
 692          'tfoot'=>['c'=>"\0\2",'ac'=>"\0\0\200\0\100",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['tbody','td','th','thead','tr']],
 693          'th'=>['c'=>"\0\0\20",'ac'=>"\1",'dd'=>"\200\4\0\40",'b'=>1,'cp'=>['td','th']],
 694          'thead'=>['c'=>"\0\2",'ac'=>"\0\0\200\0\100",'dd'=>"\0",'nt'=>1,'b'=>1],
 695          'time'=>['c'=>"\7",'ac'=>"\4",'ac2'=>'@datetime','dd'=>"\0"],
 696          'title'=>['c'=>"\100",'ac'=>"\0",'dd'=>"\0",'to'=>1,'b'=>1],
 697          'tr'=>['c'=>"\0\2\0\0\100",'ac'=>"\0\0\220",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['td','th','tr']],
 698          'track'=>['c'=>"\0\0\0\100",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1,'b'=>1],
 699          'u'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0",'fe'=>1],
 700          'ul'=>['c'=>"\3",'c1'=>'li','ac'=>"\0\0\200\0\200",'dd'=>"\0",'nt'=>1,'b'=>1,'cp'=>['p']],
 701          'var'=>['c'=>"\7",'ac'=>"\4",'dd'=>"\0"],
 702          'video'=>['c'=>"\57",'c3'=>'@controls','ac'=>"\0\0\0\104",'ac26'=>'not(@src)','dd'=>"\0\0\0\0\0\2",'dd41'=>'@src','t'=>1],
 703          'wbr'=>['c'=>"\5",'ac'=>"\0",'dd'=>"\0",'nt'=>1,'e'=>1,'v'=>1]
 704      ];
 705  	public static function closesParent(DOMElement $child, DOMElement $parent)
 706      {
 707          $parentName = $parent->nodeName;
 708          $childName  = $child->nodeName;
 709          return !empty(self::$htmlElements[$childName]['cp']) && \in_array($parentName, self::$htmlElements[$childName]['cp'], \true);
 710      }
 711  	public static function disallowsText(DOMElement $element)
 712      {
 713          return self::hasProperty($element, 'nt');
 714      }
 715  	public static function getAllowChildBitfield(DOMElement $element)
 716      {
 717          return self::getBitfield($element, 'ac');
 718      }
 719  	public static function getCategoryBitfield(DOMElement $element)
 720      {
 721          return self::getBitfield($element, 'c');
 722      }
 723  	public static function getDenyDescendantBitfield(DOMElement $element)
 724      {
 725          return self::getBitfield($element, 'dd');
 726      }
 727  	public static function isBlock(DOMElement $element)
 728      {
 729          return self::hasProperty($element, 'b');
 730      }
 731  	public static function isEmpty(DOMElement $element)
 732      {
 733          return self::hasProperty($element, 'e');
 734      }
 735  	public static function isFormattingElement(DOMElement $element)
 736      {
 737          return self::hasProperty($element, 'fe');
 738      }
 739  	public static function isTextOnly(DOMElement $element)
 740      {
 741          return self::hasProperty($element, 'to');
 742      }
 743  	public static function isTransparent(DOMElement $element)
 744      {
 745          return self::hasProperty($element, 't');
 746      }
 747  	public static function isVoid(DOMElement $element)
 748      {
 749          return self::hasProperty($element, 'v');
 750      }
 751  	public static function preservesWhitespace(DOMElement $element)
 752      {
 753          return self::hasProperty($element, 'pre');
 754      }
 755  	protected static function evaluate($query, DOMElement $element)
 756      {
 757          $xpath = new DOMXPath($element->ownerDocument);
 758          return $xpath->evaluate('boolean(' . $query . ')', $element);
 759      }
 760  	protected static function getBitfield(DOMElement $element, $name)
 761      {
 762          $props    = self::getProperties($element);
 763          $bitfield = self::toBin($props[$name]);
 764          foreach (\array_keys(\array_filter(\str_split($bitfield, 1))) as $bitNumber)
 765          {
 766              $conditionName = $name . $bitNumber;
 767              if (isset($props[$conditionName]) && !self::evaluate($props[$conditionName], $element))
 768                  $bitfield[$bitNumber] = '0';
 769          }
 770          return self::toRaw($bitfield);
 771      }
 772  	protected static function getProperties(DOMElement $element)
 773      {
 774          return (isset(self::$htmlElements[$element->nodeName])) ? self::$htmlElements[$element->nodeName] : self::$htmlElements['span'];
 775      }
 776  	protected static function hasProperty(DOMElement $element, $propName)
 777      {
 778          $props = self::getProperties($element);
 779          return !empty($props[$propName]) && (!isset($props[$propName . '?']) || self::evaluate($props[$propName . '?'], $element));
 780      }
 781  	protected static function toBin($raw)
 782      {
 783          $bin = '';
 784          foreach (\str_split($raw, 1) as $char)
 785              $bin .= \strrev(\substr('0000000' . \decbin(\ord($char)), -8));
 786          return $bin;
 787      }
 788  	protected static function toRaw($bin)
 789      {
 790          return \implode('', \array_map('chr', \array_map('bindec', \array_map('strrev', \str_split($bin, 8)))));
 791      }
 792  }
 793  
 794  /*
 795  * @package   s9e\TextFormatter
 796  * @copyright Copyright (c) 2010-2019 The s9e Authors
 797  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 798  */
 799  namespace s9e\TextFormatter\Configurator\Helpers;
 800  use DOMDocument;
 801  use DOMXPath;
 802  abstract class NodeLocator
 803  {
 804  	public static function getAttributesByRegexp(DOMDocument $dom, $regexp)
 805      {
 806          return self::getNodesByRegexp($dom, $regexp, 'attribute');
 807      }
 808  	public static function getCSSNodes(DOMDocument $dom)
 809      {
 810          $regexp = '/^style$/i';
 811          $nodes  = \array_merge(
 812              self::getAttributesByRegexp($dom, $regexp),
 813              self::getElementsByRegexp($dom, '/^style$/i')
 814          );
 815          return $nodes;
 816      }
 817  	public static function getElementsByRegexp(DOMDocument $dom, $regexp)
 818      {
 819          return self::getNodesByRegexp($dom, $regexp, 'element');
 820      }
 821  	public static function getJSNodes(DOMDocument $dom)
 822      {
 823          $regexp = '/^(?:data-s9e-livepreview-postprocess$|on)/i';
 824          $nodes  = \array_merge(
 825              self::getAttributesByRegexp($dom, $regexp),
 826              self::getElementsByRegexp($dom, '/^script$/i')
 827          );
 828          return $nodes;
 829      }
 830  	public static function getObjectParamsByRegexp(DOMDocument $dom, $regexp)
 831      {
 832          $xpath = new DOMXPath($dom);
 833          $nodes = [];
 834          foreach (self::getAttributesByRegexp($dom, $regexp) as $attribute)
 835              if ($attribute->nodeType === \XML_ATTRIBUTE_NODE)
 836              {
 837                  if (\strtolower($attribute->parentNode->localName) === 'embed')
 838                      $nodes[] = $attribute;
 839              }
 840              elseif ($xpath->evaluate('count(ancestor::embed)', $attribute))
 841                  $nodes[] = $attribute;
 842          foreach ($xpath->query('//object//param') as $param)
 843              if (\preg_match($regexp, $param->getAttribute('name')))
 844                  $nodes[] = $param;
 845          return $nodes;
 846      }
 847  	public static function getURLNodes(DOMDocument $dom)
 848      {
 849          $regexp = '/(?:^(?:action|background|c(?:ite|lassid|odebase)|data|formaction|href|icon|longdesc|manifest|p(?:ing|luginspage|oster|rofile)|usemap)|src)$/i';
 850          $nodes  = self::getAttributesByRegexp($dom, $regexp);
 851          foreach (self::getObjectParamsByRegexp($dom, '/^(?:dataurl|movie)$/i') as $param)
 852          {
 853              $node = $param->getAttributeNode('value');
 854              if ($node)
 855                  $nodes[] = $node;
 856          }
 857          return $nodes;
 858      }
 859  	protected static function getNodes(DOMDocument $dom, $type)
 860      {
 861          $nodes  = [];
 862          $prefix = ($type === 'attribute') ? '@' : '';
 863          $xpath  = new DOMXPath($dom);
 864          foreach ($xpath->query('//' . $prefix . '*') as $node)
 865              $nodes[] = [$node, $node->nodeName];
 866          foreach ($xpath->query('//xsl:' . $type) as $node)
 867              $nodes[] = [$node, $node->getAttribute('name')];
 868          foreach ($xpath->query('//xsl:copy-of') as $node)
 869              if (\preg_match('/^' . $prefix . '(\\w+)$/', $node->getAttribute('select'), $m))
 870                  $nodes[] = [$node, $m[1]];
 871          return $nodes;
 872      }
 873  	protected static function getNodesByRegexp(DOMDocument $dom, $regexp, $type)
 874      {
 875          $nodes = [];
 876          foreach (self::getNodes($dom, $type) as $_13697a20)
 877          {
 878              list($node, $name) = $_13697a20;
 879              if (\preg_match($regexp, $name))
 880                  $nodes[] = $node;
 881          }
 882          return $nodes;
 883      }
 884  }
 885  
 886  /*
 887  * @package   s9e\TextFormatter
 888  * @copyright Copyright (c) 2010-2019 The s9e Authors
 889  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
 890  */
 891  namespace s9e\TextFormatter\Configurator\Helpers;
 892  use RuntimeException;
 893  abstract class RegexpBuilder
 894  {
 895      protected static $characterClassBuilder;
 896  	public static function fromList(array $words, array $options = [])
 897      {
 898          if (empty($words))
 899              return '';
 900          $options += [
 901              'delimiter'       => '/',
 902              'caseInsensitive' => \false,
 903              'specialChars'    => [],
 904              'unicode'         => \true,
 905              'useLookahead'    => \false
 906          ];
 907          if ($options['caseInsensitive'])
 908          {
 909              foreach ($words as &$word)
 910                  $word = \strtr(
 911                      $word,
 912                      'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
 913                      'abcdefghijklmnopqrstuvwxyz'
 914                  );
 915              unset($word);
 916          }
 917          $words = \array_unique($words);
 918          \sort($words);
 919          $initials = [];
 920          $esc  = $options['specialChars'];
 921          $esc += [$options['delimiter'] => '\\' . $options['delimiter']];
 922          $esc += [
 923              '!' => '!',
 924              '-' => '-',
 925              ':' => ':',
 926              '<' => '<',
 927              '=' => '=',
 928              '>' => '>',
 929              '}' => '}'
 930          ];
 931          $splitWords = [];
 932          foreach ($words as $word)
 933          {
 934              $regexp = ($options['unicode']) ? '(.)us' : '(.)s';
 935              if (\preg_match_all($regexp, $word, $matches) === \false)
 936                  throw new RuntimeException("Invalid UTF-8 string '" . $word . "'");
 937              $splitWord = [];
 938              foreach ($matches[0] as $pos => $c)
 939              {
 940                  if (!isset($esc[$c]))
 941                      $esc[$c] = \preg_quote($c);
 942                  if ($pos === 0)
 943                      $initials[] = $esc[$c];
 944                  $splitWord[] = $esc[$c];
 945              }
 946              $splitWords[] = $splitWord;
 947          }
 948          self::$characterClassBuilder            = new CharacterClassBuilder;
 949          self::$characterClassBuilder->delimiter = $options['delimiter'];
 950          $regexp = self::assemble([self::mergeChains($splitWords)]);
 951          if ($options['useLookahead']
 952           && \count($initials) > 1
 953           && $regexp[0] !== '[')
 954          {
 955              $useLookahead = \true;
 956              foreach ($initials as $initial)
 957                  if (!self::canBeUsedInCharacterClass($initial))
 958                  {
 959                      $useLookahead = \false;
 960                      break;
 961                  }
 962              if ($useLookahead)
 963                  $regexp = '(?=' . self::generateCharacterClass($initials) . ')' . $regexp;
 964          }
 965          return $regexp;
 966      }
 967  	protected static function mergeChains(array $chains, $preventRemerge = \false)
 968      {
 969          if (!isset($chains[1]))
 970              return $chains[0];
 971          $mergedChain = self::removeLongestCommonPrefix($chains);
 972          if (!isset($chains[0][0])
 973           && !\array_filter($chains))
 974              return $mergedChain;
 975          $suffix = self::removeLongestCommonSuffix($chains);
 976          if (isset($chains[1]))
 977          {
 978              self::optimizeDotChains($chains);
 979              self::optimizeCatchallChains($chains);
 980          }
 981          $endOfChain = \false;
 982          $remerge = \false;
 983          $groups = [];
 984          foreach ($chains as $chain)
 985          {
 986              if (!isset($chain[0]))
 987              {
 988                  $endOfChain = \true;
 989                  continue;
 990              }
 991              $head = $chain[0];
 992              if (isset($groups[$head]))
 993                  $remerge = \true;
 994              $groups[$head][] = $chain;
 995          }
 996          $characterClass = [];
 997          foreach ($groups as $head => $groupChains)
 998          {
 999              $head = (string) $head;
1000              if ($groupChains === [[$head]]
1001               && self::canBeUsedInCharacterClass($head))
1002                  $characterClass[$head] = $head;
1003          }
1004          \sort($characterClass);
1005          if (isset($characterClass[1]))
1006          {
1007              foreach ($characterClass as $char)
1008                  unset($groups[$char]);
1009              $head = self::generateCharacterClass($characterClass);
1010              $groups[$head][] = [$head];
1011              $groups = [$head => $groups[$head]]
1012                      + $groups;
1013          }
1014          if ($remerge && !$preventRemerge)
1015          {
1016              $mergedChains = [];
1017              foreach ($groups as $head => $groupChains)
1018                  $mergedChains[] = self::mergeChains($groupChains);
1019              self::mergeTails($mergedChains);
1020              $regexp = \implode('', self::mergeChains($mergedChains, \true));
1021              if ($endOfChain)
1022                  $regexp = self::makeRegexpOptional($regexp);
1023              $mergedChain[] = $regexp;
1024          }
1025          else
1026          {
1027              self::mergeTails($chains);
1028              $mergedChain[] = self::assemble($chains);
1029          }
1030          foreach ($suffix as $atom)
1031              $mergedChain[] = $atom;
1032          return $mergedChain;
1033      }
1034  	protected static function mergeTails(array &$chains)
1035      {
1036          self::mergeTailsCC($chains);
1037          self::mergeTailsAltern($chains);
1038          $chains = \array_values($chains);
1039      }
1040  	protected static function mergeTailsCC(array &$chains)
1041      {
1042          $groups = [];
1043          foreach ($chains as $k => $chain)
1044              if (isset($chain[1])
1045               && !isset($chain[2])
1046               && self::canBeUsedInCharacterClass($chain[0]))
1047                  $groups[$chain[1]][$k] = $chain;
1048          foreach ($groups as $groupChains)
1049          {
1050              if (\count($groupChains) < 2)
1051                  continue;
1052              $chains = \array_diff_key($chains, $groupChains);
1053              $chains[] = self::mergeChains(\array_values($groupChains));
1054          }
1055      }
1056  	protected static function mergeTailsAltern(array &$chains)
1057      {
1058          $groups = [];
1059          foreach ($chains as $k => $chain)
1060              if (!empty($chain))
1061              {
1062                  $tail = \array_slice($chain, -1);
1063                  $groups[$tail[0]][$k] = $chain;
1064              }
1065          foreach ($groups as $tail => $groupChains)
1066          {
1067              if (\count($groupChains) < 2)
1068                  continue;
1069              $mergedChain = self::mergeChains(\array_values($groupChains));
1070              $oldLen = 0;
1071              foreach ($groupChains as $groupChain)
1072                  $oldLen += \array_sum(\array_map('strlen', $groupChain));
1073              if ($oldLen <= \array_sum(\array_map('strlen', $mergedChain)))
1074                  continue;
1075              $chains = \array_diff_key($chains, $groupChains);
1076              $chains[] = $mergedChain;
1077          }
1078      }
1079  	protected static function removeLongestCommonPrefix(array &$chains)
1080      {
1081          $pLen = 0;
1082          while (1)
1083          {
1084              $c = \null;
1085              foreach ($chains as $chain)
1086              {
1087                  if (!isset($chain[$pLen]))
1088                      break 2;
1089                  if (!isset($c))
1090                  {
1091                      $c = $chain[$pLen];
1092                      continue;
1093                  }
1094                  if ($chain[$pLen] !== $c)
1095                      break 2;
1096              }
1097              ++$pLen;
1098          }
1099          if (!$pLen)
1100              return [];
1101          $prefix = \array_slice($chains[0], 0, $pLen);
1102          foreach ($chains as &$chain)
1103              $chain = \array_slice($chain, $pLen);
1104          unset($chain);
1105          return $prefix;
1106      }
1107  	protected static function removeLongestCommonSuffix(array &$chains)
1108      {
1109          $chainsLen = \array_map('count', $chains);
1110          $maxLen = \min($chainsLen);
1111          if (\max($chainsLen) === $maxLen)
1112              --$maxLen;
1113          $sLen = 0;
1114          while ($sLen < $maxLen)
1115          {
1116              $c = \null;
1117              foreach ($chains as $k => $chain)
1118              {
1119                  $pos = $chainsLen[$k] - ($sLen + 1);
1120                  if (!isset($c))
1121                  {
1122                      $c = $chain[$pos];
1123                      continue;
1124                  }
1125                  if ($chain[$pos] !== $c)
1126                      break 2;
1127              }
1128              ++$sLen;
1129          }
1130          if (!$sLen)
1131              return [];
1132          $suffix = \array_slice($chains[0], -$sLen);
1133          foreach ($chains as &$chain)
1134              $chain = \array_slice($chain, 0, -$sLen);
1135          unset($chain);
1136          return $suffix;
1137      }
1138  	protected static function assemble(array $chains)
1139      {
1140          $endOfChain = \false;
1141          $regexps        = [];
1142          $characterClass = [];
1143          foreach ($chains as $chain)
1144          {
1145              if (empty($chain))
1146              {
1147                  $endOfChain = \true;
1148                  continue;
1149              }
1150              if (!isset($chain[1])
1151               && self::canBeUsedInCharacterClass($chain[0]))
1152                  $characterClass[$chain[0]] = $chain[0];
1153              else
1154                  $regexps[] = \implode('', $chain);
1155          }
1156          if (!empty($characterClass))
1157          {
1158              \sort($characterClass);
1159              $regexp = (isset($characterClass[1]))
1160                      ? self::generateCharacterClass($characterClass)
1161                      : $characterClass[0];
1162              \array_unshift($regexps, $regexp);
1163          }
1164          if (empty($regexps))
1165              return '';
1166          if (isset($regexps[1]))
1167          {
1168              $regexp = \implode('|', $regexps);
1169              $regexp = ((self::canUseAtomicGrouping($regexp)) ? '(?>' : '(?:') . $regexp . ')';
1170          }
1171          else
1172              $regexp = $regexps[0];
1173          if ($endOfChain)
1174              $regexp = self::makeRegexpOptional($regexp);
1175          return $regexp;
1176      }
1177  	protected static function makeRegexpOptional($regexp)
1178      {
1179          if (\preg_match('#^\\.\\+\\??$#', $regexp))
1180              return \str_replace('+', '*', $regexp);
1181          if (\preg_match('#^(\\\\?.)((?:\\1\\?)+)$#Du', $regexp, $m))
1182              return $m[1] . '?' . $m[2];
1183          if (\preg_match('#^(?:[$^]|\\\\[bBAZzGQEK])$#', $regexp))
1184              return '';
1185          if (\preg_match('#^\\\\?.$#Dus', $regexp))
1186              $isAtomic = \true;
1187          elseif (\preg_match('#^[^[(].#s', $regexp))
1188              $isAtomic = \false;
1189          else
1190          {
1191              $def    = RegexpParser::parse('#' . $regexp . '#');
1192              $tokens = $def['tokens'];
1193              switch (\count($tokens))
1194              {
1195                  case 1:
1196                      $startPos = $tokens[0]['pos'];
1197                      $len      = $tokens[0]['len'];
1198                      $isAtomic = (bool) ($startPos === 0 && $len === \strlen($regexp));
1199                      if ($isAtomic && $tokens[0]['type'] === 'characterClass')
1200                      {
1201                          $regexp = \rtrim($regexp, '+*?');
1202                          if (!empty($tokens[0]['quantifiers']) && $tokens[0]['quantifiers'] !== '?')
1203                              $regexp .= '*';
1204                      }
1205                      break;
1206                  case 2:
1207                      if ($tokens[0]['type'] === 'nonCapturingSubpatternStart'
1208                       && $tokens[1]['type'] === 'nonCapturingSubpatternEnd')
1209                      {
1210                          $startPos = $tokens[0]['pos'];
1211                          $len      = $tokens[1]['pos'] + $tokens[1]['len'];
1212                          $isAtomic = (bool) ($startPos === 0 && $len === \strlen($regexp));
1213                          break;
1214                      }
1215                      default:
1216                      $isAtomic = \false;
1217              }
1218          }
1219          if (!$isAtomic)
1220              $regexp = ((self::canUseAtomicGrouping($regexp)) ? '(?>' : '(?:') . $regexp . ')';
1221          $regexp .= '?';
1222          return $regexp;
1223      }
1224  	protected static function generateCharacterClass(array $chars)
1225      {
1226          return self::$characterClassBuilder->fromList($chars);
1227      }
1228  	protected static function canBeUsedInCharacterClass($char)
1229      {
1230          if (\preg_match('#^\\\\[aefnrtdDhHsSvVwW]$#D', $char))
1231              return \true;
1232          if (\preg_match('#^\\\\[^A-Za-z0-9]$#Dus', $char))
1233              return \true;
1234          if (\preg_match('#..#Dus', $char))
1235              return \false;
1236          if (\preg_quote($char) !== $char
1237           && !\preg_match('#^[-!:<=>}]$#D', $char))
1238              return \false;
1239          return \true;
1240      }
1241  	protected static function optimizeDotChains(array &$chains)
1242      {
1243          $validAtoms = [
1244              '\\d' => 1, '\\D' => 1, '\\h' => 1, '\\H' => 1,
1245              '\\s' => 1, '\\S' => 1, '\\v' => 1, '\\V' => 1,
1246              '\\w' => 1, '\\W' => 1,
1247              '\\^' => 1, '\\$' => 1, '\\.' => 1, '\\?' => 1,
1248              '\\[' => 1, '\\]' => 1, '\\(' => 1, '\\)' => 1,
1249              '\\+' => 1, '\\*' => 1, '\\\\' => 1
1250          ];
1251          do
1252          {
1253              $hasMoreDots = \false;
1254              foreach ($chains as $k1 => $dotChain)
1255              {
1256                  $dotKeys = \array_keys($dotChain, '.?', \true);
1257                  if (!empty($dotKeys))
1258                  {
1259                      $dotChain[$dotKeys[0]] = '.';
1260                      $chains[$k1] = $dotChain;
1261                      \array_splice($dotChain, $dotKeys[0], 1);
1262                      $chains[] = $dotChain;
1263                      if (isset($dotKeys[1]))
1264                          $hasMoreDots = \true;
1265                  }
1266              }
1267          }
1268          while ($hasMoreDots);
1269          foreach ($chains as $k1 => $dotChain)
1270          {
1271              $dotKeys = \array_keys($dotChain, '.', \true);
1272              if (empty($dotKeys))
1273                  continue;
1274              foreach ($chains as $k2 => $tmpChain)
1275              {
1276                  if ($k2 === $k1)
1277                      continue;
1278                  foreach ($dotKeys as $dotKey)
1279                  {
1280                      if (!isset($tmpChain[$dotKey]))
1281                          continue 2;
1282                      if (!\preg_match('#^.$#Du', \preg_quote($tmpChain[$dotKey]))
1283                       && !isset($validAtoms[$tmpChain[$dotKey]]))
1284                          continue 2;
1285                      $tmpChain[$dotKey] = '.';
1286                  }
1287                  if ($tmpChain === $dotChain)
1288                      unset($chains[$k2]);
1289              }
1290          }
1291      }
1292  	protected static function optimizeCatchallChains(array &$chains)
1293      {
1294          $precedence = [
1295              '.*'  => 3,
1296              '.*?' => 2,
1297              '.+'  => 1,
1298              '.+?' => 0
1299          ];
1300          $tails = [];
1301          foreach ($chains as $k => $chain)
1302          {
1303              if (!isset($chain[0]))
1304                  continue;
1305              $head = $chain[0];
1306              if (!isset($precedence[$head]))
1307                  continue;
1308              $tail = \implode('', \array_slice($chain, 1));
1309              if (!isset($tails[$tail])
1310               || $precedence[$head] > $tails[$tail]['precedence'])
1311                  $tails[$tail] = [
1312                      'key'        => $k,
1313                      'precedence' => $precedence[$head]
1314                  ];
1315          }
1316          $catchallChains = [];
1317          foreach ($tails as $tail => $info)
1318              $catchallChains[$info['key']] = $chains[$info['key']];
1319          foreach ($catchallChains as $k1 => $catchallChain)
1320          {
1321              $headExpr = $catchallChain[0];
1322              $tailExpr = \false;
1323              $match    = \array_slice($catchallChain, 1);
1324              if (isset($catchallChain[1])
1325               && isset($precedence[\end($catchallChain)]))
1326                  $tailExpr = \array_pop($match);
1327              $matchCnt = \count($match);
1328              foreach ($chains as $k2 => $chain)
1329              {
1330                  if ($k2 === $k1)
1331                      continue;
1332                  $start = 0;
1333                  $end = \count($chain);
1334                  if ($headExpr[1] === '+')
1335                  {
1336                      $found = \false;
1337                      foreach ($chain as $start => $atom)
1338                          if (self::matchesAtLeastOneCharacter($atom))
1339                          {
1340                              $found = \true;
1341                              break;
1342                          }
1343                      if (!$found)
1344                          continue;
1345                  }
1346                  if ($tailExpr === \false)
1347                      $end = $start;
1348                  else
1349                  {
1350                      if ($tailExpr[1] === '+')
1351                      {
1352                          $found = \false;
1353                          while (--$end > $start)
1354                              if (self::matchesAtLeastOneCharacter($chain[$end]))
1355                              {
1356                                  $found = \true;
1357                                  break;
1358                              }
1359                          if (!$found)
1360                              continue;
1361                      }
1362                      $end -= $matchCnt;
1363                  }
1364                  while ($start <= $end)
1365                  {
1366                      if (\array_slice($chain, $start, $matchCnt) === $match)
1367                      {
1368                          unset($chains[$k2]);
1369                          break;
1370                      }
1371                      ++$start;
1372                  }
1373              }
1374          }
1375      }
1376  	protected static function matchesAtLeastOneCharacter($expr)
1377      {
1378          if (\preg_match('#^[$*?^]$#', $expr))
1379              return \false;
1380          if (\preg_match('#^.$#u', $expr))
1381              return \true;
1382          if (\preg_match('#^.\\+#u', $expr))
1383              return \true;
1384          if (\preg_match('#^\\\\[^bBAZzGQEK1-9](?![*?])#', $expr))
1385              return \true;
1386          return \false;
1387      }
1388  	protected static function canUseAtomicGrouping($expr)
1389      {
1390          if (\preg_match('#(?<!\\\\)(?>\\\\\\\\)*\\.#', $expr))
1391              return \false;
1392          if (\preg_match('#(?<!\\\\)(?>\\\\\\\\)*[+*]#', $expr))
1393              return \false;
1394          if (\preg_match('#(?<!\\\\)(?>\\\\\\\\)*\\(?(?<!\\()\\?#', $expr))
1395              return \false;
1396          if (\preg_match('#(?<!\\\\)(?>\\\\\\\\)*\\\\[a-z0-9]#', $expr))
1397              return \false;
1398          return \true;
1399      }
1400  }
1401  
1402  /*
1403  * @package   s9e\TextFormatter
1404  * @copyright Copyright (c) 2010-2019 The s9e Authors
1405  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
1406  */
1407  namespace s9e\TextFormatter\Configurator\Helpers;
1408  use s9e\TextFormatter\Configurator\Collections\Ruleset;
1409  use s9e\TextFormatter\Configurator\Collections\TagCollection;
1410  abstract class RulesHelper
1411  {
1412  	public static function getBitfields(TagCollection $tags, Ruleset $rootRules)
1413      {
1414          $rules = ['*root*' => \iterator_to_array($rootRules)];
1415          foreach ($tags as $tagName => $tag)
1416              $rules[$tagName] = \iterator_to_array($tag->rules);
1417          $matrix = self::unrollRules($rules);
1418          self::pruneMatrix($matrix);
1419          $groupedTags = [];
1420          foreach (\array_keys($matrix) as $tagName)
1421          {
1422              if ($tagName === '*root*')
1423                  continue;
1424              $k = '';
1425              foreach ($matrix as $tagMatrix)
1426              {
1427                  $k .= $tagMatrix['allowedChildren'][$tagName];
1428                  $k .= $tagMatrix['allowedDescendants'][$tagName];
1429              }
1430              $groupedTags[$k][] = $tagName;
1431          }
1432          $bitTag     = [];
1433          $bitNumber  = 0;
1434          $tagsConfig = [];
1435          foreach ($groupedTags as $tagNames)
1436          {
1437              foreach ($tagNames as $tagName)
1438              {
1439                  $tagsConfig[$tagName]['bitNumber'] = $bitNumber;
1440                  $bitTag[$bitNumber] = $tagName;
1441              }
1442              ++$bitNumber;
1443          }
1444          foreach ($matrix as $tagName => $tagMatrix)
1445          {
1446              $allowedChildren    = '';
1447              $allowedDescendants = '';
1448              foreach ($bitTag as $targetName)
1449              {
1450                  $allowedChildren    .= $tagMatrix['allowedChildren'][$targetName];
1451                  $allowedDescendants .= $tagMatrix['allowedDescendants'][$targetName];
1452              }
1453              $tagsConfig[$tagName]['allowed'] = self::pack($allowedChildren, $allowedDescendants);
1454          }
1455          $return = [
1456              'root' => $tagsConfig['*root*'],
1457              'tags' => $tagsConfig
1458          ];
1459          unset($return['tags']['*root*']);
1460          return $return;
1461      }
1462  	protected static function initMatrix(array $rules)
1463      {
1464          $matrix   = [];
1465          $tagNames = \array_keys($rules);
1466          foreach ($rules as $tagName => $tagRules)
1467          {
1468              $matrix[$tagName]['allowedChildren']    = \array_fill_keys($tagNames, 0);
1469              $matrix[$tagName]['allowedDescendants'] = \array_fill_keys($tagNames, 0);
1470          }
1471          return $matrix;
1472      }
1473  	protected static function applyTargetedRule(array &$matrix, $rules, $ruleName, $key, $value)
1474      {
1475          foreach ($rules as $tagName => $tagRules)
1476          {
1477              if (!isset($tagRules[$ruleName]))
1478                  continue;
1479              foreach ($tagRules[$ruleName] as $targetName)
1480                  $matrix[$tagName][$key][$targetName] = $value;
1481          }
1482      }
1483  	protected static function unrollRules(array $rules)
1484      {
1485          $matrix = self::initMatrix($rules);
1486          $tagNames = \array_keys($rules);
1487          foreach ($rules as $tagName => $tagRules)
1488          {
1489              if (!empty($tagRules['ignoreTags']))
1490              {
1491                  $rules[$tagName]['denyChild']      = $tagNames;
1492                  $rules[$tagName]['denyDescendant'] = $tagNames;
1493              }
1494              if (!empty($tagRules['requireParent']))
1495              {
1496                  $denyParents = \array_diff($tagNames, $tagRules['requireParent']);
1497                  foreach ($denyParents as $parentName)
1498                      $rules[$parentName]['denyChild'][] = $tagName;
1499              }
1500          }
1501          self::applyTargetedRule($matrix, $rules, 'allowChild',      'allowedChildren',    1);
1502          self::applyTargetedRule($matrix, $rules, 'allowDescendant', 'allowedDescendants', 1);
1503          self::applyTargetedRule($matrix, $rules, 'denyChild',      'allowedChildren',    0);
1504          self::applyTargetedRule($matrix, $rules, 'denyDescendant', 'allowedDescendants', 0);
1505          return $matrix;
1506      }
1507  	protected static function pruneMatrix(array &$matrix)
1508      {
1509          $usableTags = ['*root*' => 1];
1510          $parentTags = $usableTags;
1511          do
1512          {
1513              $nextTags = [];
1514              foreach (\array_keys($parentTags) as $tagName)
1515                  $nextTags += \array_filter($matrix[$tagName]['allowedChildren']);
1516              $parentTags  = \array_diff_key($nextTags, $usableTags);
1517              $parentTags  = \array_intersect_key($parentTags, $matrix);
1518              $usableTags += $parentTags;
1519          }
1520          while (!empty($parentTags));
1521          $matrix = \array_intersect_key($matrix, $usableTags);
1522          unset($usableTags['*root*']);
1523          foreach ($matrix as $tagName => &$tagMatrix)
1524          {
1525              $tagMatrix['allowedChildren']
1526                  = \array_intersect_key($tagMatrix['allowedChildren'], $usableTags);
1527              $tagMatrix['allowedDescendants']
1528                  = \array_intersect_key($tagMatrix['allowedDescendants'], $usableTags);
1529          }
1530          unset($tagMatrix);
1531      }
1532  	protected static function pack($allowedChildren, $allowedDescendants)
1533      {
1534          $allowedChildren    = \str_split($allowedChildren,    8);
1535          $allowedDescendants = \str_split($allowedDescendants, 8);
1536          $allowed = [];
1537          foreach (\array_keys($allowedChildren) as $k)
1538              $allowed[] = \bindec(\sprintf(
1539                  '%1$08s%2$08s',
1540                  \strrev($allowedDescendants[$k]),
1541                  \strrev($allowedChildren[$k])
1542              ));
1543          return $allowed;
1544      }
1545  }
1546  
1547  /*
1548  * @package   s9e\TextFormatter
1549  * @copyright Copyright (c) 2010-2019 The s9e Authors
1550  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
1551  */
1552  namespace s9e\TextFormatter\Configurator\Helpers;
1553  use DOMAttr;
1554  use DOMCharacterData;
1555  use DOMDocument;
1556  use DOMElement;
1557  use DOMNode;
1558  use DOMProcessingInstruction;
1559  use DOMText;
1560  use DOMXPath;
1561  abstract class TemplateHelper
1562  {
1563      const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
1564  	public static function getAttributesByRegexp(DOMDocument $dom, $regexp)
1565      {
1566          return NodeLocator::getAttributesByRegexp($dom, $regexp);
1567      }
1568  	public static function getCSSNodes(DOMDocument $dom)
1569      {
1570          return NodeLocator::getCSSNodes($dom);
1571      }
1572  	public static function getElementsByRegexp(DOMDocument $dom, $regexp)
1573      {
1574          return NodeLocator::getElementsByRegexp($dom, $regexp);
1575      }
1576  	public static function getJSNodes(DOMDocument $dom)
1577      {
1578          return NodeLocator::getJSNodes($dom);
1579      }
1580  	public static function getObjectParamsByRegexp(DOMDocument $dom, $regexp)
1581      {
1582          return NodeLocator::getObjectParamsByRegexp($dom, $regexp);
1583      }
1584  	public static function getParametersFromXSL($xsl)
1585      {
1586          $paramNames = [];
1587          $xpath      = new DOMXPath(TemplateLoader::load($xsl));
1588          $query = '//xsl:*/@match | //xsl:*/@select | //xsl:*/@test';
1589          foreach ($xpath->query($query) as $attribute)
1590          {
1591              $expr        = $attribute->value;
1592              $paramNames += \array_flip(self::getParametersFromExpression($attribute, $expr));
1593          }
1594          $query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"]/@*[contains(., "{")]';
1595          foreach ($xpath->query($query) as $attribute)
1596              foreach (AVTHelper::parse($attribute->value) as $token)
1597                  if ($token[0] === 'expression')
1598                  {
1599                      $expr        = $token[1];
1600                      $paramNames += \array_flip(self::getParametersFromExpression($attribute, $expr));
1601                  }
1602          \ksort($paramNames);
1603          return \array_keys($paramNames);
1604      }
1605      public static function getURLNodes(DOMDocument $dom)
1606      {
1607          return NodeLocator::getURLNodes($dom);
1608      }
1609      public static function highlightNode(DOMNode $node, $prepend, $append)
1610      {
1611          $dom = $node->ownerDocument->cloneNode(\true);
1612          $dom->formatOutput = \true;
1613          $xpath = new DOMXPath($dom);
1614          $node  = $xpath->query($node->getNodePath())->item(0);
1615          $uniqid = \uniqid('_');
1616          if ($node instanceof DOMAttr)
1617              $node->value .= $uniqid;
1618          elseif ($node instanceof DOMElement)
1619              $node->setAttribute($uniqid, '');
1620          elseif ($node instanceof DOMCharacterData || $node instanceof DOMProcessingInstruction)
1621              $node->data .= $uniqid;
1622          $docXml = TemplateLoader::innerXML($dom->documentElement);
1623          $docXml = \trim(\str_replace("\n  ", "\n", $docXml));
1624          $nodeHtml = \htmlspecialchars(\trim($dom->saveXML($node)));
1625          $docHtml  = \htmlspecialchars($docXml);
1626          $html = \str_replace($nodeHtml, $prepend . $nodeHtml . $append, $docHtml);
1627          $html = \str_replace(' ' . $uniqid . '=&quot;&quot;', '', $html);
1628          $html = \str_replace($uniqid, '', $html);
1629          return $html;
1630      }
1631      public static function loadTemplate($template)
1632      {
1633          return TemplateLoader::load($template);
1634      }
1635      public static function replaceHomogeneousTemplates(array &$templates, $minCount = 3)
1636      {
1637          $expr = 'name()';
1638          $tagNames = [];
1639          foreach ($templates as $tagName => $template)
1640          {
1641              $elName = \strtolower(\preg_replace('/^[^:]+:/', '', $tagName));
1642              if ($template === '<' . $elName . '><xsl:apply-templates/></' . $elName . '>')
1643              {
1644                  $tagNames[] = $tagName;
1645                  if (\strpos($tagName, ':') !== \false)
1646                      $expr = 'local-name()';
1647              }
1648          }
1649          if (\count($tagNames) < $minCount)
1650              return;
1651          $chars = \preg_replace('/[^A-Z]+/', '', \count_chars(\implode('', $tagNames), 3));
1652          if ($chars > '')
1653              $expr = 'translate(' . $expr . ",'" . $chars . "','" . \strtolower($chars) . "')";
1654          $template = '<xsl:element name="{' . $expr . '}"><xsl:apply-templates/></xsl:element>';
1655          foreach ($tagNames as $tagName)
1656              $templates[$tagName] = $template;
1657      }
1658      public static function replaceTokens($template, $regexp, $fn)
1659      {
1660          return TemplateModifier::replaceTokens($template, $regexp, $fn);
1661      }
1662      public static function saveTemplate(DOMDocument $dom)
1663      {
1664          return TemplateLoader::save($dom);
1665      }
1666      protected static function getParametersFromExpression(DOMNode $node, $expr)
1667      {
1668          $varNames   = XPathHelper::getVariables($expr);
1669          $paramNames = [];
1670          $xpath      = new DOMXPath($node->ownerDocument);
1671          foreach ($varNames as $name)
1672          {
1673              $query = 'ancestor-or-self::*/preceding-sibling::xsl:variable[@name="' . $name . '"]';
1674              if (!$xpath->query($query, $node)->length)
1675                  $paramNames[] = $name;
1676          }
1677          return $paramNames;
1678      }
1679  }
1680  
1681  /*
1682  * @package   s9e\TextFormatter
1683  * @copyright Copyright (c) 2010-2019 The s9e Authors
1684  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
1685  */
1686  namespace s9e\TextFormatter\Configurator\Helpers;
1687  use DOMElement;
1688  use DOMXPath;
1689  class TemplateInspector
1690  {
1691      const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
1692      protected $allowChildBitfields = [];
1693      protected $allowsChildElements;
1694      protected $allowsText;
1695      protected $branches;
1696      protected $contentBitfield = "\0";
1697      protected $defaultBranchBitfield;
1698      protected $denyDescendantBitfield = "\0";
1699      protected $dom;
1700      protected $hasElements = \false;
1701      protected $hasRootText;
1702      protected $isBlock = \false;
1703      protected $isEmpty;
1704      protected $isFormattingElement;
1705      protected $isPassthrough = \false;
1706      protected $isTransparent = \false;
1707      protected $isVoid;
1708      protected $leafNodes = [];
1709      protected $preservesNewLines = \false;
1710      protected $rootBitfields = [];
1711      protected $rootNodes = [];
1712      protected $xpath;
1713  	public function __construct($template)
1714      {
1715          $this->dom   = TemplateHelper::loadTemplate($template);
1716          $this->xpath = new DOMXPath($this->dom);
1717          $this->defaultBranchBitfield = ElementInspector::getAllowChildBitfield($this->dom->createElement('div'));
1718          $this->analyseRootNodes();
1719          $this->analyseBranches();
1720          $this->analyseContent();
1721      }
1722  	public function allowsChild(TemplateInspector $child)
1723      {
1724          if (!$this->allowsDescendant($child))
1725              return \false;
1726          foreach ($child->rootBitfields as $rootBitfield)
1727              foreach ($this->allowChildBitfields as $allowChildBitfield)
1728                  if (!self::match($rootBitfield, $allowChildBitfield))
1729                      return \false;
1730          return ($this->allowsText || !$child->hasRootText);
1731      }
1732  	public function allowsDescendant(TemplateInspector $descendant)
1733      {
1734          if (self::match($descendant->contentBitfield, $this->denyDescendantBitfield))
1735              return \false;
1736          return ($this->allowsChildElements || !$descendant->hasElements);
1737      }
1738  	public function allowsChildElements()
1739      {
1740          return $this->allowsChildElements;
1741      }
1742  	public function allowsText()
1743      {
1744          return $this->allowsText;
1745      }
1746  	public function closesParent(TemplateInspector $parent)
1747      {
1748          foreach ($this->rootNodes as $rootNode)
1749              foreach ($parent->leafNodes as $leafNode)
1750                  if (ElementInspector::closesParent($rootNode, $leafNode))
1751                      return \true;
1752          return \false;
1753      }
1754  	public function evaluate($expr, DOMElement $node = \null)
1755      {
1756          return $this->xpath->evaluate($expr, $node);
1757      }
1758  	public function isBlock()
1759      {
1760          return $this->isBlock;
1761      }
1762  	public function isFormattingElement()
1763      {
1764          return $this->isFormattingElement;
1765      }
1766  	public function isEmpty()
1767      {
1768          return $this->isEmpty;
1769      }
1770  	public function isPassthrough()
1771      {
1772          return $this->isPassthrough;
1773      }
1774  	public function isTransparent()
1775      {
1776          return $this->isTransparent;
1777      }
1778  	public function isVoid()
1779      {
1780          return $this->isVoid;
1781      }
1782  	public function preservesNewLines()
1783      {
1784          return $this->preservesNewLines;
1785      }
1786  	protected function analyseContent()
1787      {
1788          $query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"]';
1789          foreach ($this->xpath->query($query) as $node)
1790          {
1791              $this->contentBitfield |= ElementInspector::getCategoryBitfield($node);
1792              $this->hasElements = \true;
1793          }
1794          $this->isPassthrough = (bool) $this->evaluate('count(//xsl:apply-templates)');
1795      }
1796      protected function analyseRootNodes()
1797      {
1798          $query = '//*[namespace-uri() != "' . self::XMLNS_XSL . '"][not(ancestor::*[namespace-uri() != "' . self::XMLNS_XSL . '"])]';
1799          foreach ($this->xpath->query($query) as $node)
1800          {
1801              $this->rootNodes[] = $node;
1802              if ($this->elementIsBlock($node))
1803                  $this->isBlock = \true;
1804              $this->rootBitfields[] = ElementInspector::getCategoryBitfield($node);
1805          }
1806          $predicate = '[not(ancestor::*[namespace-uri() != "' . self::XMLNS_XSL . '"])]';
1807          $predicate .= '[not(ancestor::xsl:attribute | ancestor::xsl:comment | ancestor::xsl:variable)]';
1808          $query = '//text()[normalize-space() != ""]' . $predicate
1809                 . '|//xsl:text[normalize-space() != ""]' . $predicate
1810                 . '|//xsl:value-of' . $predicate;
1811          $this->hasRootText = (bool) $this->evaluate('count(' . $query . ')');
1812      }
1813      protected function analyseBranches()
1814      {
1815          $this->branches = [];
1816          foreach ($this->xpath->query('//xsl:apply-templates') as $applyTemplates)
1817          {
1818              $query            = 'ancestor::*[namespace-uri() != "' . self::XMLNS_XSL . '"]';
1819              $this->branches[] = \iterator_to_array($this->xpath->query($query, $applyTemplates));
1820          }
1821          $this->computeAllowsChildElements();
1822          $this->computeAllowsText();
1823          $this->computeBitfields();
1824          $this->computeFormattingElement();
1825          $this->computeIsEmpty();
1826          $this->computeIsTransparent();
1827          $this->computeIsVoid();
1828          $this->computePreservesNewLines();
1829          $this->storeLeafNodes();
1830      }
1831      protected function anyBranchHasProperty($methodName)
1832      {
1833          foreach ($this->branches as $branch)
1834              foreach ($branch as $element)
1835                  if (ElementInspector::$methodName($element))
1836                      return \true;
1837          return \false;
1838      }
1839      protected function computeBitfields()
1840      {
1841          if (empty($this->branches))
1842          {
1843              $this->allowChildBitfields = ["\0"];
1844              return;
1845          }
1846          foreach ($this->branches as $branch)
1847          {
1848              $branchBitfield = $this->defaultBranchBitfield;
1849              foreach ($branch as $element)
1850              {
1851                  if (!ElementInspector::isTransparent($element))
1852                      $branchBitfield = "\0";
1853                  $branchBitfield |= ElementInspector::getAllowChildBitfield($element);
1854                  $this->denyDescendantBitfield |= ElementInspector::getDenyDescendantBitfield($element);
1855              }
1856              $this->allowChildBitfields[] = $branchBitfield;
1857          }
1858      }
1859      protected function computeAllowsChildElements()
1860      {
1861          $this->allowsChildElements = ($this->anyBranchHasProperty('isTextOnly')) ? \false : !empty($this->branches);
1862      }
1863      protected function computeAllowsText()
1864      {
1865          foreach (\array_filter($this->branches) as $branch)
1866              if (ElementInspector::disallowsText(\end($branch)))
1867              {
1868                  $this->allowsText = \false;
1869                  return;
1870              }
1871          $this->allowsText = \true;
1872      }
1873      protected function computeFormattingElement()
1874      {
1875          foreach ($this->branches as $branch)
1876              foreach ($branch as $element)
1877                  if (!ElementInspector::isFormattingElement($element) && !$this->isFormattingSpan($element))
1878                  {
1879                      $this->isFormattingElement = \false;
1880                      return;
1881                  }
1882          $this->isFormattingElement = (bool) \count(\array_filter($this->branches));
1883      }
1884      protected function computeIsEmpty()
1885      {
1886          $this->isEmpty = ($this->anyBranchHasProperty('isEmpty')) || empty($this->branches);
1887      }
1888      protected function computeIsTransparent()
1889      {
1890          foreach ($this->branches as $branch)
1891              foreach ($branch as $element)
1892                  if (!ElementInspector::isTransparent($element))
1893                  {
1894                      $this->isTransparent = \false;
1895                      return;
1896                  }
1897          $this->isTransparent = !empty($this->branches);
1898      }
1899      protected function computeIsVoid()
1900      {
1901          $this->isVoid = ($this->anyBranchHasProperty('isVoid')) || empty($this->branches);
1902      }
1903      protected function computePreservesNewLines()
1904      {
1905          foreach ($this->branches as $branch)
1906          {
1907              $style = '';
1908              foreach ($branch as $element)
1909                  $style .= $this->getStyle($element, \true);
1910              if (\preg_match('(.*white-space\\s*:\\s*(no|pre))is', $style, $m) && \strtolower($m[1]) === 'pre')
1911              {
1912                  $this->preservesNewLines = \true;
1913                  return;
1914              }
1915          }
1916          $this->preservesNewLines = \false;
1917      }
1918      protected function elementIsBlock(DOMElement $element)
1919      {
1920          $style = $this->getStyle($element);
1921          if (\preg_match('(\\bdisplay\\s*:\\s*block)i', $style))
1922              return \true;
1923          if (\preg_match('(\\bdisplay\\s*:\\s*(?:inli|no)ne)i', $style))
1924              return \false;
1925          return ElementInspector::isBlock($element);
1926      }
1927      protected function getStyle(DOMElement $node, $deep = \false)
1928      {
1929          $style = '';
1930          if (ElementInspector::preservesWhitespace($node))
1931              $style .= 'white-space:pre;';
1932          $style .= $node->getAttribute('style');
1933          $query = (($deep) ? './/' : './') . 'xsl:attribute[@name="style"]';
1934          foreach ($this->xpath->query($query, $node) as $attribute)
1935              $style .= ';' . $attribute->textContent;
1936          return $style;
1937      }
1938      protected function isFormattingSpan(DOMElement $node)
1939      {
1940          if ($node->nodeName !== 'span')
1941              return \false;
1942          if ($node->getAttribute('class') === '' && $node->getAttribute('style') === '')
1943              return \false;
1944          foreach ($node->attributes as $attrName => $attribute)
1945              if ($attrName !== 'class' && $attrName !== 'style')
1946                  return \false;
1947          return \true;
1948      }
1949      protected function storeLeafNodes()
1950      {
1951          foreach (\array_filter($this->branches) as $branch)
1952              $this->leafNodes[] = \end($branch);
1953      }
1954      protected static function match($bitfield1, $bitfield2)
1955      {
1956          return (\trim($bitfield1 & $bitfield2, "\0") !== '');
1957      }
1958  }
1959  
1960  /*
1961  * @package   s9e\TextFormatter
1962  * @copyright Copyright (c) 2010-2019 The s9e Authors
1963  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
1964  */
1965  namespace s9e\TextFormatter\Configurator\Helpers;
1966  use DOMDocument;
1967  use DOMElement;
1968  use DOMXPath;
1969  use RuntimeException;
1970  abstract class TemplateLoader
1971  {
1972      const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
1973  	public static function innerXML(DOMElement $element)
1974      {
1975          $xml = $element->ownerDocument->saveXML($element);
1976          $pos = 1 + \strpos($xml, '>');
1977          $len = \strrpos($xml, '<') - $pos;
1978          return ($len < 1) ? '' : \substr($xml, $pos, $len);
1979      }
1980  	public static function load($template)
1981      {
1982          $dom = self::loadAsXML($template) ?: self::loadAsXML(self::fixEntities($template));
1983          if ($dom)
1984              return $dom;
1985          if (\strpos($template, '<xsl:') !== \false)
1986          {
1987              $error = \libxml_get_last_error();
1988              throw new RuntimeException('Invalid XSL: ' . $error->message);
1989          }
1990          return self::loadAsHTML($template);
1991      }
1992  	public static function save(DOMDocument $dom)
1993      {
1994          $xml = self::innerXML($dom->documentElement);
1995          if (\strpos($xml, 'xmlns:xsl') !== \false)
1996              $xml = \preg_replace('((<[^>]+?) xmlns:xsl="' . self::XMLNS_XSL . '")', '$1', $xml);
1997          return $xml;
1998      }
1999  	protected static function fixEntities($template)
2000      {
2001          return \preg_replace_callback(
2002              '(&(?!quot;|amp;|apos;|lt;|gt;)\\w+;)',
2003              function ($m)
2004              {
2005                  return \html_entity_decode($m[0], \ENT_NOQUOTES, 'UTF-8');
2006              },
2007              \preg_replace('(&(?![A-Za-z0-9]+;|#\\d+;|#x[A-Fa-f0-9]+;))', '&amp;', $template)
2008          );
2009      }
2010  	protected static function loadAsHTML($template)
2011      {
2012          $dom  = new DOMDocument;
2013          $html = '<?xml version="1.0" encoding="utf-8" ?><html><body><div>' . $template . '</div></body></html>';
2014          $useErrors = \libxml_use_internal_errors(\true);
2015          $dom->loadHTML($html, \LIBXML_NSCLEAN);
2016          self::removeInvalidAttributes($dom);
2017          \libxml_use_internal_errors($useErrors);
2018          $xml = '<?xml version="1.0" encoding="utf-8" ?><xsl:template xmlns:xsl="' . self::XMLNS_XSL . '">' . self::innerXML($dom->documentElement->firstChild->firstChild) . '</xsl:template>';
2019          $useErrors = \libxml_use_internal_errors(\true);
2020          $dom->loadXML($xml, \LIBXML_NSCLEAN);
2021          \libxml_use_internal_errors($useErrors);
2022          return $dom;
2023      }
2024  	protected static function loadAsXML($template)
2025      {
2026          $xml = '<?xml version="1.0" encoding="utf-8" ?><xsl:template xmlns:xsl="' . self::XMLNS_XSL . '">' . $template . '</xsl:template>';
2027          $useErrors = \libxml_use_internal_errors(\true);
2028          $dom       = new DOMDocument;
2029          $success   = $dom->loadXML($xml, \LIBXML_NSCLEAN);
2030          self::removeInvalidAttributes($dom);
2031          \libxml_use_internal_errors($useErrors);
2032          return ($success) ? $dom : \false;
2033      }
2034  	protected static function removeInvalidAttributes(DOMDocument $dom)
2035      {
2036          $xpath = new DOMXPath($dom);
2037          foreach ($xpath->query('//@*') as $attribute)
2038              if (!\preg_match('(^(?:[-\\w]+:)?(?!\\d)[-\\w]+$)D', $attribute->nodeName))
2039                  $attribute->parentNode->removeAttributeNode($attribute);
2040      }
2041  }
2042  
2043  /*
2044  * @package   s9e\TextFormatter
2045  * @copyright Copyright (c) 2010-2019 The s9e Authors
2046  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2047  */
2048  namespace s9e\TextFormatter\Configurator\Helpers;
2049  use s9e\TextFormatter\Configurator\Helpers\TemplateParser\Normalizer;
2050  use s9e\TextFormatter\Configurator\Helpers\TemplateParser\Optimizer;
2051  use s9e\TextFormatter\Configurator\Helpers\TemplateParser\Parser;
2052  class TemplateParser
2053  {
2054      const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
2055      public static $voidRegexp = '/^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/Di';
2056  	public static function parse($template)
2057      {
2058          $parser = new Parser(new Normalizer(new Optimizer));
2059          return $parser->parse($template);
2060      }
2061  	public static function parseEqualityExpr($expr)
2062      {
2063          return XPathHelper::parseEqualityExpr($expr);
2064      }
2065  }
2066  
2067  /*
2068  * @package   s9e\TextFormatter
2069  * @copyright Copyright (c) 2010-2019 The s9e Authors
2070  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2071  */
2072  namespace s9e\TextFormatter\Configurator\Helpers\TemplateParser;
2073  use DOMDocument;
2074  use DOMElement;
2075  use DOMNode;
2076  use DOMXPath;
2077  abstract class IRProcessor
2078  {
2079      const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
2080      protected $xpath;
2081  	protected function appendElement(DOMElement $parentNode, $name, $value = '')
2082      {
2083          return $parentNode->appendChild($parentNode->ownerDocument->createElement($name, $value));
2084      }
2085  	protected function createXPath(DOMDocument $dom)
2086      {
2087          $this->xpath = new DOMXPath($dom);
2088      }
2089  	protected function evaluate($expr, DOMNode $node = \null)
2090      {
2091          return (isset($node)) ? $this->xpath->evaluate($expr, $node) : $this->xpath->evaluate($expr);
2092      }
2093  	protected function query($query, DOMNode $node = \null)
2094      {
2095          return (isset($node)) ? $this->xpath->query($query, $node) : $this->xpath->query($query);
2096      }
2097  }
2098  
2099  /*
2100  * @package   s9e\TextFormatter
2101  * @copyright Copyright (c) 2010-2019 The s9e Authors
2102  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2103  */
2104  namespace s9e\TextFormatter\Configurator\Helpers;
2105  use RuntimeException;
2106  use s9e\TextFormatter\Utils\XPath;
2107  abstract class XPathHelper
2108  {
2109  	public static function getVariables($expr)
2110      {
2111          $expr = \preg_replace('/(["\']).*?\\1/s', '$1$1', $expr);
2112          \preg_match_all('/\\$(\\w+)/', $expr, $matches);
2113          $varNames = \array_unique($matches[1]);
2114          \sort($varNames);
2115          return $varNames;
2116      }
2117  	public static function isExpressionNumeric($expr)
2118      {
2119          $expr = \strrev(\preg_replace('(\\((?!\\s*(?!vid(?!\\w))\\w))', ' ', \strrev($expr)));
2120          $expr = \str_replace(')', ' ', $expr);
2121          if (\preg_match('(^\\s*([$@][-\\w]++|-?\\.\\d++|-?\\d++(?:\\.\\d++)?)(?>\\s*(?>[-+*]|div)\\s*(?1))++\\s*$)', $expr))
2122              return \true;
2123          return \false;
2124      }
2125  	public static function minify($expr)
2126      {
2127          $old     = $expr;
2128          $strings = [];
2129          $expr = \preg_replace_callback(
2130              '/"[^"]*"|\'[^\']*\'/',
2131              function ($m) use (&$strings)
2132              {
2133                  $uniqid = '(' . \sha1(\uniqid()) . ')';
2134                  $strings[$uniqid] = $m[0];
2135                  return $uniqid;
2136              },
2137              \trim($expr)
2138          );
2139          if (\preg_match('/[\'"]/', $expr))
2140              throw new RuntimeException("Cannot parse XPath expression '" . $old . "'");
2141          $expr = \preg_replace('/\\s+/', ' ', $expr);
2142          $expr = \preg_replace('/([-a-z_0-9]) ([^-a-z_0-9])/i', '$1$2', $expr);
2143          $expr = \preg_replace('/([^-a-z_0-9]) ([-a-z_0-9])/i', '$1$2', $expr);
2144          $expr = \preg_replace('/(?!- -)([^-a-z_0-9]) ([^-a-z_0-9])/i', '$1$2', $expr);
2145          $expr = \preg_replace('/ - ([a-z_0-9])/i', ' -$1', $expr);
2146          $expr = \preg_replace('/((?:^|[ \\(])\\d+) div ?/', '$1div', $expr);
2147          $expr = \preg_replace('/([^-a-z_0-9]div) (?=[$0-9@])/', '$1', $expr);
2148          $expr = \strtr($expr, $strings);
2149          return $expr;
2150      }
2151  	public static function parseEqualityExpr($expr)
2152      {
2153          $eq = '(?<equality>(?<key>@[-\\w]+|\\$\\w+|\\.)(?<operator>\\s*=\\s*)(?:(?<literal>(?<string>"[^"]*"|\'[^\']*\')|0|[1-9][0-9]*)|(?<concat>concat\\(\\s*(?&string)\\s*(?:,\\s*(?&string)\\s*)+\\)))|(?:(?<literal>(?&literal))|(?<concat>(?&concat)))(?&operator)(?<key>(?&key)))';
2154          $regexp = '(^(?J)\\s*' . $eq . '\\s*(?:or\\s*(?&equality)\\s*)*$)';
2155          if (!\preg_match($regexp, $expr))
2156              return \false;
2157          \preg_match_all("((?J)$eq)", $expr, $matches, \PREG_SET_ORDER);
2158          $map = [];
2159          foreach ($matches as $m)
2160          {
2161              $key   = $m['key'];
2162              $value = (!empty($m['concat']))
2163                     ? self::evaluateConcat($m['concat'])
2164                     : self::evaluateLiteral($m['literal']);
2165              $map[$key][] = $value;
2166          }
2167          return $map;
2168      }
2169  	protected static function evaluateConcat($expr)
2170      {
2171          \preg_match_all('(\'[^\']*\'|"[^"]*")', $expr, $strings);
2172          $value = '';
2173          foreach ($strings[0] as $string)
2174              $value .= \substr($string, 1, -1);
2175          return $value;
2176      }
2177  	protected static function evaluateLiteral($expr)
2178      {
2179          if ($expr[0] === '"' || $expr[0] === "'")
2180              $expr = \substr($expr, 1, -1);
2181          return $expr;
2182      }
2183  }
2184  
2185  /*
2186  * @package   s9e\TextFormatter
2187  * @copyright Copyright (c) 2010-2019 The s9e Authors
2188  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2189  */
2190  namespace s9e\TextFormatter\Configurator\Items;
2191  use DOMDocument;
2192  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
2193  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
2194  use s9e\TextFormatter\Configurator\TemplateNormalizer;
2195  class Template
2196  {
2197      protected $inspector;
2198      protected $isNormalized = \false;
2199      protected $template;
2200  	public function __construct($template)
2201      {
2202          $this->template = $template;
2203      }
2204  	public function __call($methodName, $args)
2205      {
2206          return \call_user_func_array([$this->getInspector(), $methodName], $args);
2207      }
2208  	public function __toString()
2209      {
2210          return $this->template;
2211      }
2212  	public function asDOM()
2213      {
2214          $xml = '<xsl:template xmlns:xsl="http://www.w3.org/1999/XSL/Transform">'
2215               . $this->__toString()
2216               . '</xsl:template>';
2217          $dom = new TemplateDocument($this);
2218          $dom->loadXML($xml);
2219          return $dom;
2220      }
2221  	public function getCSSNodes()
2222      {
2223          return TemplateHelper::getCSSNodes($this->asDOM());
2224      }
2225  	public function getInspector()
2226      {
2227          if (!isset($this->inspector))
2228              $this->inspector = new TemplateInspector($this->__toString());
2229          return $this->inspector;
2230      }
2231  	public function getJSNodes()
2232      {
2233          return TemplateHelper::getJSNodes($this->asDOM());
2234      }
2235  	public function getURLNodes()
2236      {
2237          return TemplateHelper::getURLNodes($this->asDOM());
2238      }
2239  	public function getParameters()
2240      {
2241          return TemplateHelper::getParametersFromXSL($this->__toString());
2242      }
2243  	public function isNormalized($bool = \null)
2244      {
2245          if (isset($bool))
2246              $this->isNormalized = $bool;
2247          return $this->isNormalized;
2248      }
2249  	public function normalize(TemplateNormalizer $templateNormalizer)
2250      {
2251          $this->inspector    = \null;
2252          $this->template     = $templateNormalizer->normalizeTemplate($this->template);
2253          $this->isNormalized = \true;
2254      }
2255  	public function replaceTokens($regexp, $fn)
2256      {
2257          $this->inspector    = \null;
2258          $this->template     = TemplateHelper::replaceTokens($this->template, $regexp, $fn);
2259          $this->isNormalized = \false;
2260      }
2261  	public function setContent($template)
2262      {
2263          $this->inspector    = \null;
2264          $this->template     = (string) $template;
2265          $this->isNormalized = \false;
2266      }
2267  }
2268  
2269  /*
2270  * @package   s9e\TextFormatter
2271  * @copyright Copyright (c) 2010-2019 The s9e Authors
2272  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2273  */
2274  namespace s9e\TextFormatter\Configurator\JavaScript;
2275  use InvalidArgumentException;
2276  class FunctionProvider
2277  {
2278      public static $cache = [
2279          'addslashes'=>'function(str)
2280  {
2281      return str.replace(/["\'\\\\]/g, \'\\\\$&\').replace(/\\u0000/g, \'\\\\0\');
2282  }',
2283          'dechex'=>'function(str)
2284  {
2285      return parseInt(str).toString(16);
2286  }',
2287          'intval'=>'function(str)
2288  {
2289      return parseInt(str) || 0;
2290  }',
2291          'ltrim'=>'function(str)
2292  {
2293      return str.replace(/^[ \\n\\r\\t\\0\\x0B]+/g, \'\');
2294  }',
2295          'mb_strtolower'=>'function(str)
2296  {
2297      return str.toLowerCase();
2298  }',
2299          'mb_strtoupper'=>'function(str)
2300  {
2301      return str.toUpperCase();
2302  }',
2303          'mt_rand'=>'function(min, max)
2304  {
2305      return (min + Math.floor(Math.random() * (max + 1 - min)));
2306  }',
2307          'rawurlencode'=>'function(str)
2308  {
2309      return encodeURIComponent(str).replace(
2310          /[!\'()*]/g,
2311          /**
2312          * @param {!string} c
2313          */
2314          function(c)
2315          {
2316              return \'%\' + c.charCodeAt(0).toString(16).toUpperCase();
2317          }
2318      );
2319  }',
2320          'rtrim'=>'function(str)
2321  {
2322      return str.replace(/[ \\n\\r\\t\\0\\x0B]+$/g, \'\');
2323  }',
2324          'str_rot13'=>'function(str)
2325  {
2326      return str.replace(
2327          /[a-z]/gi,
2328          function(c)
2329          {
2330              return String.fromCharCode(c.charCodeAt(0) + ((c.toLowerCase() < \'n\') ? 13 : -13));
2331          }
2332      );
2333  }',
2334          'stripslashes'=>'function(str)
2335  {
2336      // NOTE: this will not correctly transform \\0 into a NULL byte. I consider this a feature
2337      //       rather than a bug. There\'s no reason to use NULL bytes in a text.
2338      return str.replace(/\\\\([\\s\\S]?)/g, \'\\\\1\');
2339  }',
2340          'strrev'=>'function(str)
2341  {
2342      return str.split(\'\').reverse().join(\'\');
2343  }',
2344          'strtolower'=>'function(str)
2345  {
2346      return str.toLowerCase();
2347  }',
2348          'strtotime'=>'function(str)
2349  {
2350      return Date.parse(str) / 1000;
2351  }',
2352          'strtoupper'=>'function(str)
2353  {
2354      return str.toUpperCase();
2355  }',
2356          'trim'=>'function(str)
2357  {
2358      return str.replace(/^[ \\n\\r\\t\\0\\x0B]+/g, \'\').replace(/[ \\n\\r\\t\\0\\x0B]+$/g, \'\');
2359  }',
2360          'ucfirst'=>'function(str)
2361  {
2362      return str[0].toUpperCase() + str.substr(1);
2363  }',
2364          'ucwords'=>'function(str)
2365  {
2366      return str.replace(
2367          /(?:^|\\s)[a-z]/g,
2368          function(m)
2369          {
2370              return m.toUpperCase()
2371          }
2372      );
2373  }',
2374          'urldecode'=>'function(str)
2375  {
2376      return decodeURIComponent(str);
2377  }',
2378          'urlencode'=>'function(str)
2379  {
2380      return encodeURIComponent(str);
2381  }'
2382      ];
2383  	public static function get($funcName)
2384      {
2385          if (isset(self::$cache[$funcName]))
2386              return self::$cache[$funcName];
2387          if (\preg_match('(^[a-z_0-9]+$)D', $funcName))
2388          {
2389              $filepath = __DIR__ . '/Configurator/JavaScript/functions/' . $funcName . '.js';
2390              if (\file_exists($filepath))
2391                  return \file_get_contents($filepath);
2392          }
2393          throw new InvalidArgumentException("Unknown function '" . $funcName . "'");
2394      }
2395  }
2396  
2397  /*
2398  * @package   s9e\TextFormatter
2399  * @copyright Copyright (c) 2010-2019 The s9e Authors
2400  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2401  */
2402  namespace s9e\TextFormatter\Configurator;
2403  interface RendererGenerator
2404  {
2405  	public function getRenderer(Rendering $rendering);
2406  }
2407  
2408  /*
2409  * @package   s9e\TextFormatter
2410  * @copyright Copyright (c) 2010-2019 The s9e Authors
2411  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2412  */
2413  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
2414  abstract class AbstractOptimizer
2415  {
2416      protected $cnt;
2417      protected $i;
2418      protected $changed;
2419      protected $tokens;
2420  	public function optimize($php)
2421      {
2422          $this->reset($php);
2423          $this->optimizeTokens();
2424          if ($this->changed)
2425              $php = $this->serialize();
2426          unset($this->tokens);
2427          return $php;
2428      }
2429      abstract protected function optimizeTokens();
2430  	protected function reset($php)
2431      {
2432          $this->tokens  = \token_get_all('<?php ' . $php);
2433          $this->i       = 0;
2434          $this->cnt     = \count($this->tokens);
2435          $this->changed = \false;
2436      }
2437  	protected function serialize()
2438      {
2439          unset($this->tokens[0]);
2440          $php = '';
2441          foreach ($this->tokens as $token)
2442              $php .= (\is_string($token)) ? $token : $token[1];
2443          return $php;
2444      }
2445  	protected function skipToString($str)
2446      {
2447          while (++$this->i < $this->cnt && $this->tokens[$this->i] !== $str);
2448      }
2449  	protected function skipWhitespace()
2450      {
2451          while (++$this->i < $this->cnt && $this->tokens[$this->i][0] === \T_WHITESPACE);
2452      }
2453  	protected function unindentBlock($start, $end)
2454      {
2455          $this->i = $start;
2456          do
2457          {
2458              if ($this->tokens[$this->i][0] === \T_WHITESPACE || $this->tokens[$this->i][0] === \T_DOC_COMMENT)
2459                  $this->tokens[$this->i][1] = \preg_replace("/^\t/m", '', $this->tokens[$this->i][1]);
2460          }
2461          while (++$this->i <= $end);
2462      }
2463  }
2464  
2465  /*
2466  * @package   s9e\TextFormatter
2467  * @copyright Copyright (c) 2010-2019 The s9e Authors
2468  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2469  */
2470  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
2471  class BranchOutputOptimizer
2472  {
2473      protected $cnt;
2474      protected $i;
2475      protected $tokens;
2476  	public function optimize(array $tokens)
2477      {
2478          $this->tokens = $tokens;
2479          $this->i      = 0;
2480          $this->cnt    = \count($this->tokens);
2481          $php = '';
2482          while (++$this->i < $this->cnt)
2483              if ($this->tokens[$this->i][0] === \T_IF)
2484                  $php .= $this->serializeIfBlock($this->parseIfBlock());
2485              else
2486                  $php .= $this->serializeToken($this->tokens[$this->i]);
2487          unset($this->tokens);
2488          return $php;
2489      }
2490  	protected function captureOutput()
2491      {
2492          $expressions = [];
2493          while ($this->skipOutputAssignment())
2494          {
2495              do
2496              {
2497                  $expressions[] = $this->captureOutputExpression();
2498              }
2499              while ($this->tokens[$this->i++] === '.');
2500          }
2501          return $expressions;
2502      }
2503  	protected function captureOutputExpression()
2504      {
2505          $parens = 0;
2506          $php = '';
2507          do
2508          {
2509              if ($this->tokens[$this->i] === ';')
2510                  break;
2511              elseif ($this->tokens[$this->i] === '.' && !$parens)
2512                  break;
2513              elseif ($this->tokens[$this->i] === '(')
2514                  ++$parens;
2515              elseif ($this->tokens[$this->i] === ')')
2516                  --$parens;
2517              $php .= $this->serializeToken($this->tokens[$this->i]);
2518          }
2519          while (++$this->i < $this->cnt);
2520          return $php;
2521      }
2522  	protected function captureStructure()
2523      {
2524          $php = '';
2525          do
2526          {
2527              $php .= $this->serializeToken($this->tokens[$this->i]);
2528          }
2529          while ($this->tokens[++$this->i] !== '{');
2530          ++$this->i;
2531          return $php;
2532      }
2533  	protected function isBranchToken()
2534      {
2535          return \in_array($this->tokens[$this->i][0], [\T_ELSE, \T_ELSEIF, \T_IF], \true);
2536      }
2537  	protected function mergeIfBranches(array $branches)
2538      {
2539          $lastBranch = \end($branches);
2540          if ($lastBranch['structure'] === 'else')
2541          {
2542              $before = $this->optimizeBranchesHead($branches);
2543              $after  = $this->optimizeBranchesTail($branches);
2544          }
2545          else
2546              $before = $after = [];
2547          $source = '';
2548          foreach ($branches as $branch)
2549              $source .= $this->serializeBranch($branch);
2550          return [
2551              'before' => $before,
2552              'source' => $source,
2553              'after'  => $after
2554          ];
2555      }
2556  	protected function mergeOutput(array $left, array $right)
2557      {
2558          if (empty($left))
2559              return $right;
2560          if (empty($right))
2561              return $left;
2562          $k = \count($left) - 1;
2563          if (\substr($left[$k], -1) === "'" && $right[0][0] === "'")
2564          {
2565              $right[0] = \substr($left[$k], 0, -1) . \substr($right[0], 1);
2566              unset($left[$k]);
2567          }
2568          return \array_merge($left, $right);
2569      }
2570  	protected function optimizeBranchesHead(array &$branches)
2571      {
2572          $before = $this->optimizeBranchesOutput($branches, 'head');
2573          foreach ($branches as &$branch)
2574          {
2575              if ($branch['body'] !== '' || !empty($branch['tail']))
2576                  continue;
2577              $branch['tail'] = \array_reverse($branch['head']);
2578              $branch['head'] = [];
2579          }
2580          unset($branch);
2581          return $before;
2582      }
2583  	protected function optimizeBranchesOutput(array &$branches, $which)
2584      {
2585          $expressions = [];
2586          while (isset($branches[0][$which][0]))
2587          {
2588              $expr = $branches[0][$which][0];
2589              foreach ($branches as $branch)
2590                  if (!isset($branch[$which][0]) || $branch[$which][0] !== $expr)
2591                      break 2;
2592              $expressions[] = $expr;
2593              foreach ($branches as &$branch)
2594                  \array_shift($branch[$which]);
2595              unset($branch);
2596          }
2597          return $expressions;
2598      }
2599  	protected function optimizeBranchesTail(array &$branches)
2600      {
2601          return $this->optimizeBranchesOutput($branches, 'tail');
2602      }
2603  	protected function parseBranch()
2604      {
2605          $structure = $this->captureStructure();
2606          $head = $this->captureOutput();
2607          $body = '';
2608          $tail = [];
2609          $braces = 0;
2610          do
2611          {
2612              $tail = $this->mergeOutput($tail, \array_reverse($this->captureOutput()));
2613              if ($this->tokens[$this->i] === '}' && !$braces)
2614                  break;
2615              $body .= $this->serializeOutput(\array_reverse($tail));
2616              $tail  = [];
2617              if ($this->tokens[$this->i][0] === \T_IF)
2618              {
2619                  $child = $this->parseIfBlock();
2620                  if ($body === '')
2621                      $head = $this->mergeOutput($head, $child['before']);
2622                  else
2623                      $body .= $this->serializeOutput($child['before']);
2624                  $body .= $child['source'];
2625                  $tail  = $child['after'];
2626              }
2627              else
2628              {
2629                  $body .= $this->serializeToken($this->tokens[$this->i]);
2630                  if ($this->tokens[$this->i] === '{')
2631                      ++$braces;
2632                  elseif ($this->tokens[$this->i] === '}')
2633                      --$braces;
2634              }
2635          }
2636          while (++$this->i < $this->cnt);
2637          return [
2638              'structure' => $structure,
2639              'head'      => $head,
2640              'body'      => $body,
2641              'tail'      => $tail
2642          ];
2643      }
2644  	protected function parseIfBlock()
2645      {
2646          $branches = [];
2647          do
2648          {
2649              $branches[] = $this->parseBranch();
2650          }
2651          while (++$this->i < $this->cnt && $this->isBranchToken());
2652          --$this->i;
2653          return $this->mergeIfBranches($branches);
2654      }
2655  	protected function serializeBranch(array $branch)
2656      {
2657          if ($branch['structure'] === 'else'
2658           && $branch['body']      === ''
2659           && empty($branch['head'])
2660           && empty($branch['tail']))
2661              return '';
2662          return $branch['structure'] . '{' . $this->serializeOutput($branch['head']) . $branch['body'] . $this->serializeOutput(\array_reverse($branch['tail'])) . '}';
2663      }
2664  	protected function serializeIfBlock(array $block)
2665      {
2666          return $this->serializeOutput($block['before']) . $block['source'] . $this->serializeOutput(\array_reverse($block['after']));
2667      }
2668  	protected function serializeOutput(array $expressions)
2669      {
2670          if (empty($expressions))
2671              return '';
2672          return '$this->out.=' . \implode('.', $expressions) . ';';
2673      }
2674  	protected function serializeToken($token)
2675      {
2676          return (\is_array($token)) ? $token[1] : $token;
2677      }
2678  	protected function skipOutputAssignment()
2679      {
2680          if ($this->tokens[$this->i    ][0] !== \T_VARIABLE
2681           || $this->tokens[$this->i    ][1] !== '$this'
2682           || $this->tokens[$this->i + 1][0] !== \T_OBJECT_OPERATOR
2683           || $this->tokens[$this->i + 2][0] !== \T_STRING
2684           || $this->tokens[$this->i + 2][1] !== 'out'
2685           || $this->tokens[$this->i + 3][0] !== \T_CONCAT_EQUAL)
2686               return \false;
2687          $this->i += 4;
2688          return \true;
2689      }
2690  }
2691  
2692  /*
2693  * @package   s9e\TextFormatter
2694  * @copyright Copyright (c) 2010-2019 The s9e Authors
2695  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2696  */
2697  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
2698  class Optimizer
2699  {
2700      public $branchOutputOptimizer;
2701      protected $cnt;
2702      protected $i;
2703      public $maxLoops = 10;
2704      protected $tokens;
2705  	public function __construct()
2706      {
2707          $this->branchOutputOptimizer = new BranchOutputOptimizer;
2708      }
2709  	public function optimize($php)
2710      {
2711          $this->tokens = \token_get_all('<?php ' . $php);
2712          $this->cnt    = \count($this->tokens);
2713          $this->i      = 0;
2714          foreach ($this->tokens as &$token)
2715              if (\is_array($token))
2716                  unset($token[2]);
2717          unset($token);
2718          $passes = [
2719              'optimizeOutConcatEqual',
2720              'optimizeConcatenations',
2721              'optimizeHtmlspecialchars'
2722          ];
2723          $remainingLoops = $this->maxLoops;
2724          do
2725          {
2726              $continue = \false;
2727              foreach ($passes as $pass)
2728              {
2729                  $this->$pass();
2730                  $cnt = \count($this->tokens);
2731                  if ($this->cnt !== $cnt)
2732                  {
2733                      $this->tokens = \array_values($this->tokens);
2734                      $this->cnt    = $cnt;
2735                      $continue     = \true;
2736                  }
2737              }
2738          }
2739          while ($continue && --$remainingLoops);
2740          $php = $this->branchOutputOptimizer->optimize($this->tokens);
2741          unset($this->tokens);
2742          return $php;
2743      }
2744  	protected function isBetweenHtmlspecialcharCalls()
2745      {
2746          return ($this->tokens[$this->i + 1]    === [\T_STRING, 'htmlspecialchars']
2747               && $this->tokens[$this->i + 2]    === '('
2748               && $this->tokens[$this->i - 1]    === ')'
2749               && $this->tokens[$this->i - 2][0] === \T_LNUMBER
2750               && $this->tokens[$this->i - 3]    === ',');
2751      }
2752  	protected function isHtmlspecialcharSafeVar()
2753      {
2754          return ($this->tokens[$this->i    ]    === [\T_VARIABLE,        '$node']
2755               && $this->tokens[$this->i + 1]    === [\T_OBJECT_OPERATOR, '->']
2756               && ($this->tokens[$this->i + 2]   === [\T_STRING,          'localName']
2757                || $this->tokens[$this->i + 2]   === [\T_STRING,          'nodeName'])
2758               && $this->tokens[$this->i + 3]    === ','
2759               && $this->tokens[$this->i + 4][0] === \T_LNUMBER
2760               && $this->tokens[$this->i + 5]    === ')');
2761      }
2762  	protected function isOutputAssignment()
2763      {
2764          return ($this->tokens[$this->i    ] === [\T_VARIABLE,        '$this']
2765               && $this->tokens[$this->i + 1] === [\T_OBJECT_OPERATOR, '->']
2766               && $this->tokens[$this->i + 2] === [\T_STRING,          'out']
2767               && $this->tokens[$this->i + 3] === [\T_CONCAT_EQUAL,    '.=']);
2768      }
2769  	protected function isPrecededByOutputVar()
2770      {
2771          return ($this->tokens[$this->i - 1] === [\T_STRING,          'out']
2772               && $this->tokens[$this->i - 2] === [\T_OBJECT_OPERATOR, '->']
2773               && $this->tokens[$this->i - 3] === [\T_VARIABLE,        '$this']);
2774      }
2775  	protected function mergeConcatenatedHtmlSpecialChars()
2776      {
2777          if (!$this->isBetweenHtmlspecialcharCalls())
2778               return \false;
2779          $escapeMode = $this->tokens[$this->i - 2][1];
2780          $startIndex = $this->i - 3;
2781          $endIndex = $this->i + 2;
2782          $this->i = $endIndex;
2783          $parens = 0;
2784          while (++$this->i < $this->cnt)
2785          {
2786              if ($this->tokens[$this->i] === ',' && !$parens)
2787                  break;
2788              if ($this->tokens[$this->i] === '(')
2789                  ++$parens;
2790              elseif ($this->tokens[$this->i] === ')')
2791                  --$parens;
2792          }
2793          if ($this->tokens[$this->i + 1] !== [\T_LNUMBER, $escapeMode])
2794              return \false;
2795          $this->tokens[$startIndex] = '.';
2796          $this->i = $startIndex;
2797          while (++$this->i <= $endIndex)
2798              unset($this->tokens[$this->i]);
2799          return \true;
2800      }
2801  	protected function mergeConcatenatedStrings()
2802      {
2803          if ($this->tokens[$this->i - 1][0]    !== \T_CONSTANT_ENCAPSED_STRING
2804           || $this->tokens[$this->i + 1][0]    !== \T_CONSTANT_ENCAPSED_STRING
2805           || $this->tokens[$this->i - 1][1][0] !== $this->tokens[$this->i + 1][1][0])
2806              return \false;
2807          $this->tokens[$this->i + 1][1] = \substr($this->tokens[$this->i - 1][1], 0, -1)
2808                                         . \substr($this->tokens[$this->i + 1][1], 1);
2809          unset($this->tokens[$this->i - 1]);
2810          unset($this->tokens[$this->i]);
2811          ++$this->i;
2812          return \true;
2813      }
2814  	protected function optimizeOutConcatEqual()
2815      {
2816          $this->i = 3;
2817          while ($this->skipTo([\T_CONCAT_EQUAL, '.=']))
2818          {
2819              if (!$this->isPrecededByOutputVar())
2820                   continue;
2821              while ($this->skipPast(';'))
2822              {
2823                  if (!$this->isOutputAssignment())
2824                       break;
2825                  $this->tokens[$this->i - 1] = '.';
2826                  unset($this->tokens[$this->i++]);
2827                  unset($this->tokens[$this->i++]);
2828                  unset($this->tokens[$this->i++]);
2829                  unset($this->tokens[$this->i++]);
2830              }
2831          }
2832      }
2833  	protected function optimizeConcatenations()
2834      {
2835          $this->i = 1;
2836          while ($this->skipTo('.'))
2837              $this->mergeConcatenatedStrings() || $this->mergeConcatenatedHtmlSpecialChars();
2838      }
2839  	protected function optimizeHtmlspecialchars()
2840      {
2841          $this->i = 0;
2842          while ($this->skipPast([\T_STRING, 'htmlspecialchars']))
2843              if ($this->tokens[$this->i] === '(')
2844              {
2845                  ++$this->i;
2846                  $this->replaceHtmlspecialcharsLiteral() || $this->removeHtmlspecialcharsSafeVar();
2847              }
2848      }
2849  	protected function removeHtmlspecialcharsSafeVar()
2850      {
2851          if (!$this->isHtmlspecialcharSafeVar())
2852               return \false;
2853          unset($this->tokens[$this->i - 2]);
2854          unset($this->tokens[$this->i - 1]);
2855          unset($this->tokens[$this->i + 3]);
2856          unset($this->tokens[$this->i + 4]);
2857          unset($this->tokens[$this->i + 5]);
2858          $this->i += 6;
2859          return \true;
2860      }
2861  	protected function replaceHtmlspecialcharsLiteral()
2862      {
2863          if ($this->tokens[$this->i    ][0] !== \T_CONSTANT_ENCAPSED_STRING
2864           || $this->tokens[$this->i + 1]    !== ','
2865           || $this->tokens[$this->i + 2][0] !== \T_LNUMBER
2866           || $this->tokens[$this->i + 3]    !== ')')
2867              return \false;
2868          $this->tokens[$this->i][1] = \var_export(
2869              \htmlspecialchars(
2870                  \stripslashes(\substr($this->tokens[$this->i][1], 1, -1)),
2871                  $this->tokens[$this->i + 2][1]
2872              ),
2873              \true
2874          );
2875          unset($this->tokens[$this->i - 2]);
2876          unset($this->tokens[$this->i - 1]);
2877          unset($this->tokens[++$this->i]);
2878          unset($this->tokens[++$this->i]);
2879          unset($this->tokens[++$this->i]);
2880          return \true;
2881      }
2882  	protected function skipPast($token)
2883      {
2884          return ($this->skipTo($token) && ++$this->i < $this->cnt);
2885      }
2886  	protected function skipTo($token)
2887      {
2888          while (++$this->i < $this->cnt)
2889              if ($this->tokens[$this->i] === $token)
2890                  return \true;
2891          return \false;
2892      }
2893  }
2894  
2895  /*
2896  * @package   s9e\TextFormatter
2897  * @copyright Copyright (c) 2010-2019 The s9e Authors
2898  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
2899  */
2900  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
2901  use Closure;
2902  use RuntimeException;
2903  use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
2904  class Quick
2905  {
2906  	public static function getSource(array $compiledTemplates)
2907      {
2908          $map         = ['dynamic' => [], 'php' => [], 'static' => []];
2909          $tagNames    = [];
2910          $unsupported = [];
2911          unset($compiledTemplates['br']);
2912          unset($compiledTemplates['e']);
2913          unset($compiledTemplates['i']);
2914          unset($compiledTemplates['p']);
2915          unset($compiledTemplates['s']);
2916          foreach ($compiledTemplates as $tagName => $php)
2917          {
2918              $renderings = self::getRenderingStrategy($php);
2919              if (empty($renderings))
2920              {
2921                  $unsupported[] = $tagName;
2922                  continue;
2923              }
2924              foreach ($renderings as $i => $_562c18b7)
2925              {
2926                  list($strategy, $replacement) = $_562c18b7;
2927                  $match = (($i) ? '/' : '') . $tagName;
2928                  $map[$strategy][$match] = $replacement;
2929              }
2930              if (!isset($renderings[1]))
2931                  $tagNames[] = $tagName;
2932          }
2933          $php = [];
2934          $php[] = '    /** {@inheritdoc} */';
2935          $php[] = '    public $enableQuickRenderer=true;';
2936          $php[] = '    /** {@inheritdoc} */';
2937          $php[] = '    protected $static=' . self::export($map['static']) . ';';
2938          $php[] = '    /** {@inheritdoc} */';
2939          $php[] = '    protected $dynamic=' . self::export($map['dynamic']) . ';';
2940          $quickSource = '';
2941          if (!empty($map['php']))
2942              $quickSource = SwitchStatement::generate('$id', $map['php']);
2943          $regexp  = '(<(?:(?!/)(';
2944          $regexp .= ($tagNames) ? RegexpBuilder::fromList($tagNames) : '(?!)';
2945          $regexp .= ')(?: [^>]*)?>.*?</\\1|(/?(?!br/|p>)[^ />]+)[^>]*?(/)?)>)s';
2946          $php[] = '    /** {@inheritdoc} */';
2947          $php[] = '    protected $quickRegexp=' . \var_export($regexp, \true) . ';';
2948          if (!empty($unsupported))
2949          {
2950              $regexp = '(<(?:[!?]|' . RegexpBuilder::fromList($unsupported) . '[ />]))';
2951              $php[]  = '    /** {@inheritdoc} */';
2952              $php[]  = '    protected $quickRenderingTest=' . \var_export($regexp, \true) . ';';
2953          }
2954          $php[] = '    /** {@inheritdoc} */';
2955          $php[] = '    protected function renderQuickTemplate($id, $xml)';
2956          $php[] = '    {';
2957          $php[] = '        $attributes=$this->matchAttributes($xml);';
2958          $php[] = "        \$html='';" . $quickSource;
2959          $php[] = '';
2960          $php[] = '        return $html;';
2961          $php[] = '    }';
2962          return \implode("\n", $php);
2963      }
2964  	protected static function export(array $arr)
2965      {
2966          $exportKeys = (\array_keys($arr) !== \range(0, \count($arr) - 1));
2967          \ksort($arr);
2968          $entries = [];
2969          foreach ($arr as $k => $v)
2970              $entries[] = (($exportKeys) ? \var_export($k, \true) . '=>' : '')
2971                         . ((\is_array($v)) ? self::export($v) : \var_export($v, \true));
2972          return '[' . \implode(',', $entries) . ']';
2973      }
2974  	public static function getRenderingStrategy($php)
2975      {
2976          $phpRenderings = self::getQuickRendering($php);
2977          if (empty($phpRenderings))
2978              return [];
2979          $renderings = self::getStringRenderings($php);
2980          foreach ($phpRenderings as $i => $phpRendering)
2981              if (!isset($renderings[$i]) || \strpos($phpRendering, '$this->attributes[]') !== \false)
2982                  $renderings[$i] = ['php', $phpRendering];
2983          return $renderings;
2984      }
2985  	protected static function getQuickRendering($php)
2986      {
2987          if (\preg_match('(\\$this->at\\((?!\\$node\\);))', $php))
2988              return [];
2989          $tokens   = \token_get_all('<?php ' . $php);
2990          $tokens[] = [0, ''];
2991          \array_shift($tokens);
2992          $cnt = \count($tokens);
2993          $branch = [
2994              'braces'      => -1,
2995              'branches'    => [],
2996              'head'        => '',
2997              'passthrough' => 0,
2998              'statement'   => '',
2999              'tail'        => ''
3000          ];
3001          $braces = 0;
3002          $i = 0;
3003          do
3004          {
3005              if ($tokens[$i    ][0] === \T_VARIABLE
3006               && $tokens[$i    ][1] === '$this'
3007               && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR
3008               && $tokens[$i + 2][0] === \T_STRING
3009               && $tokens[$i + 2][1] === 'at'
3010               && $tokens[$i + 3]    === '('
3011               && $tokens[$i + 4][0] === \T_VARIABLE
3012               && $tokens[$i + 4][1] === '$node'
3013               && $tokens[$i + 5]    === ')'
3014               && $tokens[$i + 6]    === ';')
3015              {
3016                  if (++$branch['passthrough'] > 1)
3017                      return [];
3018                  $i += 6;
3019                  continue;
3020              }
3021              $key = ($branch['passthrough']) ? 'tail' : 'head';
3022              $branch[$key] .= (\is_array($tokens[$i])) ? $tokens[$i][1] : $tokens[$i];
3023              if ($tokens[$i] === '{')
3024              {
3025                  ++$braces;
3026                  continue;
3027              }
3028              if ($tokens[$i] === '}')
3029              {
3030                  --$braces;
3031                  if ($branch['braces'] === $braces)
3032                  {
3033                      $branch[$key] = \substr($branch[$key], 0, -1);
3034                      $branch =& $branch['parent'];
3035                      $j = $i;
3036                      while ($tokens[++$j][0] === \T_WHITESPACE);
3037                      if ($tokens[$j][0] !== \T_ELSEIF && $tokens[$j][0] !== \T_ELSE)
3038                      {
3039                          $passthroughs = self::getBranchesPassthrough($branch['branches']);
3040                          if ($passthroughs === [0])
3041                          {
3042                              foreach ($branch['branches'] as $child)
3043                                  $branch['head'] .= $child['statement'] . '{' . $child['head'] . '}';
3044                              $branch['branches'] = [];
3045                              continue;
3046                          }
3047                          if ($passthroughs === [1])
3048                          {
3049                              ++$branch['passthrough'];
3050                              continue;
3051                          }
3052                          return [];
3053                      }
3054                  }
3055                  continue;
3056              }
3057              if ($branch['passthrough'])
3058                  continue;
3059              if ($tokens[$i][0] === \T_IF
3060               || $tokens[$i][0] === \T_ELSEIF
3061               || $tokens[$i][0] === \T_ELSE)
3062              {
3063                  $branch[$key] = \substr($branch[$key], 0, -\strlen($tokens[$i][1]));
3064                  $branch['branches'][] = [
3065                      'braces'      => $braces,
3066                      'branches'    => [],
3067                      'head'        => '',
3068                      'parent'      => &$branch,
3069                      'passthrough' => 0,
3070                      'statement'   => '',
3071                      'tail'        => ''
3072                  ];
3073                  $branch =& $branch['branches'][\count($branch['branches']) - 1];
3074                  do
3075                  {
3076                      $branch['statement'] .= (\is_array($tokens[$i])) ? $tokens[$i][1] : $tokens[$i];
3077                  }
3078                  while ($tokens[++$i] !== '{');
3079                  ++$braces;
3080              }
3081          }
3082          while (++$i < $cnt);
3083          list($head, $tail) = self::buildPHP($branch['branches']);
3084          $head  = $branch['head'] . $head;
3085          $tail .= $branch['tail'];
3086          self::convertPHP($head, $tail, (bool) $branch['passthrough']);
3087          if (\preg_match('((?<!-|\\$this)->)', $head . $tail))
3088              return [];
3089          return ($branch['passthrough']) ? [$head, $tail] : [$head];
3090      }
3091  	protected static function convertPHP(&$head, &$tail, $passthrough)
3092      {
3093          $saveAttributes = (bool) \preg_match('(\\$node->(?:get|has)Attribute)', $tail);
3094          \preg_match_all(
3095              "(\\\$node->getAttribute\\('([^']+)'\\))",
3096              \preg_replace_callback(
3097                  '(if\\(\\$node->hasAttribute\\(([^\\)]+)[^}]+)',
3098                  function ($m)
3099                  {
3100                      return \str_replace('$node->getAttribute(' . $m[1] . ')', '', $m[0]);
3101                  },
3102                  $head . $tail
3103              ),
3104              $matches
3105          );
3106          $attrNames = \array_unique($matches[1]);
3107          self::replacePHP($head);
3108          self::replacePHP($tail);
3109          if (!$passthrough && \strpos($head, '$node->textContent') !== \false)
3110              $head = '$textContent=$this->getQuickTextContent($xml);' . \str_replace('$node->textContent', '$textContent', $head);
3111          if (!empty($attrNames))
3112          {
3113              \ksort($attrNames);
3114              $head = "\$attributes+=['" . \implode("'=>null,'", $attrNames) . "'=>null];" . $head;
3115          }
3116          if ($saveAttributes)
3117          {
3118              $head .= '$this->attributes[]=$attributes;';
3119              $tail  = '$attributes=array_pop($this->attributes);' . $tail;
3120          }
3121      }
3122  	protected static function replacePHP(&$php)
3123      {
3124          $getAttribute = "\\\$node->getAttribute\\(('[^']+')\\)";
3125          $string       = "'(?:[^\\\\']|\\\\.)*+'";
3126          $replacements = [
3127              '$this->out' => '$html',
3128              '(htmlspecialchars\\(' . $getAttribute . ',' . \ENT_NOQUOTES . '\\))'
3129                  => "str_replace('&quot;','\"',\$attributes[\$1])",
3130              '(htmlspecialchars\\((' . $getAttribute . '(?:\\.' . $getAttribute . ')*),' . \ENT_COMPAT . '\\))'
3131                  => function ($m) use ($getAttribute)
3132                  {
3133                      return \preg_replace('(' . $getAttribute . ')', '$attributes[$1]', $m[1]);
3134                  },
3135              '(htmlspecialchars\\(strtr\\(' . $getAttribute . ",('[^\"&\\\\';<>aglmopqtu]+'),('[^\"&\\\\'<>]+')\\)," . \ENT_COMPAT . '\\))'
3136                  => 'strtr($attributes[$1],$2,$3)',
3137              '(' . $getAttribute . '(!?=+)' . $getAttribute . ')'
3138                  => '$attributes[$1]$2$attributes[$3]',
3139              '(' . $getAttribute . '===(' . $string . '))s'
3140                  => function ($m)
3141                  {
3142                      return '$attributes[' . $m[1] . ']===' . \htmlspecialchars($m[2], \ENT_COMPAT);
3143                  },
3144              '((' . $string . ')===' . $getAttribute . ')s'
3145                  => function ($m)
3146                  {
3147                      return \htmlspecialchars($m[1], \ENT_COMPAT) . '===$attributes[' . $m[2] . ']';
3148                  },
3149              '(strpos\\(' . $getAttribute . ',(' . $string . ')\\)([!=]==(?:0|false)))s'
3150                  => function ($m)
3151                  {
3152                      return 'strpos($attributes[' . $m[1] . "]," . \htmlspecialchars($m[2], \ENT_COMPAT) . ')' . $m[3];
3153                  },
3154              '(strpos\\((' . $string . '),' . $getAttribute . '\\)([!=]==(?:0|false)))s'
3155                  => function ($m)
3156                  {
3157                      return 'strpos(' . \htmlspecialchars($m[1], \ENT_COMPAT) . ',$attributes[' . $m[2] . '])' . $m[3];
3158                  },
3159              '(' . $getAttribute . '(?=(?:==|[-+*])\\d+))'  => '$attributes[$1]',
3160              '(\\b(\\d+(?:==|[-+*]))' . $getAttribute . ')' => '$1$attributes[$2]',
3161              '(empty\\(' . $getAttribute . '\\))'           => 'empty($attributes[$1])',
3162              "(\\\$node->hasAttribute\\(('[^']+')\\))"      => 'isset($attributes[$1])',
3163              'if($node->attributes->length)'                => 'if($this->hasNonNullValues($attributes))',
3164              '(' . $getAttribute . ')' => 'htmlspecialchars_decode($attributes[$1])'
3165          ];
3166          foreach ($replacements as $match => $replace)
3167              if ($replace instanceof Closure)
3168                  $php = \preg_replace_callback($match, $replace, $php);
3169              elseif ($match[0] === '(')
3170                  $php = \preg_replace($match, $replace, $php);
3171              else
3172                  $php = \str_replace($match, $replace, $php);
3173      }
3174  	protected static function buildPHP(array $branches)
3175      {
3176          $return = ['', ''];
3177          foreach ($branches as $branch)
3178          {
3179              $return[0] .= $branch['statement'] . '{' . $branch['head'];
3180              $return[1] .= $branch['statement'] . '{';
3181              if ($branch['branches'])
3182              {
3183                  list($head, $tail) = self::buildPHP($branch['branches']);
3184                  $return[0] .= $head;
3185                  $return[1] .= $tail;
3186              }
3187              $return[0] .= '}';
3188              $return[1] .= $branch['tail'] . '}';
3189          }
3190          return $return;
3191      }
3192  	protected static function getBranchesPassthrough(array $branches)
3193      {
3194          $values = [];
3195          foreach ($branches as $branch)
3196              $values[] = $branch['passthrough'];
3197          if ($branch['statement'] !== 'else')
3198              $values[] = 0;
3199          return \array_unique($values);
3200      }
3201  	protected static function getDynamicRendering($php)
3202      {
3203          $rendering = '';
3204          $literal   = "(?<literal>'((?>[^'\\\\]+|\\\\['\\\\])*)')";
3205          $attribute = "(?<attribute>htmlspecialchars\\(\\\$node->getAttribute\\('([^']+)'\\),2\\))";
3206          $value     = "(?<value>$literal|$attribute)";
3207          $output    = "(?<output>\\\$this->out\\.=$value(?:\\.(?&value))*;)";
3208          $copyOfAttribute = "(?<copyOfAttribute>if\\(\\\$node->hasAttribute\\('([^']+)'\\)\\)\\{\\\$this->out\\.=' \\g-1=\"'\\.htmlspecialchars\\(\\\$node->getAttribute\\('\\g-1'\\),2\\)\\.'\"';\\})";
3209          $regexp = '(^(' . $output . '|' . $copyOfAttribute . ')*$)';
3210          if (!\preg_match($regexp, $php, $m))
3211              return \false;
3212          $copiedAttributes = [];
3213          $usedAttributes = [];
3214          $regexp = '(' . $output . '|' . $copyOfAttribute . ')A';
3215          $offset = 0;
3216          while (\preg_match($regexp, $php, $m, 0, $offset))
3217              if ($m['output'])
3218              {
3219                  $offset += 12;
3220                  while (\preg_match('(' . $value . ')A', $php, $m, 0, $offset))
3221                  {
3222                      if ($m['literal'])
3223                      {
3224                          $str = \stripslashes(\substr($m[0], 1, -1));
3225                          $rendering .= \preg_replace('([\\\\$](?=\\d))', '\\\\$0', $str);
3226                      }
3227                      else
3228                      {
3229                          $attrName = \end($m);
3230                          if (!isset($usedAttributes[$attrName]))
3231                              $usedAttributes[$attrName] = \uniqid($attrName, \true);
3232                          $rendering .= $usedAttributes[$attrName];
3233                      }
3234                      $offset += 1 + \strlen($m[0]);
3235                  }
3236              }
3237              else
3238              {
3239                  $attrName = \end($m);
3240                  if (!isset($copiedAttributes[$attrName]))
3241                      $copiedAttributes[$attrName] = \uniqid($attrName, \true);
3242                  $rendering .= $copiedAttributes[$attrName];
3243                  $offset += \strlen($m[0]);
3244              }
3245          $attrNames = \array_keys($copiedAttributes + $usedAttributes);
3246          \sort($attrNames);
3247          $remainingAttributes = \array_combine($attrNames, $attrNames);
3248          $regexp = '(^[^ ]+';
3249          $index  = 0;
3250          foreach ($attrNames as $attrName)
3251          {
3252              $regexp .= '(?> (?!' . RegexpBuilder::fromList($remainingAttributes) . '=)[^=]+="[^"]*")*';
3253              unset($remainingAttributes[$attrName]);
3254              $regexp .= '(';
3255              if (isset($copiedAttributes[$attrName]))
3256                  self::replacePlaceholder($rendering, $copiedAttributes[$attrName], ++$index);
3257              else
3258                  $regexp .= '?>';
3259              $regexp .= ' ' . $attrName . '="';
3260              if (isset($usedAttributes[$attrName]))
3261              {
3262                  $regexp .= '(';
3263                  self::replacePlaceholder($rendering, $usedAttributes[$attrName], ++$index);
3264              }
3265              $regexp .= '[^"]*';
3266              if (isset($usedAttributes[$attrName]))
3267                  $regexp .= ')';
3268              $regexp .= '")?';
3269          }
3270          $regexp .= '.*)s';
3271          return [$regexp, $rendering];
3272      }
3273  	protected static function getStaticRendering($php)
3274      {
3275          if ($php === '')
3276              return '';
3277          $regexp = "(^\\\$this->out\.='((?>[^'\\\\]|\\\\['\\\\])*+)';\$)";
3278          if (\preg_match($regexp, $php, $m))
3279              return \stripslashes($m[1]);
3280          return \false;
3281      }
3282  	protected static function getStringRenderings($php)
3283      {
3284          $chunks = \explode('$this->at($node);', $php);
3285          if (\count($chunks) > 2)
3286              return [];
3287          $renderings = [];
3288          foreach ($chunks as $k => $chunk)
3289          {
3290              $rendering = self::getStaticRendering($chunk);
3291              if ($rendering !== \false)
3292                  $renderings[$k] = ['static', $rendering];
3293              elseif ($k === 0)
3294              {
3295                  $rendering = self::getDynamicRendering($chunk);
3296                  if ($rendering !== \false)
3297                      $renderings[$k] = ['dynamic', $rendering];
3298              }
3299          }
3300          return $renderings;
3301      }
3302  	protected static function replacePlaceholder(&$str, $uniqid, $index)
3303      {
3304          $str = \preg_replace_callback(
3305              '(' . \preg_quote($uniqid) . '(.))',
3306              function ($m) use ($index)
3307              {
3308                  if (\is_numeric($m[1]))
3309                      return '${' . $index . '}' . $m[1];
3310                  else
3311                      return '$' . $index . $m[1];
3312              },
3313              $str
3314          );
3315      }
3316  }
3317  
3318  /*
3319  * @package   s9e\TextFormatter
3320  * @copyright Copyright (c) 2010-2019 The s9e Authors
3321  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
3322  */
3323  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
3324  use DOMElement;
3325  use DOMXPath;
3326  use RuntimeException;
3327  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
3328  use s9e\TextFormatter\Configurator\Helpers\TemplateParser;
3329  class Serializer
3330  {
3331      public $convertor;
3332      protected $isVoid;
3333      public $useMultibyteStringFunctions = \false;
3334      protected $xpath;
3335  	public function __construct()
3336      {
3337          $this->convertor = new XPathConvertor;
3338      }
3339  	public function convertCondition($expr)
3340      {
3341          $this->convertor->useMultibyteStringFunctions = $this->useMultibyteStringFunctions;
3342          return $this->convertor->convertCondition($expr);
3343      }
3344  	public function convertXPath($expr)
3345      {
3346          $this->convertor->useMultibyteStringFunctions = $this->useMultibyteStringFunctions;
3347          return $this->convertor->convertXPath($expr);
3348      }
3349  	public function serialize(DOMElement $ir)
3350      {
3351          $this->xpath  = new DOMXPath($ir->ownerDocument);
3352          $this->isVoid = [];
3353          foreach ($this->xpath->query('//element') as $element)
3354              $this->isVoid[$element->getAttribute('id')] = $element->getAttribute('void');
3355          return $this->serializeChildren($ir);
3356      }
3357  	protected function convertAttributeValueTemplate($attrValue)
3358      {
3359          $phpExpressions = [];
3360          foreach (AVTHelper::parse($attrValue) as $token)
3361              if ($token[0] === 'literal')
3362                  $phpExpressions[] = \var_export($token[1], \true);
3363              else
3364                  $phpExpressions[] = $this->convertXPath($token[1]);
3365          return \implode('.', $phpExpressions);
3366      }
3367  	protected function escapeLiteral($text, $context)
3368      {
3369          if ($context === 'raw')
3370              return $text;
3371          $escapeMode = ($context === 'attribute') ? \ENT_COMPAT : \ENT_NOQUOTES;
3372          return \htmlspecialchars($text, $escapeMode);
3373      }
3374  	protected function escapePHPOutput($php, $context)
3375      {
3376          if ($context === 'raw')
3377              return $php;
3378          $escapeMode = ($context === 'attribute') ? \ENT_COMPAT : \ENT_NOQUOTES;
3379          return 'htmlspecialchars(' . $php . ',' . $escapeMode . ')';
3380      }
3381  	protected function hasMultipleCases(DOMElement $switch)
3382      {
3383          return $this->xpath->evaluate('count(case[@test]) > 1', $switch);
3384      }
3385  	protected function serializeApplyTemplates(DOMElement $applyTemplates)
3386      {
3387          $php = '$this->at($node';
3388          if ($applyTemplates->hasAttribute('select'))
3389              $php .= ',' . \var_export($applyTemplates->getAttribute('select'), \true);
3390          $php .= ');';
3391          return $php;
3392      }
3393  	protected function serializeAttribute(DOMElement $attribute)
3394      {
3395          $attrName = $attribute->getAttribute('name');
3396          $phpAttrName = $this->convertAttributeValueTemplate($attrName);
3397          $phpAttrName = 'htmlspecialchars(' . $phpAttrName . ',' . \ENT_QUOTES . ')';
3398          return "\$this->out.=' '." . $phpAttrName . ".'=\"';"
3399               . $this->serializeChildren($attribute)
3400               . "\$this->out.='\"';";
3401      }
3402  	protected function serializeChildren(DOMElement $ir)
3403      {
3404          $php = '';
3405          foreach ($ir->childNodes as $node)
3406              if ($node instanceof DOMElement)
3407              {
3408                  $methodName = 'serialize' . \ucfirst($node->localName);
3409                  $php .= $this->$methodName($node);
3410              }
3411          return $php;
3412      }
3413  	protected function serializeCloseTag(DOMElement $closeTag)
3414      {
3415          $php = "\$this->out.='>';";
3416          $id  = $closeTag->getAttribute('id');
3417          if ($closeTag->hasAttribute('set'))
3418              $php .= '$t' . $id . '=1;';
3419          if ($closeTag->hasAttribute('check'))
3420              $php = 'if(!isset($t' . $id . ')){' . $php . '}';
3421          if ($this->isVoid[$id] === 'maybe')
3422              $php .= 'if(!$v' . $id . '){';
3423          return $php;
3424      }
3425  	protected function serializeComment(DOMElement $comment)
3426      {
3427          return "\$this->out.='<!--';"
3428               . $this->serializeChildren($comment)
3429               . "\$this->out.='-->';";
3430      }
3431  	protected function serializeCopyOfAttributes(DOMElement $copyOfAttributes)
3432      {
3433          return 'foreach($node->attributes as $attribute){'
3434               . "\$this->out.=' ';\$this->out.=\$attribute->name;\$this->out.='=\"';\$this->out.=htmlspecialchars(\$attribute->value," . \ENT_COMPAT . ");\$this->out.='\"';"
3435               . '}';
3436      }
3437  	protected function serializeElement(DOMElement $element)
3438      {
3439          $php     = '';
3440          $elName  = $element->getAttribute('name');
3441          $id      = $element->getAttribute('id');
3442          $isVoid  = $element->getAttribute('void');
3443          $isDynamic = (bool) (\strpos($elName, '{') !== \false);
3444          $phpElName = $this->convertAttributeValueTemplate($elName);
3445          $phpElName = 'htmlspecialchars(' . $phpElName . ',' . \ENT_QUOTES . ')';
3446          if ($isDynamic)
3447          {
3448              $varName = '$e' . $id;
3449              $php .= $varName . '=' . $phpElName . ';';
3450              $phpElName = $varName;
3451          }
3452          if ($isVoid === 'maybe')
3453              $php .= '$v' . $id . '=preg_match(' . \var_export(TemplateParser::$voidRegexp, \true) . ',' . $phpElName . ');';
3454          $php .= "\$this->out.='<'." . $phpElName . ';';
3455          $php .= $this->serializeChildren($element);
3456          if ($isVoid !== 'yes')
3457              $php .= "\$this->out.='</'." . $phpElName . ".'>';";
3458          if ($isVoid === 'maybe')
3459              $php .= '}';
3460          return $php;
3461      }
3462  	protected function serializeHash(DOMElement $switch)
3463      {
3464          $statements = [];
3465          foreach ($this->xpath->query('case[@branch-values]', $switch) as $case)
3466              foreach (\unserialize($case->getAttribute('branch-values')) as $value)
3467                  $statements[$value] = $this->serializeChildren($case);
3468          if (!isset($case))
3469              throw new RuntimeException;
3470          $defaultCase = $this->xpath->query('case[not(@branch-values)]', $switch)->item(0);
3471          $defaultCode = ($defaultCase instanceof DOMElement) ? $this->serializeChildren($defaultCase) : '';
3472          $expr        = $this->convertXPath($switch->getAttribute('branch-key'));
3473          return SwitchStatement::generate($expr, $statements, $defaultCode);
3474      }
3475  	protected function serializeOutput(DOMElement $output)
3476      {
3477          $context = $output->getAttribute('escape');
3478          $php = '$this->out.=';
3479          if ($output->getAttribute('type') === 'xpath')
3480              $php .= $this->escapePHPOutput($this->convertXPath($output->textContent), $context);
3481          else
3482              $php .= \var_export($this->escapeLiteral($output->textContent, $context), \true);
3483          $php .= ';';
3484          return $php;
3485      }
3486  	protected function serializeSwitch(DOMElement $switch)
3487      {
3488          if ($switch->hasAttribute('branch-key') && $this->hasMultipleCases($switch))
3489              return $this->serializeHash($switch);
3490          $php   = '';
3491          $if    = 'if';
3492          foreach ($this->xpath->query('case', $switch) as $case)
3493          {
3494              if ($case->hasAttribute('test'))
3495                  $php .= $if . '(' . $this->convertCondition($case->getAttribute('test')) . ')';
3496              else
3497                  $php .= 'else';
3498              $php .= '{' . $this->serializeChildren($case) . '}';
3499              $if   = 'elseif';
3500          }
3501          return $php;
3502      }
3503  }
3504  
3505  /*
3506  * @package   s9e\TextFormatter
3507  * @copyright Copyright (c) 2010-2019 The s9e Authors
3508  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
3509  */
3510  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
3511  class SwitchStatement
3512  {
3513      protected $branchesCode;
3514      protected $defaultCode;
3515  	public function __construct(array $branchesCode, $defaultCode = '')
3516      {
3517          \ksort($branchesCode);
3518          $this->branchesCode = $branchesCode;
3519          $this->defaultCode  = $defaultCode;
3520      }
3521  	public static function generate($expr, array $branchesCode, $defaultCode = '')
3522      {
3523          $switch = new static($branchesCode, $defaultCode);
3524          return $switch->getSource($expr);
3525      }
3526  	protected function getSource($expr)
3527      {
3528          $php = 'switch(' . $expr . '){';
3529          foreach ($this->getValuesPerCodeBranch() as $branchCode => $values)
3530          {
3531              foreach ($values as $value)
3532                  $php .= 'case' . \var_export((string) $value, \true) . ':';
3533              $php .= $branchCode . 'break;';
3534          }
3535          if ($this->defaultCode > '')
3536              $php .= 'default:' . $this->defaultCode;
3537          $php = \preg_replace('(break;$)', '', $php) . '}';
3538          return $php;
3539      }
3540  	protected function getValuesPerCodeBranch()
3541      {
3542          $values = [];
3543          foreach ($this->branchesCode as $value => $branchCode)
3544              $values[$branchCode][] = $value;
3545          return $values;
3546      }
3547  }
3548  
3549  /*
3550  * @package   s9e\TextFormatter
3551  * @copyright Copyright (c) 2010-2019 The s9e Authors
3552  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
3553  */
3554  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
3555  use LogicException;
3556  use RuntimeException;
3557  class XPathConvertor
3558  {
3559      public $pcreVersion;
3560      protected $regexp;
3561      public $useMultibyteStringFunctions = \false;
3562  	public function __construct()
3563      {
3564          $this->pcreVersion = \PCRE_VERSION;
3565      }
3566  	public function convertCondition($expr)
3567      {
3568          $expr = \trim($expr);
3569          if (\preg_match('#^@([-\\w]+)$#', $expr, $m))
3570              return '$node->hasAttribute(' . \var_export($m[1], \true) . ')';
3571          if ($expr === '@*')
3572              return '$node->attributes->length';
3573          if (\preg_match('#^not\\(@([-\\w]+)\\)$#', $expr, $m))
3574              return '!$node->hasAttribute(' . \var_export($m[1], \true) . ')';
3575          if (\preg_match('#^\\$(\\w+)$#', $expr, $m))
3576              return '$this->params[' . \var_export($m[1], \true) . "]!==''";
3577          if (\preg_match('#^not\\(\\$(\\w+)\\)$#', $expr, $m))
3578              return '$this->params[' . \var_export($m[1], \true) . "]===''";
3579          if (\preg_match('#^([$@][-\\w]+)\\s*([<>])\\s*(\\d+)$#', $expr, $m))
3580              return $this->convertXPath($m[1]) . $m[2] . $m[3];
3581          if (!\preg_match('#[=<>]|\\bor\\b|\\band\\b|^[-\\w]+\\s*\\(#', $expr))
3582              $expr = 'boolean(' . $expr . ')';
3583          return $this->convertXPath($expr);
3584      }
3585  	public function convertXPath($expr)
3586      {
3587          $expr = \trim($expr);
3588          $this->generateXPathRegexp();
3589          if (\preg_match($this->regexp, $expr, $m))
3590          {
3591              $methodName = \null;
3592              foreach ($m as $k => $v)
3593              {
3594                  if (\is_numeric($k) || $v === '' || $v === \null || !\method_exists($this, $k))
3595                      continue;
3596                  $methodName = $k;
3597                  break;
3598              }
3599              if (isset($methodName))
3600              {
3601                  $args = [$m[$methodName]];
3602                  $i = 0;
3603                  while (isset($m[$methodName . $i]))
3604                  {
3605                      $args[$i] = $m[$methodName . $i];
3606                      ++$i;
3607                  }
3608                  return \call_user_func_array([$this, $methodName], $args);
3609              }
3610          }
3611          if (!\preg_match('#[=<>]|\\bor\\b|\\band\\b|^[-\\w]+\\s*\\(#', $expr))
3612              $expr = 'string(' . $expr . ')';
3613          return '$this->xpath->evaluate(' . $this->exportXPath($expr) . ',$node)';
3614      }
3615  	protected function attr($attrName)
3616      {
3617          return '$node->getAttribute(' . \var_export($attrName, \true) . ')';
3618      }
3619  	protected function dot()
3620      {
3621          return '$node->textContent';
3622      }
3623  	protected function param($paramName)
3624      {
3625          return '$this->params[' . \var_export($paramName, \true) . ']';
3626      }
3627  	protected function string($string)
3628      {
3629          return \var_export(\substr($string, 1, -1), \true);
3630      }
3631  	protected function lname()
3632      {
3633          return '$node->localName';
3634      }
3635  	protected function name()
3636      {
3637          return '$node->nodeName';
3638      }
3639  	protected function number($sign, $number)
3640      {
3641          $number = \ltrim($number, '0') ?: 0;
3642          if (!$number)
3643              $sign = '';
3644          return "'" . $sign . $number . "'";
3645      }
3646  	protected function strlen($expr)
3647      {
3648          if ($expr === '')
3649              $expr = '.';
3650          $php = $this->convertXPath($expr);
3651          return ($this->useMultibyteStringFunctions)
3652              ? 'mb_strlen(' . $php . ",'utf-8')"
3653              : "strlen(preg_replace('(.)us','.'," . $php . '))';
3654      }
3655  	protected function contains($haystack, $needle)
3656      {
3657          return '(strpos(' . $this->convertXPath($haystack) . ',' . $this->convertXPath($needle) . ')!==false)';
3658      }
3659  	protected function startswith($string, $substring)
3660      {
3661          return '(strpos(' . $this->convertXPath($string) . ',' . $this->convertXPath($substring) . ')===0)';
3662      }
3663  	protected function not($expr)
3664      {
3665          return '!(' . $this->convertCondition($expr) . ')';
3666      }
3667  	protected function notcontains($haystack, $needle)
3668      {
3669          return '(strpos(' . $this->convertXPath($haystack) . ',' . $this->convertXPath($needle) . ')===false)';
3670      }
3671  	protected function substr($exprString, $exprPos, $exprLen = \null)
3672      {
3673          if (!$this->useMultibyteStringFunctions)
3674          {
3675              $expr = 'substring(' . $exprString . ',' . $exprPos;
3676              if (isset($exprLen))
3677                  $expr .= ',' . $exprLen;
3678              $expr .= ')';
3679              return '$this->xpath->evaluate(' . $this->exportXPath($expr) . ',$node)';
3680          }
3681          $php = 'mb_substr(' . $this->convertXPath($exprString) . ',';
3682          if (\is_numeric($exprPos))
3683              $php .= \max(0, $exprPos - 1);
3684          else
3685              $php .= 'max(0,' . $this->convertXPath($exprPos) . '-1)';
3686          $php .= ',';
3687          if (isset($exprLen))
3688              if (\is_numeric($exprLen))
3689                  if (\is_numeric($exprPos) && $exprPos < 1)
3690                      $php .= \max(0, $exprPos + $exprLen - 1);
3691                  else
3692                      $php .= \max(0, $exprLen);
3693              else
3694                  $php .= 'max(0,' . $this->convertXPath($exprLen) . ')';
3695          else
3696              $php .= 'null';
3697          $php .= ",'utf-8')";
3698          return $php;
3699      }
3700  	protected function substringafter($expr, $str)
3701      {
3702          return 'substr(strstr(' . $this->convertXPath($expr) . ',' . $this->convertXPath($str) . '),' . (\strlen($str) - 2) . ')';
3703      }
3704  	protected function substringbefore($expr1, $expr2)
3705      {
3706          return 'strstr(' . $this->convertXPath($expr1) . ',' . $this->convertXPath($expr2) . ',true)';
3707      }
3708  	protected function cmp($expr1, $operator, $expr2)
3709      {
3710          $operands  = [];
3711          $operators = [
3712              '='  => '===',
3713              '!=' => '!==',
3714              '>'  => '>',
3715              '>=' => '>=',
3716              '<'  => '<',
3717              '<=' => '<='
3718          ];
3719          foreach ([$expr1, $expr2] as $expr)
3720              if (\is_numeric($expr))
3721              {
3722                  $operators['=']  = '==';
3723                  $operators['!='] = '!=';
3724                  $operands[] = \preg_replace('(^0(.+))', '$1', $expr);
3725              }
3726              else
3727                  $operands[] = $this->convertXPath($expr);
3728          return \implode($operators[$operator], $operands);
3729      }
3730  	protected function bool($expr1, $operator, $expr2)
3731      {
3732          $operators = [
3733              'and' => '&&',
3734              'or'  => '||'
3735          ];
3736          return $this->convertCondition($expr1) . $operators[$operator] . $this->convertCondition($expr2);
3737      }
3738  	protected function parens($expr)
3739      {
3740          return '(' . $this->convertXPath($expr) . ')';
3741      }
3742  	protected function translate($str, $from, $to)
3743      {
3744          \preg_match_all('(.)su', \substr($from, 1, -1), $matches);
3745          $from = $matches[0];
3746          \preg_match_all('(.)su', \substr($to, 1, -1), $matches);
3747          $to = $matches[0];
3748          $from = \array_unique($from);
3749          $to   = \array_intersect_key($to, $from);
3750          $to  += \array_fill_keys(\array_keys(\array_diff_key($from, $to)), '');
3751          $php = 'strtr(' . $this->convertXPath($str) . ',';
3752          if ([1] === \array_unique(\array_map('strlen', $from))
3753           && [1] === \array_unique(\array_map('strlen', $to)))
3754              $php .= \var_export(\implode('', $from), \true) . ',' . \var_export(\implode('', $to), \true);
3755          else
3756          {
3757              $elements = [];
3758              foreach ($from as $k => $str)
3759                  $elements[] = \var_export($str, \true) . '=>' . \var_export($to[$k], \true);
3760              $php .= '[' . \implode(',', $elements) . ']';
3761          }
3762          $php .= ')';
3763          return $php;
3764      }
3765  	protected function math($expr1, $operator, $expr2)
3766      {
3767          if (!\is_numeric($expr1))
3768              $expr1 = $this->convertXPath($expr1);
3769          if (!\is_numeric($expr2))
3770              $expr2 = $this->convertXPath($expr2);
3771          if ($operator === 'div')
3772              $operator = '/';
3773          return $expr1 . $operator . $expr2;
3774      }
3775  	protected function exportXPath($expr)
3776      {
3777          $phpTokens = [];
3778          foreach ($this->tokenizeXPathForExport($expr) as $_f6b3b659)
3779          {
3780              list($type, $content) = $_f6b3b659;
3781              $methodName  = 'exportXPath' . \ucfirst($type);
3782              $phpTokens[] = $this->$methodName($content);
3783          }
3784          return \implode('.', $phpTokens);
3785      }
3786  	protected function exportXPathCurrent()
3787      {
3788          return '$node->getNodePath()';
3789      }
3790  	protected function exportXPathFragment($fragment)
3791      {
3792          return \var_export($fragment, \true);
3793      }
3794  	protected function exportXPathParam($param)
3795      {
3796          $paramName = \ltrim($param, '$');
3797          return '$this->getParamAsXPath(' . \var_export($paramName, \true) . ')';
3798      }
3799  	protected function generateXPathRegexp()
3800      {
3801          if (isset($this->regexp))
3802              return;
3803          $patterns = [
3804              'attr'      => ['@', '(?<attr0>[-\\w]+)'],
3805              'dot'       => '\\.',
3806              'name'      => 'name\\(\\)',
3807              'lname'     => 'local-name\\(\\)',
3808              'param'     => ['\\$', '(?<param0>\\w+)'],
3809              'string'    => '"[^"]*"|\'[^\']*\'',
3810              'number'    => ['(?<number0>-?)', '(?<number1>\\d++)'],
3811              'strlen'    => ['string-length', '\\(', '(?<strlen0>(?&value)?)', '\\)'],
3812              'contains'  => [
3813                  'contains',
3814                  '\\(',
3815                  '(?<contains0>(?&value))',
3816                  ',',
3817                  '(?<contains1>(?&value))',
3818                  '\\)'
3819              ],
3820              'translate' => [
3821                  'translate',
3822                  '\\(',
3823                  '(?<translate0>(?&value))',
3824                  ',',
3825                  '(?<translate1>(?&string))',
3826                  ',',
3827                  '(?<translate2>(?&string))',
3828                  '\\)'
3829              ],
3830              'substr' => [
3831                  'substring',
3832                  '\\(',
3833                  '(?<substr0>(?&value))',
3834                  ',',
3835                  '(?<substr1>(?&value))',
3836                  '(?:, (?<substr2>(?&value)))?',
3837                  '\\)'
3838              ],
3839              'substringafter' => [
3840                  'substring-after',
3841                  '\\(',
3842                  '(?<substringafter0>(?&value))',
3843                  ',',
3844                  '(?<substringafter1>(?&string))',
3845                  '\\)'
3846              ],
3847              'substringbefore' => [
3848                  'substring-before',
3849                  '\\(',
3850                  '(?<substringbefore0>(?&value))',
3851                  ',',
3852                  '(?<substringbefore1>(?&value))',
3853                  '\\)'
3854              ],
3855              'startswith' => [
3856                  'starts-with',
3857                  '\\(',
3858                  '(?<startswith0>(?&value))',
3859                  ',',
3860                  '(?<startswith1>(?&value))',
3861                  '\\)'
3862              ],
3863              'math' => [
3864                  '(?<math0>(?&attr)|(?&number)|(?&param))',
3865                  '(?<math1>[-+*]|div)',
3866                  '(?<math2>(?&math)|(?&math0))'
3867              ],
3868              'notcontains' => [
3869                  'not',
3870                  '\\(',
3871                  'contains',
3872                  '\\(',
3873                  '(?<notcontains0>(?&value))',
3874                  ',',
3875                  '(?<notcontains1>(?&value))',
3876                  '\\)',
3877                  '\\)'
3878              ]
3879          ];
3880          $exprs = [];
3881          if (\version_compare($this->pcreVersion, '8.13', '>='))
3882          {
3883              $exprs[] = '(?<cmp>(?<cmp0>(?&value)) (?<cmp1>!?=) (?<cmp2>(?&value)))';
3884              $exprs[] = '(?<parens>\\( (?<parens0>(?&bool)|(?&cmp)|(?&math)) \\))';
3885              $exprs[] = '(?<bool>(?<bool0>(?&cmp)|(?&not)|(?&value)|(?&parens)) (?<bool1>and|or) (?<bool2>(?&bool)|(?&cmp)|(?&not)|(?&value)|(?&parens)))';
3886              $exprs[] = '(?<not>not \\( (?<not0>(?&bool)|(?&value)) \\))';
3887              $patterns['math'][0] = \str_replace('))', ')|(?&parens))', $patterns['math'][0]);
3888              $patterns['math'][1] = \str_replace('))', ')|(?&parens))', $patterns['math'][1]);
3889          }
3890          $valueExprs = [];
3891          foreach ($patterns as $name => $pattern)
3892          {
3893              if (\is_array($pattern))
3894                  $pattern = \implode(' ', $pattern);
3895              if (\strpos($pattern, '?&') === \false || \version_compare($this->pcreVersion, '8.13', '>='))
3896                  $valueExprs[] = '(?<' . $name . '>' . $pattern . ')';
3897          }
3898          \array_unshift($exprs, '(?<value>' . \implode('|', $valueExprs) . ')');
3899          $regexp = '#^(?:' . \implode('|', $exprs) . ')$#S';
3900          $regexp = \str_replace(' ', '\\s*', $regexp);
3901          $this->regexp = $regexp;
3902      }
3903  	protected function matchXPathForExport($expr)
3904      {
3905          $tokenExprs = [
3906              '(?<current>\\bcurrent\\(\\))',
3907              '(?<param>\\$\\w+)',
3908              '(?<fragment>"[^"]*"|\'[^\']*\'|.)'
3909          ];
3910          \preg_match_all('(' . \implode('|', $tokenExprs) . ')s', $expr, $matches, \PREG_SET_ORDER);
3911          $i = \count($matches);
3912          while (--$i > 0)
3913              if (isset($matches[$i]['fragment'], $matches[$i - 1]['fragment']))
3914              {
3915                  $matches[$i - 1]['fragment'] .= $matches[$i]['fragment'];
3916                  unset($matches[$i]);
3917              }
3918          return \array_values($matches);
3919      }
3920  	protected function tokenizeXPathForExport($expr)
3921      {
3922          $tokens = [];
3923          foreach ($this->matchXPathForExport($expr) as $match)
3924              foreach (\array_reverse($match) as $k => $v)
3925                  if (!\is_numeric($k))
3926                  {
3927                      $tokens[] = [$k, $v];
3928                      break;
3929                  }
3930          return $tokens;
3931      }
3932  }
3933  
3934  /*
3935  * @package   s9e\TextFormatter
3936  * @copyright Copyright (c) 2010-2019 The s9e Authors
3937  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
3938  */
3939  namespace s9e\TextFormatter\Configurator\RulesGenerators\Interfaces;
3940  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
3941  interface BooleanRulesGenerator
3942  {
3943  	public function generateBooleanRules(TemplateInspector $src);
3944  }
3945  
3946  /*
3947  * @package   s9e\TextFormatter
3948  * @copyright Copyright (c) 2010-2019 The s9e Authors
3949  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
3950  */
3951  namespace s9e\TextFormatter\Configurator\RulesGenerators\Interfaces;
3952  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
3953  interface TargetedRulesGenerator
3954  {
3955  	public function generateTargetedRules(TemplateInspector $src, TemplateInspector $trg);
3956  }
3957  
3958  /*
3959  * @package   s9e\TextFormatter
3960  * @copyright Copyright (c) 2010-2019 The s9e Authors
3961  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
3962  */
3963  namespace s9e\TextFormatter\Configurator;
3964  use DOMElement;
3965  use s9e\TextFormatter\Configurator\Items\Tag;
3966  abstract class TemplateCheck
3967  {
3968      const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
3969      abstract public function check(DOMElement $template, Tag $tag);
3970  }
3971  
3972  /*
3973  * @package   s9e\TextFormatter
3974  * @copyright Copyright (c) 2010-2019 The s9e Authors
3975  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
3976  */
3977  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
3978  use DOMAttr;
3979  use DOMComment;
3980  use DOMElement;
3981  use DOMNode;
3982  use DOMXPath;
3983  abstract class AbstractNormalization
3984  {
3985      const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
3986      public $onlyOnce = \false;
3987      protected $ownerDocument;
3988      protected $queries = [];
3989      protected $xpath;
3990  	public function normalize(DOMElement $template)
3991      {
3992          $this->ownerDocument = $template->ownerDocument;
3993          $this->xpath         = new DOMXPath($this->ownerDocument);
3994          foreach ($this->getNodes() as $node)
3995              $this->normalizeNode($node);
3996          $this->reset();
3997      }
3998  	protected function createElement($nodeName, $textContent = '')
3999      {
4000          $methodName = 'createElement';
4001          $args       = [$nodeName];
4002          if ($textContent !== '')
4003              $args[] = \htmlspecialchars($textContent, \ENT_NOQUOTES, 'UTF-8');
4004          $prefix = \strstr($nodeName, ':', \true);
4005          if ($prefix > '')
4006          {
4007              $methodName .= 'NS';
4008              \array_unshift($args, $this->ownerDocument->lookupNamespaceURI($prefix));
4009          }
4010          return \call_user_func_array([$this->ownerDocument, $methodName], $args);
4011      }
4012  	protected function createText($content)
4013      {
4014          return (\trim($content) === '')
4015               ? $this->createElement('xsl:text', $content)
4016               : $this->ownerDocument->createTextNode($content);
4017      }
4018  	protected function createTextNode($content)
4019      {
4020          return $this->ownerDocument->createTextNode($content);
4021      }
4022  	protected function getNodes()
4023      {
4024          $query = \implode(' | ', $this->queries);
4025          return ($query === '') ? [] : $this->xpath($query);
4026      }
4027  	protected function isXsl(DOMNode $node, $localName = \null)
4028      {
4029          return ($node->namespaceURI === self::XMLNS_XSL && (!isset($localName) || $localName === $node->localName));
4030      }
4031  	protected function lowercase($str)
4032      {
4033          return \strtr($str, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz');
4034      }
4035  	protected function normalizeAttribute(DOMAttr $attribute)
4036      {
4037      }
4038  	protected function normalizeElement(DOMElement $element)
4039      {
4040      }
4041  	protected function normalizeNode(DOMNode $node)
4042      {
4043          if (!$node->parentNode)
4044              return;
4045          if ($node instanceof DOMElement)
4046              $this->normalizeElement($node);
4047          elseif ($node instanceof DOMAttr)
4048              $this->normalizeAttribute($node);
4049      }
4050  	protected function reset()
4051      {
4052          $this->ownerDocument = \null;
4053          $this->xpath         = \null;
4054      }
4055  	protected function xpath($query, DOMNode $node = \null)
4056      {
4057          $query = \str_replace('$XSL', '"' . self::XMLNS_XSL . '"', $query);
4058          return \iterator_to_array($this->xpath->query($query, $node));
4059      }
4060  }
4061  
4062  /*
4063  * @package   s9e\TextFormatter
4064  * @copyright Copyright (c) 2010-2019 The s9e Authors
4065  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4066  */
4067  namespace s9e\TextFormatter\Configurator\Traits;
4068  trait CollectionProxy
4069  {
4070  	public function __call($methodName, $args)
4071      {
4072          return \call_user_func_array([$this->collection, $methodName], $args);
4073      }
4074  	public function offsetExists($offset)
4075      {
4076          return isset($this->collection[$offset]);
4077      }
4078  	public function offsetGet($offset)
4079      {
4080          return $this->collection[$offset];
4081      }
4082  	public function offsetSet($offset, $value)
4083      {
4084          $this->collection[$offset] = $value;
4085      }
4086  	public function offsetUnset($offset)
4087      {
4088          unset($this->collection[$offset]);
4089      }
4090  	public function count()
4091      {
4092          return \count($this->collection);
4093      }
4094  	public function current()
4095      {
4096          return $this->collection->current();
4097      }
4098  	public function key()
4099      {
4100          return $this->collection->key();
4101      }
4102  	public function next()
4103      {
4104          return $this->collection->next();
4105      }
4106  	public function rewind()
4107      {
4108          $this->collection->rewind();
4109      }
4110  	public function valid()
4111      {
4112          return $this->collection->valid();
4113      }
4114  }
4115  
4116  /*
4117  * @package   s9e\TextFormatter
4118  * @copyright Copyright (c) 2010-2019 The s9e Authors
4119  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4120  */
4121  namespace s9e\TextFormatter\Configurator\Traits;
4122  use InvalidArgumentException;
4123  use RuntimeException;
4124  use Traversable;
4125  use s9e\TextFormatter\Configurator\Collections\Collection;
4126  use s9e\TextFormatter\Configurator\Collections\NormalizedCollection;
4127  trait Configurable
4128  {
4129  	public function __get($propName)
4130      {
4131          $methodName = 'get' . \ucfirst($propName);
4132          if (\method_exists($this, $methodName))
4133              return $this->$methodName();
4134          if (!\property_exists($this, $propName))
4135              throw new RuntimeException("Property '" . $propName . "' does not exist");
4136          return $this->$propName;
4137      }
4138  	public function __set($propName, $propValue)
4139      {
4140          $methodName = 'set' . \ucfirst($propName);
4141          if (\method_exists($this, $methodName))
4142          {
4143              $this->$methodName($propValue);
4144              return;
4145          }
4146          if (!isset($this->$propName))
4147          {
4148              $this->$propName = $propValue;
4149              return;
4150          }
4151          if ($this->$propName instanceof NormalizedCollection)
4152          {
4153              if (!\is_array($propValue)
4154               && !($propValue instanceof Traversable))
4155                  throw new InvalidArgumentException("Property '" . $propName . "' expects an array or a traversable object to be passed");
4156              $this->$propName->clear();
4157              foreach ($propValue as $k => $v)
4158                  $this->$propName->set($k, $v);
4159              return;
4160          }
4161          if (\is_object($this->$propName))
4162          {
4163              if (!($propValue instanceof $this->$propName))
4164                  throw new InvalidArgumentException("Cannot replace property '" . $propName . "' of class '" . \get_class($this->$propName) . "' with instance of '" . \get_class($propValue) . "'");
4165          }
4166          else
4167          {
4168              $oldType = \gettype($this->$propName);
4169              $newType = \gettype($propValue);
4170              if ($oldType === 'boolean')
4171                  if ($propValue === 'false')
4172                  {
4173                      $newType   = 'boolean';
4174                      $propValue = \false;
4175                  }
4176                  elseif ($propValue === 'true')
4177                  {
4178                      $newType   = 'boolean';
4179                      $propValue = \true;
4180                  }
4181              if ($oldType !== $newType)
4182              {
4183                  $tmp = $propValue;
4184                  \settype($tmp, $oldType);
4185                  \settype($tmp, $newType);
4186                  if ($tmp !== $propValue)
4187                      throw new InvalidArgumentException("Cannot replace property '" . $propName . "' of type " . $oldType . ' with value of type ' . $newType);
4188                  \settype($propValue, $oldType);
4189              }
4190          }
4191          $this->$propName = $propValue;
4192      }
4193  	public function __isset($propName)
4194      {
4195          $methodName = 'isset' . \ucfirst($propName);
4196          if (\method_exists($this, $methodName))
4197              return $this->$methodName();
4198          return isset($this->$propName);
4199      }
4200  	public function __unset($propName)
4201      {
4202          $methodName = 'unset' . \ucfirst($propName);
4203          if (\method_exists($this, $methodName))
4204          {
4205              $this->$methodName();
4206              return;
4207          }
4208          if (!isset($this->$propName))
4209              return;
4210          if ($this->$propName instanceof Collection)
4211          {
4212              $this->$propName->clear();
4213              return;
4214          }
4215          throw new RuntimeException("Property '" . $propName . "' cannot be unset");
4216      }
4217  }
4218  
4219  /*
4220  * @package   s9e\TextFormatter
4221  * @copyright Copyright (c) 2010-2019 The s9e Authors
4222  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4223  */
4224  namespace s9e\TextFormatter\Configurator\Traits;
4225  trait TemplateSafeness
4226  {
4227      protected $markedSafe = [];
4228  	protected function isSafe($context)
4229      {
4230          return !empty($this->markedSafe[$context]);
4231      }
4232  	public function isSafeAsURL()
4233      {
4234          return $this->isSafe('AsURL');
4235      }
4236  	public function isSafeInCSS()
4237      {
4238          return $this->isSafe('InCSS');
4239      }
4240  	public function isSafeInJS()
4241      {
4242          return $this->isSafe('InJS');
4243      }
4244  	public function markAsSafeAsURL()
4245      {
4246          $this->markedSafe['AsURL'] = \true;
4247          return $this;
4248      }
4249  	public function markAsSafeInCSS()
4250      {
4251          $this->markedSafe['InCSS'] = \true;
4252          return $this;
4253      }
4254  	public function markAsSafeInJS()
4255      {
4256          $this->markedSafe['InJS'] = \true;
4257          return $this;
4258      }
4259  	public function resetSafeness()
4260      {
4261          $this->markedSafe = [];
4262          return $this;
4263      }
4264  }
4265  
4266  /*
4267  * @package   s9e\TextFormatter
4268  * @copyright Copyright (c) 2010-2019 The s9e Authors
4269  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4270  */
4271  namespace s9e\TextFormatter\Configurator\Validators;
4272  use InvalidArgumentException;
4273  abstract class AttributeName
4274  {
4275  	public static function isValid($name)
4276      {
4277          return (bool) \preg_match('#^(?!xmlns$)[a-z_][-a-z_0-9]*$#Di', $name);
4278      }
4279  	public static function normalize($name)
4280      {
4281          if (!static::isValid($name))
4282              throw new InvalidArgumentException("Invalid attribute name '" . $name . "'");
4283          return \strtolower($name);
4284      }
4285  }
4286  
4287  /*
4288  * @package   s9e\TextFormatter
4289  * @copyright Copyright (c) 2010-2019 The s9e Authors
4290  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4291  */
4292  namespace s9e\TextFormatter\Configurator\Validators;
4293  use InvalidArgumentException;
4294  abstract class TagName
4295  {
4296  	public static function isValid($name)
4297      {
4298          return (bool) \preg_match('#^(?:(?!xmlns|xsl|s9e)[a-z_][a-z_0-9]*:)?[a-z_][-a-z_0-9]*$#Di', $name);
4299      }
4300  	public static function normalize($name)
4301      {
4302          if (!static::isValid($name))
4303              throw new InvalidArgumentException("Invalid tag name '" . $name . "'");
4304          if (\strpos($name, ':') === \false)
4305              $name = \strtoupper($name);
4306          return $name;
4307      }
4308  }
4309  
4310  /*
4311  * @package   s9e\TextFormatter
4312  * @copyright Copyright (c) 2010-2019 The s9e Authors
4313  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4314  */
4315  namespace s9e\TextFormatter\Configurator\Collections;
4316  use Countable;
4317  use Iterator;
4318  use s9e\TextFormatter\Configurator\ConfigProvider;
4319  use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
4320  class Collection implements ConfigProvider, Countable, Iterator
4321  {
4322      protected $items = [];
4323  	public function clear()
4324      {
4325          $this->items = [];
4326      }
4327  	public function asConfig()
4328      {
4329          return ConfigHelper::toArray($this->items, \true);
4330      }
4331  	public function count()
4332      {
4333          return \count($this->items);
4334      }
4335  	public function current()
4336      {
4337          return \current($this->items);
4338      }
4339  	public function key()
4340      {
4341          return \key($this->items);
4342      }
4343  	public function next()
4344      {
4345          return \next($this->items);
4346      }
4347  	public function rewind()
4348      {
4349          \reset($this->items);
4350      }
4351  	public function valid()
4352      {
4353          return (\key($this->items) !== \null);
4354      }
4355  }
4356  
4357  /*
4358  * @package   s9e\TextFormatter
4359  * @copyright Copyright (c) 2010-2019 The s9e Authors
4360  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4361  */
4362  namespace s9e\TextFormatter\Configurator\Helpers\TemplateParser;
4363  use DOMDocument;
4364  use DOMElement;
4365  use DOMNode;
4366  use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
4367  class Normalizer extends IRProcessor
4368  {
4369      protected $optimizer;
4370      public $voidRegexp = '/^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/Di';
4371  	public function __construct(Optimizer $optimizer)
4372      {
4373          $this->optimizer = $optimizer;
4374      }
4375  	public function normalize(DOMDocument $ir)
4376      {
4377          $this->createXPath($ir);
4378          $this->addDefaultCase($ir);
4379          $this->addElementIds($ir);
4380          $this->addCloseTagElements($ir);
4381          $this->markVoidElements($ir);
4382          $this->optimizer->optimize($ir);
4383          $this->markConditionalCloseTagElements($ir);
4384          $this->setOutputContext($ir);
4385          $this->markBranchTables($ir);
4386      }
4387  	protected function addCloseTagElements(DOMDocument $ir)
4388      {
4389          $exprs = [
4390              '//applyTemplates[not(ancestor::attribute)]',
4391              '//comment',
4392              '//element',
4393              '//output[not(ancestor::attribute)]'
4394          ];
4395          foreach ($this->query(\implode('|', $exprs)) as $node)
4396          {
4397              $parentElementId = $this->getParentElementId($node);
4398              if (isset($parentElementId))
4399                  $node->parentNode
4400                       ->insertBefore($ir->createElement('closeTag'), $node)
4401                       ->setAttribute('id', $parentElementId);
4402              if ($node->nodeName === 'element')
4403              {
4404                  $id = $node->getAttribute('id');
4405                  $this->appendElement($node, 'closeTag')->setAttribute('id', $id);
4406              }
4407          }
4408      }
4409  	protected function addDefaultCase(DOMDocument $ir)
4410      {
4411          foreach ($this->query('//switch[not(case[not(@test)])]') as $switch)
4412              $this->appendElement($switch, 'case');
4413      }
4414  	protected function addElementIds(DOMDocument $ir)
4415      {
4416          $id = 0;
4417          foreach ($this->query('//element') as $element)
4418              $element->setAttribute('id', ++$id);
4419      }
4420  	protected function getOutputContext(DOMNode $output)
4421      {
4422          $contexts = [
4423              'boolean(ancestor::attribute)'             => 'attribute',
4424              '@disable-output-escaping="yes"'           => 'raw',
4425              'count(ancestor::element[@name="script"])' => 'raw'
4426          ];
4427          foreach ($contexts as $expr => $context)
4428              if ($this->evaluate($expr, $output))
4429                  return $context;
4430          return 'text';
4431      }
4432  	protected function getParentElementId(DOMNode $node)
4433      {
4434          $parentNode = $node->parentNode;
4435          while (isset($parentNode))
4436          {
4437              if ($parentNode->nodeName === 'element')
4438                  return $parentNode->getAttribute('id');
4439              $parentNode = $parentNode->parentNode;
4440          }
4441      }
4442  	protected function markBranchTables(DOMDocument $ir)
4443      {
4444          foreach ($this->query('//switch[case[2][@test]]') as $switch)
4445              $this->markSwitchTable($switch);
4446      }
4447  	protected function markSwitchTable(DOMElement $switch)
4448      {
4449          $cases = [];
4450          $maps  = [];
4451          foreach ($this->query('./case[@test]', $switch) as $i => $case)
4452          {
4453              $map = XPathHelper::parseEqualityExpr($case->getAttribute('test'));
4454              if ($map === \false)
4455                  return;
4456              $maps     += $map;
4457              $cases[$i] = [$case, \end($map)];
4458          }
4459          if (\count($maps) !== 1)
4460              return;
4461          $switch->setAttribute('branch-key', \key($maps));
4462          foreach ($cases as $_6920557c)
4463          {
4464              list($case, $values) = $_6920557c;
4465              \sort($values);
4466              $case->setAttribute('branch-values', \serialize($values));
4467          }
4468      }
4469  	protected function markConditionalCloseTagElements(DOMDocument $ir)
4470      {
4471          foreach ($this->query('//closeTag') as $closeTag)
4472          {
4473              $id = $closeTag->getAttribute('id');
4474              $query = 'ancestor::switch/following-sibling::*/descendant-or-self::closeTag[@id = "' . $id . '"]';
4475              foreach ($this->query($query, $closeTag) as $following)
4476              {
4477                  $following->setAttribute('check', '');
4478                  $closeTag->setAttribute('set', '');
4479              }
4480          }
4481      }
4482  	protected function markVoidElements(DOMDocument $ir)
4483      {
4484          foreach ($this->query('//element') as $element)
4485          {
4486              $elName = $element->getAttribute('name');
4487              if (\strpos($elName, '{') !== \false)
4488                  $element->setAttribute('void', 'maybe');
4489              elseif (\preg_match($this->voidRegexp, $elName))
4490                  $element->setAttribute('void', 'yes');
4491          }
4492      }
4493  	protected function setOutputContext(DOMDocument $ir)
4494      {
4495          foreach ($this->query('//output') as $output)
4496              $output->setAttribute('escape', $this->getOutputContext($output));
4497      }
4498  }
4499  
4500  /*
4501  * @package   s9e\TextFormatter
4502  * @copyright Copyright (c) 2010-2019 The s9e Authors
4503  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4504  */
4505  namespace s9e\TextFormatter\Configurator\Helpers\TemplateParser;
4506  use DOMDocument;
4507  use DOMElement;
4508  use DOMNode;
4509  class Optimizer extends IRProcessor
4510  {
4511  	public function optimize(DOMDocument $ir)
4512      {
4513          $this->createXPath($ir);
4514          $xml = $ir->saveXML();
4515          $remainingLoops = 10;
4516          do
4517          {
4518              $old = $xml;
4519              $this->optimizeCloseTagElements($ir);
4520              $xml = $ir->saveXML();
4521          }
4522          while (--$remainingLoops > 0 && $xml !== $old);
4523          $this->removeCloseTagSiblings($ir);
4524          $this->removeContentFromVoidElements($ir);
4525          $this->mergeConsecutiveLiteralOutputElements($ir);
4526          $this->removeEmptyDefaultCases($ir);
4527      }
4528  	protected function cloneCloseTagElementsIntoSwitch(DOMDocument $ir)
4529      {
4530          $query = '//switch[name(following-sibling::*[1]) = "closeTag"]';
4531          foreach ($this->query($query) as $switch)
4532          {
4533              $closeTag = $switch->nextSibling;
4534              foreach ($this->query('case', $switch) as $case)
4535                  if (!$case->lastChild || $case->lastChild->nodeName !== 'closeTag')
4536                      $case->appendChild($closeTag->cloneNode());
4537          }
4538      }
4539  	protected function cloneCloseTagElementsOutOfSwitch(DOMDocument $ir)
4540      {
4541          $query = '//switch[case/closeTag][not(case[name(*[1]) != "closeTag"])]';
4542          foreach ($this->query($query) as $switch)
4543          {
4544              $case = $this->query('case/closeTag', $switch)->item(0);
4545              $switch->parentNode->insertBefore($case->cloneNode(), $switch);
4546          }
4547      }
4548  	protected function mergeConsecutiveLiteralOutputElements(DOMDocument $ir)
4549      {
4550          foreach ($this->query('//output[@type="literal"]') as $output)
4551          {
4552              $disableOutputEscaping = $output->getAttribute('disable-output-escaping');
4553              while ($this->nextSiblingIsLiteralOutput($output, $disableOutputEscaping))
4554              {
4555                  $output->nodeValue = \htmlspecialchars($output->nodeValue . $output->nextSibling->nodeValue);
4556                  $output->parentNode->removeChild($output->nextSibling);
4557              }
4558          }
4559      }
4560  	protected function nextSiblingIsLiteralOutput(DOMElement $node, $disableOutputEscaping)
4561      {
4562          return isset($node->nextSibling) && $node->nextSibling->nodeName === 'output' && $node->nextSibling->getAttribute('type') === 'literal' && $node->nextSibling->getAttribute('disable-output-escaping') === $disableOutputEscaping;
4563      }
4564  	protected function optimizeCloseTagElements(DOMDocument $ir)
4565      {
4566          $this->cloneCloseTagElementsIntoSwitch($ir);
4567          $this->cloneCloseTagElementsOutOfSwitch($ir);
4568          $this->removeRedundantCloseTagElementsInSwitch($ir);
4569          $this->removeRedundantCloseTagElements($ir);
4570      }
4571  	protected function removeCloseTagSiblings(DOMDocument $ir)
4572      {
4573          $query = '//switch[not(case[not(closeTag)])]/following-sibling::closeTag';
4574          $this->removeNodes($ir, $query);
4575      }
4576  	protected function removeContentFromVoidElements(DOMDocument $ir)
4577      {
4578          foreach ($this->query('//element[@void="yes"]') as $element)
4579          {
4580              $id    = $element->getAttribute('id');
4581              $query = './/closeTag[@id="' . $id . '"]/following-sibling::*';
4582              $this->removeNodes($ir, $query, $element);
4583          }
4584      }
4585  	protected function removeEmptyDefaultCases(DOMDocument $ir)
4586      {
4587          $query = '//case[not(@test)][not(*)][. = ""]';
4588          $this->removeNodes($ir, $query);
4589      }
4590  	protected function removeNodes(DOMDocument $ir, $query, DOMNode $contextNode = \null)
4591      {
4592          foreach ($this->query($query, $contextNode) as $node)
4593              if ($node->parentNode instanceof DOMElement)
4594                  $node->parentNode->removeChild($node);
4595      }
4596  	protected function removeRedundantCloseTagElements(DOMDocument $ir)
4597      {
4598          foreach ($this->query('//closeTag') as $closeTag)
4599          {
4600              $id    = $closeTag->getAttribute('id');
4601              $query = 'following-sibling::*/descendant-or-self::closeTag[@id="' . $id . '"]';
4602              $this->removeNodes($ir, $query, $closeTag);
4603          }
4604      }
4605  	protected function removeRedundantCloseTagElementsInSwitch(DOMDocument $ir)
4606      {
4607          $query = '//switch[name(following-sibling::*[1]) = "closeTag"]';
4608          foreach ($this->query($query) as $switch)
4609              foreach ($this->query('case', $switch) as $case)
4610                  while ($case->lastChild && $case->lastChild->nodeName === 'closeTag')
4611                      $case->removeChild($case->lastChild);
4612      }
4613  }
4614  
4615  /*
4616  * @package   s9e\TextFormatter
4617  * @copyright Copyright (c) 2010-2019 The s9e Authors
4618  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4619  */
4620  namespace s9e\TextFormatter\Configurator\Helpers\TemplateParser;
4621  use DOMDocument;
4622  use DOMElement;
4623  use DOMXPath;
4624  use RuntimeException;
4625  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
4626  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
4627  class Parser extends IRProcessor
4628  {
4629      protected $normalizer;
4630  	public function __construct(Normalizer $normalizer)
4631      {
4632          $this->normalizer = $normalizer;
4633      }
4634  	public function parse($template)
4635      {
4636          $dom = TemplateHelper::loadTemplate($template);
4637          $ir = new DOMDocument;
4638          $ir->loadXML('<template/>');
4639          $this->createXPath($dom);
4640          $this->parseChildren($ir->documentElement, $dom->documentElement);
4641          $this->normalizer->normalize($ir);
4642          return $ir;
4643      }
4644  	protected function appendAVT(DOMElement $parentNode, $avt)
4645      {
4646          foreach (AVTHelper::parse($avt) as $token)
4647              if ($token[0] === 'expression')
4648                  $this->appendXPathOutput($parentNode, $token[1]);
4649              else
4650                  $this->appendLiteralOutput($parentNode, $token[1]);
4651      }
4652  	protected function appendLiteralOutput(DOMElement $parentNode, $content)
4653      {
4654          if ($content === '')
4655              return;
4656          $this->appendElement($parentNode, 'output', \htmlspecialchars($content))
4657               ->setAttribute('type', 'literal');
4658      }
4659  	protected function appendConditionalAttributes(DOMElement $parentNode, $expr)
4660      {
4661          \preg_match_all('(@([-\\w]+))', $expr, $matches);
4662          foreach ($matches[1] as $attrName)
4663          {
4664              $switch = $this->appendElement($parentNode, 'switch');
4665              $case   = $this->appendElement($switch, 'case');
4666              $case->setAttribute('test', '@' . $attrName);
4667              $attribute = $this->appendElement($case, 'attribute');
4668              $attribute->setAttribute('name', $attrName);
4669              $this->appendXPathOutput($attribute, '@' . $attrName);
4670          }
4671      }
4672  	protected function appendXPathOutput(DOMElement $parentNode, $expr)
4673      {
4674          $this->appendElement($parentNode, 'output', \htmlspecialchars(\trim($expr)))
4675               ->setAttribute('type', 'xpath');
4676      }
4677  	protected function parseChildren(DOMElement $ir, DOMElement $parent)
4678      {
4679          foreach ($parent->childNodes as $child)
4680          {
4681              switch ($child->nodeType)
4682              {
4683                  case \XML_COMMENT_NODE:
4684                      break;
4685                  case \XML_TEXT_NODE:
4686                      if (\trim($child->textContent) !== '')
4687                          $this->appendLiteralOutput($ir, $child->textContent);
4688                      break;
4689                  case \XML_ELEMENT_NODE:
4690                      $this->parseNode($ir, $child);
4691                      break;
4692                  default:
4693                      throw new RuntimeException("Cannot parse node '" . $child->nodeName . "''");
4694              }
4695          }
4696      }
4697  	protected function parseNode(DOMElement $ir, DOMElement $node)
4698      {
4699          if ($node->namespaceURI === self::XMLNS_XSL)
4700          {
4701              $methodName = 'parseXsl' . \str_replace(' ', '', \ucwords(\str_replace('-', ' ', $node->localName)));
4702              if (!\method_exists($this, $methodName))
4703                  throw new RuntimeException("Element '" . $node->nodeName . "' is not supported");
4704              return $this->$methodName($ir, $node);
4705          }
4706          $element = $this->appendElement($ir, 'element');
4707          $element->setAttribute('name', $node->nodeName);
4708          $xpath = new DOMXPath($node->ownerDocument);
4709          foreach ($xpath->query('namespace::*', $node) as $ns)
4710              if ($node->hasAttribute($ns->nodeName))
4711              {
4712                  $irAttribute = $this->appendElement($element, 'attribute');
4713                  $irAttribute->setAttribute('name', $ns->nodeName);
4714                  $this->appendLiteralOutput($irAttribute, $ns->nodeValue);
4715              }
4716          foreach ($node->attributes as $attribute)
4717          {
4718              $irAttribute = $this->appendElement($element, 'attribute');
4719              $irAttribute->setAttribute('name', $attribute->nodeName);
4720              $this->appendAVT($irAttribute, $attribute->value);
4721          }
4722          $this->parseChildren($element, $node);
4723      }
4724  	protected function parseXslApplyTemplates(DOMElement $ir, DOMElement $node)
4725      {
4726          $applyTemplates = $this->appendElement($ir, 'applyTemplates');
4727          if ($node->hasAttribute('select'))
4728              $applyTemplates->setAttribute('select', $node->getAttribute('select'));
4729      }
4730  	protected function parseXslAttribute(DOMElement $ir, DOMElement $node)
4731      {
4732          $attribute = $this->appendElement($ir, 'attribute');
4733          $attribute->setAttribute('name', $node->getAttribute('name'));
4734          $this->parseChildren($attribute, $node);
4735      }
4736  	protected function parseXslChoose(DOMElement $ir, DOMElement $node)
4737      {
4738          $switch = $this->appendElement($ir, 'switch');
4739          foreach ($this->query('./xsl:when', $node) as $when)
4740          {
4741              $case = $this->appendElement($switch, 'case');
4742              $case->setAttribute('test', $when->getAttribute('test'));
4743              $this->parseChildren($case, $when);
4744          }
4745          foreach ($this->query('./xsl:otherwise', $node) as $otherwise)
4746          {
4747              $case = $this->appendElement($switch, 'case');
4748              $this->parseChildren($case, $otherwise);
4749              break;
4750          }
4751      }
4752  	protected function parseXslComment(DOMElement $ir, DOMElement $node)
4753      {
4754          $comment = $this->appendElement($ir, 'comment');
4755          $this->parseChildren($comment, $node);
4756      }
4757  	protected function parseXslCopyOf(DOMElement $ir, DOMElement $node)
4758      {
4759          $expr = $node->getAttribute('select');
4760          if (\preg_match('#^@[-\\w]+(?:\\s*\\|\\s*@[-\\w]+)*$#', $expr, $m))
4761              $this->appendConditionalAttributes($ir, $expr);
4762          elseif ($expr === '@*')
4763              $this->appendElement($ir, 'copyOfAttributes');
4764          else
4765              throw new RuntimeException("Unsupported <xsl:copy-of/> expression '" . $expr . "'");
4766      }
4767  	protected function parseXslElement(DOMElement $ir, DOMElement $node)
4768      {
4769          $element = $this->appendElement($ir, 'element');
4770          $element->setAttribute('name', $node->getAttribute('name'));
4771          $this->parseChildren($element, $node);
4772      }
4773  	protected function parseXslIf(DOMElement $ir, DOMElement $node)
4774      {
4775          $switch = $this->appendElement($ir, 'switch');
4776          $case   = $this->appendElement($switch, 'case');
4777          $case->setAttribute('test', $node->getAttribute('test'));
4778          $this->parseChildren($case, $node);
4779      }
4780  	protected function parseXslText(DOMElement $ir, DOMElement $node)
4781      {
4782          $this->appendLiteralOutput($ir, $node->textContent);
4783          if ($node->getAttribute('disable-output-escaping') === 'yes')
4784              $ir->lastChild->setAttribute('disable-output-escaping', 'yes');
4785      }
4786  	protected function parseXslValueOf(DOMElement $ir, DOMElement $node)
4787      {
4788          $this->appendXPathOutput($ir, $node->getAttribute('select'));
4789          if ($node->getAttribute('disable-output-escaping') === 'yes')
4790              $ir->lastChild->setAttribute('disable-output-escaping', 'yes');
4791      }
4792  }
4793  
4794  /*
4795  * @package   s9e\TextFormatter
4796  * @copyright Copyright (c) 2010-2019 The s9e Authors
4797  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4798  */
4799  namespace s9e\TextFormatter\Configurator\Items;
4800  use s9e\TextFormatter\Configurator\Collections\AttributeFilterChain;
4801  use s9e\TextFormatter\Configurator\ConfigProvider;
4802  use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
4803  use s9e\TextFormatter\Configurator\Items\ProgrammableCallback;
4804  use s9e\TextFormatter\Configurator\Traits\Configurable;
4805  use s9e\TextFormatter\Configurator\Traits\TemplateSafeness;
4806  class Attribute implements ConfigProvider
4807  {
4808      use Configurable;
4809      use TemplateSafeness;
4810      protected $defaultValue;
4811      protected $filterChain;
4812      protected $required = \true;
4813  	public function __construct(array $options = \null)
4814      {
4815          $this->filterChain = new AttributeFilterChain;
4816          if (isset($options))
4817              foreach ($options as $optionName => $optionValue)
4818                  $this->__set($optionName, $optionValue);
4819      }
4820  	protected function isSafe($context)
4821      {
4822          $methodName = 'isSafe' . $context;
4823          foreach ($this->filterChain as $filter)
4824              if ($filter->$methodName())
4825                  return \true;
4826          return !empty($this->markedSafe[$context]);
4827      }
4828  	public function asConfig()
4829      {
4830          $vars = \get_object_vars($this);
4831          unset($vars['markedSafe']);
4832          return ConfigHelper::toArray($vars) + ['filterChain' => []];
4833      }
4834  }
4835  
4836  /*
4837  * @package   s9e\TextFormatter
4838  * @copyright Copyright (c) 2010-2019 The s9e Authors
4839  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4840  */
4841  namespace s9e\TextFormatter\Configurator\Items;
4842  use InvalidArgumentException;
4843  use s9e\TextFormatter\Configurator\ConfigProvider;
4844  use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
4845  use s9e\TextFormatter\Configurator\JavaScript\Code;
4846  use s9e\TextFormatter\Configurator\JavaScript\FunctionProvider;
4847  class ProgrammableCallback implements ConfigProvider
4848  {
4849      protected $callback;
4850      protected $js = 'returnFalse';
4851      protected $params = [];
4852      protected $vars = [];
4853  	public function __construct($callback)
4854      {
4855          if (!\is_callable($callback))
4856              throw new InvalidArgumentException(__METHOD__ . '() expects a callback');
4857          $this->callback = $this->normalizeCallback($callback);
4858          $this->autoloadJS();
4859      }
4860  	public function addParameterByValue($paramValue)
4861      {
4862          $this->params[] = $paramValue;
4863          return $this;
4864      }
4865  	public function addParameterByName($paramName)
4866      {
4867          if (\array_key_exists($paramName, $this->params))
4868              throw new InvalidArgumentException("Parameter '" . $paramName . "' already exists");
4869          $this->params[$paramName] = \null;
4870          return $this;
4871      }
4872  	public function getCallback()
4873      {
4874          return $this->callback;
4875      }
4876  	public function getJS()
4877      {
4878          return $this->js;
4879      }
4880  	public function getVars()
4881      {
4882          return $this->vars;
4883      }
4884  	public function resetParameters()
4885      {
4886          $this->params = [];
4887          return $this;
4888      }
4889  	public function setJS($js)
4890      {
4891          $this->js = $js;
4892          return $this;
4893      }
4894  	public function setVar($name, $value)
4895      {
4896          $this->vars[$name] = $value;
4897          return $this;
4898      }
4899  	public function setVars(array $vars)
4900      {
4901          $this->vars = $vars;
4902          return $this;
4903      }
4904  	public function asConfig()
4905      {
4906          $config = ['callback' => $this->callback];
4907          foreach ($this->params as $k => $v)
4908              if (\is_numeric($k))
4909                  $config['params'][] = $v;
4910              elseif (isset($this->vars[$k]))
4911                  $config['params'][] = $this->vars[$k];
4912              else
4913                  $config['params'][$k] = \null;
4914          if (isset($config['params']))
4915              $config['params'] = ConfigHelper::toArray($config['params'], \true, \true);
4916          $config['js'] = new Code($this->js);
4917          return $config;
4918      }
4919  	protected function autoloadJS()
4920      {
4921          if (!\is_string($this->callback))
4922              return;
4923          try
4924          {
4925              $this->js = FunctionProvider::get($this->callback);
4926          }
4927          catch (InvalidArgumentException $e)
4928          {
4929              }
4930      }
4931  	protected function normalizeCallback($callback)
4932      {
4933          if (\is_array($callback) && \is_string($callback[0]))
4934              $callback = $callback[0] . '::' . $callback[1];
4935          if (\is_string($callback))
4936              $callback = \ltrim($callback, '\\');
4937          return $callback;
4938      }
4939  }
4940  
4941  /*
4942  * @package   s9e\TextFormatter
4943  * @copyright Copyright (c) 2010-2019 The s9e Authors
4944  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
4945  */
4946  namespace s9e\TextFormatter\Configurator\Items;
4947  use InvalidArgumentException;
4948  use s9e\TextFormatter\Configurator\ConfigProvider;
4949  use s9e\TextFormatter\Configurator\FilterableConfigValue;
4950  use s9e\TextFormatter\Configurator\Helpers\RegexpParser;
4951  use s9e\TextFormatter\Configurator\JavaScript\Code;
4952  use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor;
4953  class Regexp implements ConfigProvider, FilterableConfigValue
4954  {
4955      protected $isGlobal;
4956      protected $jsRegexp;
4957      protected $regexp;
4958  	public function __construct($regexp, $isGlobal = \false)
4959      {
4960          if (@\preg_match($regexp, '') === \false)
4961              throw new InvalidArgumentException('Invalid regular expression ' . \var_export($regexp, \true));
4962          $this->regexp   = $regexp;
4963          $this->isGlobal = $isGlobal;
4964      }
4965  	public function __toString()
4966      {
4967          return $this->regexp;
4968      }
4969  	public function asConfig()
4970      {
4971          return $this;
4972      }
4973  	public function filterConfig($target)
4974      {
4975          return ($target === 'JS') ? new Code($this->getJS()) : (string) $this;
4976      }
4977  	public function getCaptureNames()
4978      {
4979          return RegexpParser::getCaptureNames($this->regexp);
4980      }
4981  	public function getJS()
4982      {
4983          if (!isset($this->jsRegexp))
4984              $this->jsRegexp = RegexpConvertor::toJS($this->regexp, $this->isGlobal);
4985          return $this->jsRegexp;
4986      }
4987  	public function getNamedCaptures()
4988      {
4989          $captures   = [];
4990          $regexpInfo = RegexpParser::parse($this->regexp);
4991          $start = $regexpInfo['delimiter'] . '^';
4992          $end   = '$' . $regexpInfo['delimiter'] . $regexpInfo['modifiers'];
4993          if (\strpos($regexpInfo['modifiers'], 'D') === \false)
4994              $end .= 'D';
4995          foreach ($this->getNamedCapturesExpressions($regexpInfo['tokens']) as $name => $expr)
4996              $captures[$name] = $start . $expr . $end;
4997          return $captures;
4998      }
4999  	protected function getNamedCapturesExpressions(array $tokens)
5000      {
5001          $exprs = [];
5002          foreach ($tokens as $token)
5003          {
5004              if ($token['type'] !== 'capturingSubpatternStart' || !isset($token['name']))
5005                  continue;
5006              $expr = $token['content'];
5007              if (\strpos($expr, '|') !== \false)
5008                  $expr = '(?:' . $expr . ')';
5009              $exprs[$token['name']] = $expr;
5010          }
5011          return $exprs;
5012      }
5013  	public function setJS($jsRegexp)
5014      {
5015          $this->jsRegexp = $jsRegexp;
5016      }
5017  }
5018  
5019  /*
5020  * @package   s9e\TextFormatter
5021  * @copyright Copyright (c) 2010-2019 The s9e Authors
5022  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5023  */
5024  namespace s9e\TextFormatter\Configurator\Items;
5025  use InvalidArgumentException;
5026  use s9e\TextFormatter\Configurator\Collections\AttributeCollection;
5027  use s9e\TextFormatter\Configurator\Collections\AttributePreprocessorCollection;
5028  use s9e\TextFormatter\Configurator\Collections\Ruleset;
5029  use s9e\TextFormatter\Configurator\Collections\TagFilterChain;
5030  use s9e\TextFormatter\Configurator\ConfigProvider;
5031  use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
5032  use s9e\TextFormatter\Configurator\Items\Template;
5033  use s9e\TextFormatter\Configurator\Traits\Configurable;
5034  class Tag implements ConfigProvider
5035  {
5036      use Configurable;
5037      protected $attributes;
5038      protected $attributePreprocessors;
5039      protected $filterChain;
5040      protected $nestingLimit = 10;
5041      protected $rules;
5042      protected $tagLimit = 5000;
5043      protected $template;
5044  	public function __construct(array $options = \null)
5045      {
5046          $this->attributes             = new AttributeCollection;
5047          $this->attributePreprocessors = new AttributePreprocessorCollection;
5048          $this->filterChain            = new TagFilterChain;
5049          $this->rules                  = new Ruleset;
5050          $this->filterChain->append('s9e\\TextFormatter\\Parser\\FilterProcessing::executeAttributePreprocessors')
5051                            ->addParameterByName('tagConfig')
5052                            ->setJS('executeAttributePreprocessors');
5053          $this->filterChain->append('s9e\\TextFormatter\\Parser\\FilterProcessing::filterAttributes')
5054                            ->addParameterByName('tagConfig')
5055                            ->addParameterByName('registeredVars')
5056                            ->addParameterByName('logger')
5057                            ->setJS('filterAttributes');
5058          if (isset($options))
5059          {
5060              \ksort($options);
5061              foreach ($options as $optionName => $optionValue)
5062                  $this->__set($optionName, $optionValue);
5063          }
5064      }
5065  	public function asConfig()
5066      {
5067          $vars = \get_object_vars($this);
5068          unset($vars['template']);
5069          if (!\count($this->attributePreprocessors))
5070          {
5071              $callback = 's9e\\TextFormatter\\Parser\\FilterProcessing::executeAttributePreprocessors';
5072              $filterChain = clone $vars['filterChain'];
5073              $i = \count($filterChain);
5074              while (--$i >= 0)
5075                  if ($filterChain[$i]->getCallback() === $callback)
5076                      unset($filterChain[$i]);
5077              $vars['filterChain'] = $filterChain;
5078          }
5079          return ConfigHelper::toArray($vars) + ['attributes' => [], 'filterChain' => []];
5080      }
5081  	public function getTemplate()
5082      {
5083          return $this->template;
5084      }
5085  	public function issetTemplate()
5086      {
5087          return isset($this->template);
5088      }
5089  	public function setAttributePreprocessors($attributePreprocessors)
5090      {
5091          $this->attributePreprocessors->clear();
5092          $this->attributePreprocessors->merge($attributePreprocessors);
5093      }
5094  	public function setNestingLimit($limit)
5095      {
5096          $limit = (int) $limit;
5097          if ($limit < 1)
5098              throw new InvalidArgumentException('nestingLimit must be a number greater than 0');
5099          $this->nestingLimit = $limit;
5100      }
5101  	public function setRules($rules)
5102      {
5103          $this->rules->clear();
5104          $this->rules->merge($rules);
5105      }
5106  	public function setTagLimit($limit)
5107      {
5108          $limit = (int) $limit;
5109          if ($limit < 1)
5110              throw new InvalidArgumentException('tagLimit must be a number greater than 0');
5111          $this->tagLimit = $limit;
5112      }
5113  	public function setTemplate($template)
5114      {
5115          if (!($template instanceof Template))
5116              $template = new Template($template);
5117          $this->template = $template;
5118      }
5119  	public function unsetTemplate()
5120      {
5121          unset($this->template);
5122      }
5123  }
5124  
5125  /*
5126  * @package   s9e\TextFormatter
5127  * @copyright Copyright (c) 2010-2019 The s9e Authors
5128  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5129  */
5130  namespace s9e\TextFormatter\Configurator\JavaScript;
5131  use s9e\TextFormatter\Configurator\FilterableConfigValue;
5132  class Code implements FilterableConfigValue
5133  {
5134      public $code;
5135  	public function __construct($code)
5136      {
5137          $this->code = $code;
5138      }
5139  	public function __toString()
5140      {
5141          return (string) $this->code;
5142      }
5143  	public function filterConfig($target)
5144      {
5145          return ($target === 'JS') ? $this : \null;
5146      }
5147  }
5148  
5149  /*
5150  * @package   s9e\TextFormatter
5151  * @copyright Copyright (c) 2010-2019 The s9e Authors
5152  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5153  */
5154  namespace s9e\TextFormatter\Configurator\RendererGenerators;
5155  use DOMElement;
5156  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
5157  use s9e\TextFormatter\Configurator\Helpers\TemplateParser;
5158  use s9e\TextFormatter\Configurator\RendererGenerator;
5159  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\ControlStructuresOptimizer;
5160  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\Optimizer;
5161  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\Quick;
5162  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\Serializer;
5163  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\SwitchStatement;
5164  use s9e\TextFormatter\Configurator\Rendering;
5165  use s9e\TextFormatter\Configurator\TemplateNormalizer;
5166  class PHP implements RendererGenerator
5167  {
5168      const XMLNS_XSL = 'http://www.w3.org/1999/XSL/Transform';
5169      public $cacheDir;
5170      public $className;
5171      public $controlStructuresOptimizer;
5172      public $defaultClassPrefix = 'Renderer_';
5173      public $enableQuickRenderer = \true;
5174      public $filepath;
5175      public $lastClassName;
5176      public $lastFilepath;
5177      protected $normalizer;
5178      public $optimizer;
5179      public $serializer;
5180      public $useMultibyteStringFunctions;
5181  	public function __construct($cacheDir = \null)
5182      {
5183          $this->cacheDir = (isset($cacheDir)) ? $cacheDir : \sys_get_temp_dir();
5184          if (\extension_loaded('tokenizer'))
5185          {
5186              $this->controlStructuresOptimizer = new ControlStructuresOptimizer;
5187              $this->optimizer = new Optimizer;
5188          }
5189          $this->useMultibyteStringFunctions = \extension_loaded('mbstring');
5190          $this->serializer = new Serializer;
5191          $this->normalizer = new TemplateNormalizer;
5192          $this->normalizer->clear();
5193          $this->normalizer->append('RemoveLivePreviewAttributes');
5194      }
5195  	public function getRenderer(Rendering $rendering)
5196      {
5197          $php = $this->generate($rendering);
5198          if (isset($this->filepath))
5199              $filepath = $this->filepath;
5200          else
5201              $filepath = $this->cacheDir . '/' . \str_replace('\\', '_', $this->lastClassName) . '.php';
5202          \file_put_contents($filepath, "<?php\n" . $php);
5203          $this->lastFilepath = \realpath($filepath);
5204          if (!\class_exists($this->lastClassName, \false))
5205              include $filepath;
5206          return new $this->lastClassName;
5207      }
5208  	public function generate(Rendering $rendering)
5209      {
5210          $this->serializer->useMultibyteStringFunctions = $this->useMultibyteStringFunctions;
5211          $compiledTemplates = \array_map([$this, 'compileTemplate'], $rendering->getTemplates());
5212          $php = [];
5213          $php[] = ' extends \\s9e\\TextFormatter\\Renderers\\PHP';
5214          $php[] = '{';
5215          $php[] = '    protected $params=' . self::export($rendering->getAllParameters()) . ';';
5216          $php[] = '    protected function renderNode(\\DOMNode $node)';
5217          $php[] = '    {';
5218          $php[] = '        ' . SwitchStatement::generate('$node->nodeName', $compiledTemplates, '$this->at($node);');
5219          $php[] = '    }';
5220          if ($this->enableQuickRenderer)
5221              $php[] = Quick::getSource($compiledTemplates);
5222          $php[] = '}';
5223          $php = \implode("\n", $php);
5224          if (isset($this->controlStructuresOptimizer))
5225              $php = $this->controlStructuresOptimizer->optimize($php);
5226          $className = (isset($this->className))
5227                     ? $this->className
5228                     : $this->defaultClassPrefix . \sha1($php);
5229          $this->lastClassName = $className;
5230          $header = "\n/**\n* @package   s9e\TextFormatter\n* @copyright Copyright (c) 2010-2019 The s9e Authors\n* @license   http://www.opensource.org/licenses/mit-license.php The MIT License\n*/\n";
5231          $pos = \strrpos($className, '\\');
5232          if ($pos !== \false)
5233          {
5234              $header .= 'namespace ' . \substr($className, 0, $pos) . ";\n\n";
5235              $className = \substr($className, 1 + $pos);
5236          }
5237          $php = $header . 'class ' . $className . $php;
5238          return $php;
5239      }
5240  	protected static function export(array $value)
5241      {
5242          $pairs = [];
5243          foreach ($value as $k => $v)
5244              $pairs[] = \var_export($k, \true) . '=>' . \var_export($v, \true);
5245          return '[' . \implode(',', $pairs) . ']';
5246      }
5247  	protected function compileTemplate($template)
5248      {
5249          $template = $this->normalizer->normalizeTemplate($template);
5250          $ir = TemplateParser::parse($template);
5251          $php = $this->serializer->serialize($ir->documentElement);
5252          if (isset($this->optimizer))
5253              $php = $this->optimizer->optimize($php);
5254          return $php;
5255      }
5256  }
5257  
5258  /*
5259  * @package   s9e\TextFormatter
5260  * @copyright Copyright (c) 2010-2019 The s9e Authors
5261  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5262  */
5263  namespace s9e\TextFormatter\Configurator\RendererGenerators\PHP;
5264  class ControlStructuresOptimizer extends AbstractOptimizer
5265  {
5266      protected $braces;
5267      protected $context;
5268  	protected function blockEndsWithIf()
5269      {
5270          return \in_array($this->context['lastBlock'], [\T_IF, \T_ELSEIF], \true);
5271      }
5272  	protected function isControlStructure()
5273      {
5274          return \in_array(
5275              $this->tokens[$this->i][0],
5276              [\T_ELSE, \T_ELSEIF, \T_FOR, \T_FOREACH, \T_IF, \T_WHILE],
5277              \true
5278          );
5279      }
5280  	protected function isFollowedByElse()
5281      {
5282          if ($this->i > $this->cnt - 4)
5283              return \false;
5284          $k = $this->i + 1;
5285          if ($this->tokens[$k][0] === \T_WHITESPACE)
5286              ++$k;
5287          return \in_array($this->tokens[$k][0], [\T_ELSEIF, \T_ELSE], \true);
5288      }
5289  	protected function mustPreserveBraces()
5290      {
5291          return ($this->blockEndsWithIf() && $this->isFollowedByElse());
5292      }
5293  	protected function optimizeTokens()
5294      {
5295          while (++$this->i < $this->cnt)
5296              if ($this->tokens[$this->i] === ';')
5297                  ++$this->context['statements'];
5298              elseif ($this->tokens[$this->i] === '{')
5299                  ++$this->braces;
5300              elseif ($this->tokens[$this->i] === '}')
5301              {
5302                  if ($this->context['braces'] === $this->braces)
5303                      $this->processEndOfBlock();
5304                  --$this->braces;
5305              }
5306              elseif ($this->isControlStructure())
5307                  $this->processControlStructure();
5308      }
5309  	protected function processControlStructure()
5310      {
5311          $savedIndex = $this->i;
5312          if (!\in_array($this->tokens[$this->i][0], [\T_ELSE, \T_ELSEIF], \true))
5313              ++$this->context['statements'];
5314          if ($this->tokens[$this->i][0] !== \T_ELSE)
5315              $this->skipCondition();
5316          $this->skipWhitespace();
5317          if ($this->tokens[$this->i] !== '{')
5318          {
5319              $this->i = $savedIndex;
5320              return;
5321          }
5322          ++$this->braces;
5323          $replacement = [\T_WHITESPACE, ''];
5324          if ($this->tokens[$savedIndex][0]  === \T_ELSE
5325           && $this->tokens[$this->i + 1][0] !== \T_VARIABLE
5326           && $this->tokens[$this->i + 1][0] !== \T_WHITESPACE)
5327              $replacement = [\T_WHITESPACE, ' '];
5328          $this->context['lastBlock'] = $this->tokens[$savedIndex][0];
5329          $this->context = [
5330              'braces'      => $this->braces,
5331              'index'       => $this->i,
5332              'lastBlock'   => \null,
5333              'parent'      => $this->context,
5334              'replacement' => $replacement,
5335              'savedIndex'  => $savedIndex,
5336              'statements'  => 0
5337          ];
5338      }
5339  	protected function processEndOfBlock()
5340      {
5341          if ($this->context['statements'] < 2 && !$this->mustPreserveBraces())
5342              $this->removeBracesInCurrentContext();
5343          $this->context = $this->context['parent'];
5344          $this->context['parent']['lastBlock'] = $this->context['lastBlock'];
5345      }
5346  	protected function removeBracesInCurrentContext()
5347      {
5348          $this->tokens[$this->context['index']] = $this->context['replacement'];
5349          $this->tokens[$this->i] = ($this->context['statements']) ? [\T_WHITESPACE, ''] : ';';
5350          foreach ([$this->context['index'] - 1, $this->i - 1] as $tokenIndex)
5351              if ($this->tokens[$tokenIndex][0] === \T_WHITESPACE)
5352                  $this->tokens[$tokenIndex][1] = '';
5353          if ($this->tokens[$this->context['savedIndex']][0] === \T_ELSE)
5354          {
5355              $j = 1 + $this->context['savedIndex'];
5356              while ($this->tokens[$j][0] === \T_WHITESPACE
5357                  || $this->tokens[$j][0] === \T_COMMENT
5358                  || $this->tokens[$j][0] === \T_DOC_COMMENT)
5359                  ++$j;
5360              if ($this->tokens[$j][0] === \T_IF)
5361              {
5362                  $this->tokens[$j] = [\T_ELSEIF, 'elseif'];
5363                  $j = $this->context['savedIndex'];
5364                  $this->tokens[$j] = [\T_WHITESPACE, ''];
5365                  if ($this->tokens[$j - 1][0] === \T_WHITESPACE)
5366                      $this->tokens[$j - 1][1] = '';
5367                  $this->unindentBlock($j, $this->i - 1);
5368                  $this->tokens[$this->context['index']] = [\T_WHITESPACE, ''];
5369              }
5370          }
5371          $this->changed = \true;
5372      }
5373  	protected function reset($php)
5374      {
5375          parent::reset($php);
5376          $this->braces  = 0;
5377          $this->context = [
5378              'braces'      => 0,
5379              'index'       => -1,
5380              'parent'      => [],
5381              'preventElse' => \false,
5382              'savedIndex'  => 0,
5383              'statements'  => 0
5384          ];
5385      }
5386  	protected function skipCondition()
5387      {
5388          $this->skipToString('(');
5389          $parens = 0;
5390          while (++$this->i < $this->cnt)
5391              if ($this->tokens[$this->i] === ')')
5392                  if ($parens)
5393                      --$parens;
5394                  else
5395                      break;
5396              elseif ($this->tokens[$this->i] === '(')
5397                  ++$parens;
5398      }
5399  }
5400  
5401  /*
5402  * @package   s9e\TextFormatter
5403  * @copyright Copyright (c) 2010-2019 The s9e Authors
5404  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5405  */
5406  namespace s9e\TextFormatter\Configurator;
5407  use ReflectionClass;
5408  use RuntimeException;
5409  use s9e\TextFormatter\Configurator;
5410  use s9e\TextFormatter\Configurator\Collections\TemplateParameterCollection;
5411  use s9e\TextFormatter\Configurator\RendererGenerator;
5412  use s9e\TextFormatter\Configurator\Traits\Configurable;
5413  class Rendering
5414  {
5415      use Configurable;
5416      protected $configurator;
5417      protected $engine;
5418      protected $parameters;
5419  	public function __construct(Configurator $configurator)
5420      {
5421          $this->configurator = $configurator;
5422          $this->parameters   = new TemplateParameterCollection;
5423      }
5424  	public function getAllParameters()
5425      {
5426          $params = [];
5427          foreach ($this->configurator->tags as $tag)
5428              if (isset($tag->template))
5429                  foreach ($tag->template->getParameters() as $paramName)
5430                      $params[$paramName] = '';
5431          $params = \iterator_to_array($this->parameters) + $params;
5432          \ksort($params);
5433          return $params;
5434      }
5435  	public function getEngine()
5436      {
5437          if (!isset($this->engine))
5438              $this->setEngine('XSLT');
5439          return $this->engine;
5440      }
5441  	public function getRenderer()
5442      {
5443          return $this->getEngine()->getRenderer($this);
5444      }
5445  	public function getTemplates()
5446      {
5447          $templates = [
5448              'br' => '<br/>',
5449              'e'  => '',
5450              'i'  => '',
5451              'p'  => '<p><xsl:apply-templates/></p>',
5452              's'  => ''
5453          ];
5454          foreach ($this->configurator->tags as $tagName => $tag)
5455              if (isset($tag->template))
5456                  $templates[$tagName] = (string) $tag->template;
5457          \ksort($templates);
5458          return $templates;
5459      }
5460  	public function setEngine($engine)
5461      {
5462          if (!($engine instanceof RendererGenerator))
5463          {
5464              $className  = 's9e\\TextFormatter\\Configurator\\RendererGenerators\\' . $engine;
5465              $reflection = new ReflectionClass($className);
5466              $engine = $reflection->newInstanceArgs(\array_slice(\func_get_args(), 1));
5467          }
5468          $this->engine = $engine;
5469          return $engine;
5470      }
5471  }
5472  
5473  /*
5474  * @package   s9e\TextFormatter
5475  * @copyright Copyright (c) 2010-2019 The s9e Authors
5476  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5477  */
5478  namespace s9e\TextFormatter\Configurator;
5479  use ArrayAccess;
5480  use DOMDocument;
5481  use Iterator;
5482  use s9e\TextFormatter\Configurator\Collections\RulesGeneratorList;
5483  use s9e\TextFormatter\Configurator\Collections\TagCollection;
5484  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5485  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5486  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\TargetedRulesGenerator;
5487  use s9e\TextFormatter\Configurator\Traits\CollectionProxy;
5488  class RulesGenerator implements ArrayAccess, Iterator
5489  {
5490      use CollectionProxy;
5491      protected $collection;
5492  	public function __construct()
5493      {
5494          $this->collection = new RulesGeneratorList;
5495          $this->collection->append('AutoCloseIfVoid');
5496          $this->collection->append('AutoReopenFormattingElements');
5497          $this->collection->append('BlockElementsCloseFormattingElements');
5498          $this->collection->append('BlockElementsFosterFormattingElements');
5499          $this->collection->append('DisableAutoLineBreaksIfNewLinesArePreserved');
5500          $this->collection->append('EnforceContentModels');
5501          $this->collection->append('EnforceOptionalEndTags');
5502          $this->collection->append('IgnoreTagsInCode');
5503          $this->collection->append('IgnoreTextIfDisallowed');
5504          $this->collection->append('IgnoreWhitespaceAroundBlockElements');
5505          $this->collection->append('TrimFirstLineInCodeBlocks');
5506      }
5507  	public function getRules(TagCollection $tags)
5508      {
5509          $tagInspectors = $this->getTagInspectors($tags);
5510          return [
5511              'root' => $this->generateRootRules($tagInspectors),
5512              'tags' => $this->generateTagRules($tagInspectors)
5513          ];
5514      }
5515  	protected function generateTagRules(array $tagInspectors)
5516      {
5517          $rules = [];
5518          foreach ($tagInspectors as $tagName => $tagInspector)
5519              $rules[$tagName] = $this->generateRuleset($tagInspector, $tagInspectors);
5520          return $rules;
5521      }
5522  	protected function generateRootRules(array $tagInspectors)
5523      {
5524          $rootInspector = new TemplateInspector('<div><xsl:apply-templates/></div>');
5525          $rules         = $this->generateRuleset($rootInspector, $tagInspectors);
5526          unset($rules['autoClose']);
5527          unset($rules['autoReopen']);
5528          unset($rules['breakParagraph']);
5529          unset($rules['closeAncestor']);
5530          unset($rules['closeParent']);
5531          unset($rules['fosterParent']);
5532          unset($rules['ignoreSurroundingWhitespace']);
5533          unset($rules['isTransparent']);
5534          unset($rules['requireAncestor']);
5535          unset($rules['requireParent']);
5536          return $rules;
5537      }
5538  	protected function generateRuleset(TemplateInspector $srcInspector, array $trgInspectors)
5539      {
5540          $rules = [];
5541          foreach ($this->collection as $rulesGenerator)
5542          {
5543              if ($rulesGenerator instanceof BooleanRulesGenerator)
5544                  foreach ($rulesGenerator->generateBooleanRules($srcInspector) as $ruleName => $bool)
5545                      $rules[$ruleName] = $bool;
5546              if ($rulesGenerator instanceof TargetedRulesGenerator)
5547                  foreach ($trgInspectors as $tagName => $trgInspector)
5548                  {
5549                      $targetedRules = $rulesGenerator->generateTargetedRules($srcInspector, $trgInspector);
5550                      foreach ($targetedRules as $ruleName)
5551                          $rules[$ruleName][] = $tagName;
5552                  }
5553          }
5554          return $rules;
5555      }
5556  	protected function getTagInspectors(TagCollection $tags)
5557      {
5558          $tagInspectors = [];
5559          foreach ($tags as $tagName => $tag)
5560          {
5561              $template = (isset($tag->template)) ? $tag->template : '<xsl:apply-templates/>';
5562              $tagInspectors[$tagName] = new TemplateInspector($template);
5563          }
5564          return $tagInspectors;
5565      }
5566  }
5567  
5568  /*
5569  * @package   s9e\TextFormatter
5570  * @copyright Copyright (c) 2010-2019 The s9e Authors
5571  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5572  */
5573  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5574  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5575  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5576  class AutoCloseIfVoid implements BooleanRulesGenerator
5577  {
5578  	public function generateBooleanRules(TemplateInspector $src)
5579      {
5580          return ($src->isVoid()) ? ['autoClose' => \true] : [];
5581      }
5582  }
5583  
5584  /*
5585  * @package   s9e\TextFormatter
5586  * @copyright Copyright (c) 2010-2019 The s9e Authors
5587  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5588  */
5589  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5590  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5591  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5592  class AutoReopenFormattingElements implements BooleanRulesGenerator
5593  {
5594  	public function generateBooleanRules(TemplateInspector $src)
5595      {
5596          return ($src->isFormattingElement()) ? ['autoReopen' => \true] : [];
5597      }
5598  }
5599  
5600  /*
5601  * @package   s9e\TextFormatter
5602  * @copyright Copyright (c) 2010-2019 The s9e Authors
5603  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5604  */
5605  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5606  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5607  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\TargetedRulesGenerator;
5608  class BlockElementsCloseFormattingElements implements TargetedRulesGenerator
5609  {
5610  	public function generateTargetedRules(TemplateInspector $src, TemplateInspector $trg)
5611      {
5612          return ($src->isBlock() && $trg->isFormattingElement()) ? ['closeParent'] : [];
5613      }
5614  }
5615  
5616  /*
5617  * @package   s9e\TextFormatter
5618  * @copyright Copyright (c) 2010-2019 The s9e Authors
5619  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5620  */
5621  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5622  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5623  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\TargetedRulesGenerator;
5624  class BlockElementsFosterFormattingElements implements TargetedRulesGenerator
5625  {
5626  	public function generateTargetedRules(TemplateInspector $src, TemplateInspector $trg)
5627      {
5628          return ($src->isBlock() && $src->isPassthrough() && $trg->isFormattingElement()) ? ['fosterParent'] : [];
5629      }
5630  }
5631  
5632  /*
5633  * @package   s9e\TextFormatter
5634  * @copyright Copyright (c) 2010-2019 The s9e Authors
5635  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5636  */
5637  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5638  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5639  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5640  class DisableAutoLineBreaksIfNewLinesArePreserved implements BooleanRulesGenerator
5641  {
5642  	public function generateBooleanRules(TemplateInspector $src)
5643      {
5644          return ($src->preservesNewLines()) ? ['disableAutoLineBreaks' => \true] : [];
5645      }
5646  }
5647  
5648  /*
5649  * @package   s9e\TextFormatter
5650  * @copyright Copyright (c) 2010-2019 The s9e Authors
5651  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5652  */
5653  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5654  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5655  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5656  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\TargetedRulesGenerator;
5657  class EnforceContentModels implements BooleanRulesGenerator, TargetedRulesGenerator
5658  {
5659      protected $br;
5660      protected $span;
5661  	public function __construct()
5662      {
5663          $this->br   = new TemplateInspector('<br/>');
5664          $this->span = new TemplateInspector('<span><xsl:apply-templates/></span>');
5665      }
5666  	public function generateBooleanRules(TemplateInspector $src)
5667      {
5668          $rules = [];
5669          if ($src->isTransparent())
5670              $rules['isTransparent'] = \true;
5671          if (!$src->allowsChild($this->br))
5672          {
5673              $rules['preventLineBreaks'] = \true;
5674              $rules['suspendAutoLineBreaks'] = \true;
5675          }
5676          if (!$src->allowsDescendant($this->br))
5677          {
5678              $rules['disableAutoLineBreaks'] = \true;
5679              $rules['preventLineBreaks'] = \true;
5680          }
5681          return $rules;
5682      }
5683  	public function generateTargetedRules(TemplateInspector $src, TemplateInspector $trg)
5684      {
5685          $rules = [];
5686          if ($src->allowsChild($trg))
5687              $rules[] = 'allowChild';
5688          if ($src->allowsDescendant($trg))
5689              $rules[] = 'allowDescendant';
5690          return $rules;
5691      }
5692  }
5693  
5694  /*
5695  * @package   s9e\TextFormatter
5696  * @copyright Copyright (c) 2010-2019 The s9e Authors
5697  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5698  */
5699  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5700  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5701  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\TargetedRulesGenerator;
5702  class EnforceOptionalEndTags implements TargetedRulesGenerator
5703  {
5704  	public function generateTargetedRules(TemplateInspector $src, TemplateInspector $trg)
5705      {
5706          return ($src->closesParent($trg)) ? ['closeParent'] : [];
5707      }
5708  }
5709  
5710  /*
5711  * @package   s9e\TextFormatter
5712  * @copyright Copyright (c) 2010-2019 The s9e Authors
5713  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5714  */
5715  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5716  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5717  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5718  class IgnoreTagsInCode implements BooleanRulesGenerator
5719  {
5720  	public function generateBooleanRules(TemplateInspector $src)
5721      {
5722          return ($src->evaluate('count(//code//xsl:apply-templates)')) ? ['ignoreTags' => \true] : [];
5723      }
5724  }
5725  
5726  /*
5727  * @package   s9e\TextFormatter
5728  * @copyright Copyright (c) 2010-2019 The s9e Authors
5729  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5730  */
5731  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5732  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5733  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5734  class IgnoreTextIfDisallowed implements BooleanRulesGenerator
5735  {
5736  	public function generateBooleanRules(TemplateInspector $src)
5737      {
5738          return ($src->allowsText()) ? [] : ['ignoreText' => \true];
5739      }
5740  }
5741  
5742  /*
5743  * @package   s9e\TextFormatter
5744  * @copyright Copyright (c) 2010-2019 The s9e Authors
5745  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5746  */
5747  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5748  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5749  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5750  class IgnoreWhitespaceAroundBlockElements implements BooleanRulesGenerator
5751  {
5752  	public function generateBooleanRules(TemplateInspector $src)
5753      {
5754          return ($src->isBlock()) ? ['ignoreSurroundingWhitespace' => \true] : [];
5755      }
5756  }
5757  
5758  /*
5759  * @package   s9e\TextFormatter
5760  * @copyright Copyright (c) 2010-2019 The s9e Authors
5761  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5762  */
5763  namespace s9e\TextFormatter\Configurator\RulesGenerators;
5764  use s9e\TextFormatter\Configurator\Helpers\TemplateInspector;
5765  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
5766  class TrimFirstLineInCodeBlocks implements BooleanRulesGenerator
5767  {
5768  	public function generateBooleanRules(TemplateInspector $src)
5769      {
5770          return ($src->evaluate('count(//pre//code//xsl:apply-templates)')) ? ['trimFirstLine' => \true] : [];
5771      }
5772  }
5773  
5774  /*
5775  * @package   s9e\TextFormatter
5776  * @copyright Copyright (c) 2010-2019 The s9e Authors
5777  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5778  */
5779  namespace s9e\TextFormatter\Configurator;
5780  use ArrayAccess;
5781  use Iterator;
5782  use s9e\TextFormatter\Configurator\Collections\TemplateCheckList;
5783  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
5784  use s9e\TextFormatter\Configurator\Items\Tag;
5785  use s9e\TextFormatter\Configurator\Items\UnsafeTemplate;
5786  use s9e\TextFormatter\Configurator\TemplateChecks\DisallowElementNS;
5787  use s9e\TextFormatter\Configurator\TemplateChecks\DisallowXPathFunction;
5788  use s9e\TextFormatter\Configurator\TemplateChecks\RestrictFlashScriptAccess;
5789  use s9e\TextFormatter\Configurator\Traits\CollectionProxy;
5790  class TemplateChecker implements ArrayAccess, Iterator
5791  {
5792      use CollectionProxy;
5793      protected $collection;
5794      protected $disabled = \false;
5795  	public function __construct()
5796      {
5797          $this->collection = new TemplateCheckList;
5798          $this->collection->append('DisallowAttributeSets');
5799          $this->collection->append('DisallowCopy');
5800          $this->collection->append('DisallowDisableOutputEscaping');
5801          $this->collection->append('DisallowDynamicAttributeNames');
5802          $this->collection->append('DisallowDynamicElementNames');
5803          $this->collection->append('DisallowObjectParamsWithGeneratedName');
5804          $this->collection->append('DisallowPHPTags');
5805          $this->collection->append('DisallowUnsafeCopyOf');
5806          $this->collection->append('DisallowUnsafeDynamicCSS');
5807          $this->collection->append('DisallowUnsafeDynamicJS');
5808          $this->collection->append('DisallowUnsafeDynamicURL');
5809          $this->collection->append(new DisallowElementNS('http://icl.com/saxon', 'output'));
5810          $this->collection->append(new DisallowXPathFunction('document'));
5811          $this->collection->append(new RestrictFlashScriptAccess('sameDomain', \true));
5812      }
5813  	public function checkTag(Tag $tag)
5814      {
5815          if (isset($tag->template) && !($tag->template instanceof UnsafeTemplate))
5816          {
5817              $template = (string) $tag->template;
5818              $this->checkTemplate($template, $tag);
5819          }
5820      }
5821  	public function checkTemplate($template, Tag $tag = \null)
5822      {
5823          if ($this->disabled)
5824              return;
5825          if (!isset($tag))
5826              $tag = new Tag;
5827          $dom = TemplateHelper::loadTemplate($template);
5828          foreach ($this->collection as $check)
5829              $check->check($dom->documentElement, $tag);
5830      }
5831  	public function disable()
5832      {
5833          $this->disabled = \true;
5834      }
5835  	public function enable()
5836      {
5837          $this->disabled = \false;
5838      }
5839  }
5840  
5841  /*
5842  * @package   s9e\TextFormatter
5843  * @copyright Copyright (c) 2010-2019 The s9e Authors
5844  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5845  */
5846  namespace s9e\TextFormatter\Configurator\TemplateChecks;
5847  use DOMAttr;
5848  use DOMElement;
5849  use DOMNode;
5850  use DOMXPath;
5851  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
5852  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
5853  use s9e\TextFormatter\Configurator\Items\Attribute;
5854  use s9e\TextFormatter\Configurator\Items\Tag;
5855  use s9e\TextFormatter\Configurator\TemplateCheck;
5856  abstract class AbstractDynamicContentCheck extends TemplateCheck
5857  {
5858      protected $ignoreUnknownAttributes = \false;
5859      abstract protected function getNodes(DOMElement $template);
5860      abstract protected function isSafe(Attribute $attribute);
5861  	public function check(DOMElement $template, Tag $tag)
5862      {
5863          foreach ($this->getNodes($template) as $node)
5864              $this->checkNode($node, $tag);
5865      }
5866  	public function detectUnknownAttributes()
5867      {
5868          $this->ignoreUnknownAttributes = \false;
5869      }
5870  	public function ignoreUnknownAttributes()
5871      {
5872          $this->ignoreUnknownAttributes = \true;
5873      }
5874  	protected function checkAttribute(DOMNode $node, Tag $tag, $attrName)
5875      {
5876          if (!isset($tag->attributes[$attrName]))
5877          {
5878              if ($this->ignoreUnknownAttributes)
5879                  return;
5880              throw new UnsafeTemplateException("Cannot assess the safety of unknown attribute '" . $attrName . "'", $node);
5881          }
5882          if (!$this->tagFiltersAttributes($tag) || !$this->isSafe($tag->attributes[$attrName]))
5883              throw new UnsafeTemplateException("Attribute '" . $attrName . "' is not properly sanitized to be used in this context", $node);
5884      }
5885  	protected function checkAttributeExpression(DOMNode $node, Tag $tag, $expr)
5886      {
5887          \preg_match_all('(@([-\\w]+))', $expr, $matches);
5888          foreach ($matches[1] as $attrName)
5889              $this->checkAttribute($node, $tag, $attrName);
5890      }
5891  	protected function checkAttributeNode(DOMAttr $attribute, Tag $tag)
5892      {
5893          foreach (AVTHelper::parse($attribute->value) as $token)
5894              if ($token[0] === 'expression')
5895                  $this->checkExpression($attribute, $token[1], $tag);
5896      }
5897  	protected function checkContext(DOMNode $node)
5898      {
5899          $xpath     = new DOMXPath($node->ownerDocument);
5900          $ancestors = $xpath->query('ancestor::xsl:for-each', $node);
5901          if ($ancestors->length)
5902              throw new UnsafeTemplateException("Cannot assess context due to '" . $ancestors->item(0)->nodeName . "'", $node);
5903      }
5904  	protected function checkCopyOfNode(DOMElement $node, Tag $tag)
5905      {
5906          $this->checkSelectNode($node->getAttributeNode('select'), $tag);
5907      }
5908  	protected function checkElementNode(DOMElement $element, Tag $tag)
5909      {
5910          $xpath = new DOMXPath($element->ownerDocument);
5911          $predicate = ($element->localName === 'attribute') ? '' : '[not(ancestor::xsl:attribute)]';
5912          $query = './/xsl:value-of' . $predicate;
5913          foreach ($xpath->query($query, $element) as $valueOf)
5914              $this->checkSelectNode($valueOf->getAttributeNode('select'), $tag);
5915          $query = './/xsl:apply-templates' . $predicate;
5916          foreach ($xpath->query($query, $element) as $applyTemplates)
5917              throw new UnsafeTemplateException('Cannot allow unfiltered data in this context', $applyTemplates);
5918      }
5919  	protected function checkExpression(DOMNode $node, $expr, Tag $tag)
5920      {
5921          $this->checkContext($node);
5922          if (\preg_match('/^\\$(\\w+)$/', $expr, $m))
5923              $this->checkVariable($node, $tag, $m[1]);
5924          elseif (\preg_match('/^@[-\\w]+(?:\\s*\\|\\s*@[-\\w]+)*$/', $expr))
5925              $this->checkAttributeExpression($node, $tag, $expr);
5926          elseif (!$this->isExpressionSafe($expr))
5927              throw new UnsafeTemplateException("Cannot assess the safety of expression '" . $expr . "'", $node);
5928      }
5929  	protected function checkNode(DOMNode $node, Tag $tag)
5930      {
5931          if ($node instanceof DOMAttr)
5932              $this->checkAttributeNode($node, $tag);
5933          elseif ($node instanceof DOMElement)
5934              if ($node->namespaceURI === self::XMLNS_XSL && $node->localName === 'copy-of')
5935                  $this->checkCopyOfNode($node, $tag);
5936              else
5937                  $this->checkElementNode($node, $tag);
5938      }
5939  	protected function checkVariable(DOMNode $node, $tag, $qname)
5940      {
5941          $this->checkVariableDeclaration($node, $tag, 'xsl:param[@name="' . $qname . '"]');
5942          $this->checkVariableDeclaration($node, $tag, 'xsl:variable[@name="' . $qname . '"]');
5943      }
5944  	protected function checkVariableDeclaration(DOMNode $node, $tag, $query)
5945      {
5946          $query = 'ancestor-or-self::*/preceding-sibling::' . $query . '[@select]';
5947          $xpath = new DOMXPath($node->ownerDocument);
5948          foreach ($xpath->query($query, $node) as $varNode)
5949          {
5950              try
5951              {
5952                  $this->checkExpression($varNode, $varNode->getAttribute('select'), $tag);
5953              }
5954              catch (UnsafeTemplateException $e)
5955              {
5956                  $e->setNode($node);
5957                  throw $e;
5958              }
5959          }
5960      }
5961  	protected function checkSelectNode(DOMAttr $select, Tag $tag)
5962      {
5963          $this->checkExpression($select, $select->value, $tag);
5964      }
5965  	protected function isExpressionSafe($expr)
5966      {
5967          return \false;
5968      }
5969  	protected function tagFiltersAttributes(Tag $tag)
5970      {
5971          return $tag->filterChain->containsCallback('s9e\\TextFormatter\\Parser\\FilterProcessing::filterAttributes');
5972      }
5973  }
5974  
5975  /*
5976  * @package   s9e\TextFormatter
5977  * @copyright Copyright (c) 2010-2019 The s9e Authors
5978  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
5979  */
5980  namespace s9e\TextFormatter\Configurator\TemplateChecks;
5981  use DOMElement;
5982  use DOMNode;
5983  use DOMXPath;
5984  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
5985  use s9e\TextFormatter\Configurator\Items\Tag;
5986  use s9e\TextFormatter\Configurator\TemplateCheck;
5987  abstract class AbstractFlashRestriction extends TemplateCheck
5988  {
5989      public $defaultSetting;
5990      public $maxSetting;
5991      public $onlyIfDynamic;
5992      protected $settingName;
5993      protected $settings;
5994      protected $template;
5995  	public function __construct($maxSetting, $onlyIfDynamic = \false)
5996      {
5997          $this->maxSetting    = $maxSetting;
5998          $this->onlyIfDynamic = $onlyIfDynamic;
5999      }
6000  	public function check(DOMElement $template, Tag $tag)
6001      {
6002          $this->template = $template;
6003          $this->checkEmbeds();
6004          $this->checkObjects();
6005      }
6006  	protected function checkAttributes(DOMElement $embed)
6007      {
6008          $settingName = \strtolower($this->settingName);
6009          $useDefault  = \true;
6010          foreach ($embed->attributes as $attribute)
6011          {
6012              $attrName = \strtolower($attribute->name);
6013              if ($attrName === $settingName)
6014              {
6015                  $this->checkSetting($attribute, $attribute->value);
6016                  $useDefault = \false;
6017              }
6018          }
6019          if ($useDefault)
6020              $this->checkSetting($embed, $this->defaultSetting);
6021      }
6022  	protected function checkDynamicAttributes(DOMElement $embed)
6023      {
6024          $settingName = \strtolower($this->settingName);
6025          foreach ($embed->getElementsByTagNameNS(self::XMLNS_XSL, 'attribute') as $attribute)
6026          {
6027              $attrName = \strtolower($attribute->getAttribute('name'));
6028              if ($attrName === $settingName)
6029                  throw new UnsafeTemplateException('Cannot assess the safety of dynamic attributes', $attribute);
6030          }
6031      }
6032  	protected function checkDynamicParams(DOMElement $object)
6033      {
6034          foreach ($this->getObjectParams($object) as $param)
6035              foreach ($param->getElementsByTagNameNS(self::XMLNS_XSL, 'attribute') as $attribute)
6036                  if (\strtolower($attribute->getAttribute('name')) === 'value')
6037                      throw new UnsafeTemplateException('Cannot assess the safety of dynamic attributes', $attribute);
6038      }
6039  	protected function checkEmbeds()
6040      {
6041          foreach ($this->getElements('embed') as $embed)
6042          {
6043              $this->checkDynamicAttributes($embed);
6044              $this->checkAttributes($embed);
6045          }
6046      }
6047  	protected function checkObjects()
6048      {
6049          foreach ($this->getElements('object') as $object)
6050          {
6051              $this->checkDynamicParams($object);
6052              $params = $this->getObjectParams($object);
6053              foreach ($params as $param)
6054                  $this->checkSetting($param, $param->getAttribute('value'));
6055              if (empty($params))
6056                  $this->checkSetting($object, $this->defaultSetting);
6057          }
6058      }
6059  	protected function checkSetting(DOMNode $node, $setting)
6060      {
6061          if (!isset($this->settings[\strtolower($setting)]))
6062          {
6063              if (\preg_match('/(?<!\\{)\\{(?:\\{\\{)*(?!\\{)/', $setting))
6064                  throw new UnsafeTemplateException('Cannot assess ' . $this->settingName . " setting '" . $setting . "'", $node);
6065              throw new UnsafeTemplateException('Unknown ' . $this->settingName . " value '" . $setting . "'", $node);
6066          }
6067          $value    = $this->settings[\strtolower($setting)];
6068          $maxValue = $this->settings[\strtolower($this->maxSetting)];
6069          if ($value > $maxValue)
6070              throw new UnsafeTemplateException($this->settingName . " setting '" . $setting . "' exceeds restricted value '" . $this->maxSetting . "'", $node);
6071      }
6072  	protected function isDynamic(DOMElement $node)
6073      {
6074          if ($node->getElementsByTagNameNS(self::XMLNS_XSL, '*')->length)
6075              return \true;
6076          $xpath = new DOMXPath($node->ownerDocument);
6077          $query = './/@*[contains(., "{")]';
6078          foreach ($xpath->query($query, $node) as $attribute)
6079              if (\preg_match('/(?<!\\{)\\{(?:\\{\\{)*(?!\\{)/', $attribute->value))
6080                  return \true;
6081          return \false;
6082      }
6083  	protected function getElements($tagName)
6084      {
6085          $nodes = [];
6086          foreach ($this->template->ownerDocument->getElementsByTagName($tagName) as $node)
6087              if (!$this->onlyIfDynamic || $this->isDynamic($node))
6088                  $nodes[] = $node;
6089          return $nodes;
6090      }
6091  	protected function getObjectParams(DOMElement $object)
6092      {
6093          $params      = [];
6094          $settingName = \strtolower($this->settingName);
6095          foreach ($object->getElementsByTagName('param') as $param)
6096          {
6097              $paramName = \strtolower($param->getAttribute('name'));
6098              if ($paramName === $settingName && $param->parentNode->isSameNode($object))
6099                  $params[] = $param;
6100          }
6101          return $params;
6102      }
6103  }
6104  
6105  /*
6106  * @package   s9e\TextFormatter
6107  * @copyright Copyright (c) 2010-2019 The s9e Authors
6108  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6109  */
6110  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6111  use DOMElement;
6112  use DOMXPath;
6113  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6114  use s9e\TextFormatter\Configurator\Items\Tag;
6115  use s9e\TextFormatter\Configurator\TemplateCheck;
6116  class DisallowAttributeSets extends TemplateCheck
6117  {
6118  	public function check(DOMElement $template, Tag $tag)
6119      {
6120          $xpath = new DOMXPath($template->ownerDocument);
6121          $nodes = $xpath->query('//@use-attribute-sets');
6122          if ($nodes->length)
6123              throw new UnsafeTemplateException('Cannot assess the safety of attribute sets', $nodes->item(0));
6124      }
6125  }
6126  
6127  /*
6128  * @package   s9e\TextFormatter
6129  * @copyright Copyright (c) 2010-2019 The s9e Authors
6130  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6131  */
6132  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6133  use DOMElement;
6134  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6135  use s9e\TextFormatter\Configurator\Items\Tag;
6136  use s9e\TextFormatter\Configurator\TemplateCheck;
6137  class DisallowCopy extends TemplateCheck
6138  {
6139  	public function check(DOMElement $template, Tag $tag)
6140      {
6141          $nodes = $template->getElementsByTagNameNS(self::XMLNS_XSL, 'copy');
6142          $node  = $nodes->item(0);
6143          if ($node)
6144              throw new UnsafeTemplateException("Cannot assess the safety of an '" . $node->nodeName . "' element", $node);
6145      }
6146  }
6147  
6148  /*
6149  * @package   s9e\TextFormatter
6150  * @copyright Copyright (c) 2010-2019 The s9e Authors
6151  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6152  */
6153  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6154  use DOMElement;
6155  use DOMXPath;
6156  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6157  use s9e\TextFormatter\Configurator\Items\Tag;
6158  use s9e\TextFormatter\Configurator\TemplateCheck;
6159  class DisallowDisableOutputEscaping extends TemplateCheck
6160  {
6161  	public function check(DOMElement $template, Tag $tag)
6162      {
6163          $xpath = new DOMXPath($template->ownerDocument);
6164          $node  = $xpath->query('//@disable-output-escaping')->item(0);
6165          if ($node)
6166              throw new UnsafeTemplateException("The template contains a 'disable-output-escaping' attribute", $node);
6167      }
6168  }
6169  
6170  /*
6171  * @package   s9e\TextFormatter
6172  * @copyright Copyright (c) 2010-2019 The s9e Authors
6173  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6174  */
6175  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6176  use DOMElement;
6177  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6178  use s9e\TextFormatter\Configurator\Items\Tag;
6179  use s9e\TextFormatter\Configurator\TemplateCheck;
6180  class DisallowDynamicAttributeNames extends TemplateCheck
6181  {
6182  	public function check(DOMElement $template, Tag $tag)
6183      {
6184          $nodes = $template->getElementsByTagNameNS(self::XMLNS_XSL, 'attribute');
6185          foreach ($nodes as $node)
6186              if (\strpos($node->getAttribute('name'), '{') !== \false)
6187                  throw new UnsafeTemplateException('Dynamic <xsl:attribute/> names are disallowed', $node);
6188      }
6189  }
6190  
6191  /*
6192  * @package   s9e\TextFormatter
6193  * @copyright Copyright (c) 2010-2019 The s9e Authors
6194  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6195  */
6196  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6197  use DOMElement;
6198  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6199  use s9e\TextFormatter\Configurator\Items\Tag;
6200  use s9e\TextFormatter\Configurator\TemplateCheck;
6201  class DisallowDynamicElementNames extends TemplateCheck
6202  {
6203  	public function check(DOMElement $template, Tag $tag)
6204      {
6205          $nodes = $template->getElementsByTagNameNS(self::XMLNS_XSL, 'element');
6206          foreach ($nodes as $node)
6207              if (\strpos($node->getAttribute('name'), '{') !== \false)
6208                  throw new UnsafeTemplateException('Dynamic <xsl:element/> names are disallowed', $node);
6209      }
6210  }
6211  
6212  /*
6213  * @package   s9e\TextFormatter
6214  * @copyright Copyright (c) 2010-2019 The s9e Authors
6215  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6216  */
6217  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6218  use DOMElement;
6219  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6220  use s9e\TextFormatter\Configurator\Items\Tag;
6221  use s9e\TextFormatter\Configurator\TemplateCheck;
6222  class DisallowElementNS extends TemplateCheck
6223  {
6224      public $elName;
6225      public $namespaceURI;
6226  	public function __construct($namespaceURI, $elName)
6227      {
6228          $this->namespaceURI  = $namespaceURI;
6229          $this->elName        = $elName;
6230      }
6231  	public function check(DOMElement $template, Tag $tag)
6232      {
6233          $node = $template->getElementsByTagNameNS($this->namespaceURI, $this->elName)->item(0);
6234          if ($node)
6235              throw new UnsafeTemplateException("Element '" . $node->nodeName . "' is disallowed", $node);
6236      }
6237  }
6238  
6239  /*
6240  * @package   s9e\TextFormatter
6241  * @copyright Copyright (c) 2010-2019 The s9e Authors
6242  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6243  */
6244  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6245  use DOMElement;
6246  use DOMXPath;
6247  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6248  use s9e\TextFormatter\Configurator\Items\Tag;
6249  use s9e\TextFormatter\Configurator\TemplateCheck;
6250  class DisallowObjectParamsWithGeneratedName extends TemplateCheck
6251  {
6252  	public function check(DOMElement $template, Tag $tag)
6253      {
6254          $xpath = new DOMXPath($template->ownerDocument);
6255          $query = '//object//param[contains(@name, "{") or .//xsl:attribute[translate(@name, "NAME", "name") = "name"]]';
6256          $nodes = $xpath->query($query);
6257          foreach ($nodes as $node)
6258              throw new UnsafeTemplateException("A 'param' element with a suspect name has been found", $node);
6259      }
6260  }
6261  
6262  /*
6263  * @package   s9e\TextFormatter
6264  * @copyright Copyright (c) 2010-2019 The s9e Authors
6265  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6266  */
6267  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6268  use DOMElement;
6269  use DOMXPath;
6270  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6271  use s9e\TextFormatter\Configurator\Items\Tag;
6272  use s9e\TextFormatter\Configurator\TemplateCheck;
6273  class DisallowPHPTags extends TemplateCheck
6274  {
6275  	public function check(DOMElement $template, Tag $tag)
6276      {
6277          $queries = [
6278              '//processing-instruction()["php" = translate(name(),"HP","hp")]'
6279                  => 'PHP tags are not allowed in the template',
6280              '//script["php" = translate(@language,"HP","hp")]'
6281                  => 'PHP tags are not allowed in the template',
6282              '//xsl:processing-instruction["php" = translate(@name,"HP","hp")]'
6283                  => 'PHP tags are not allowed in the output',
6284              '//xsl:processing-instruction[contains(@name, "{")]'
6285                  => 'Dynamic processing instructions are not allowed',
6286          ];
6287          $xpath = new DOMXPath($template->ownerDocument);
6288          foreach ($queries as $query => $error)
6289          {
6290              $nodes = $xpath->query($query); 
6291              if ($nodes->length)
6292                  throw new UnsafeTemplateException($error, $nodes->item(0));
6293          }
6294      }
6295  }
6296  
6297  /*
6298  * @package   s9e\TextFormatter
6299  * @copyright Copyright (c) 2010-2019 The s9e Authors
6300  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6301  */
6302  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6303  use DOMElement;
6304  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6305  use s9e\TextFormatter\Configurator\Items\Tag;
6306  use s9e\TextFormatter\Configurator\TemplateCheck;
6307  class DisallowUnsafeCopyOf extends TemplateCheck
6308  {
6309  	public function check(DOMElement $template, Tag $tag)
6310      {
6311          $nodes = $template->getElementsByTagNameNS(self::XMLNS_XSL, 'copy-of');
6312          foreach ($nodes as $node)
6313          {
6314              $expr = $node->getAttribute('select');
6315              if (!\preg_match('#^@[-\\w]*(?:\\s*\\|\\s*@[-\\w]*)*$#D', $expr))
6316                  throw new UnsafeTemplateException("Cannot assess the safety of '" . $node->nodeName . "' select expression '" . $expr . "'", $node);
6317          }
6318      }
6319  }
6320  
6321  /*
6322  * @package   s9e\TextFormatter
6323  * @copyright Copyright (c) 2010-2019 The s9e Authors
6324  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6325  */
6326  namespace s9e\TextFormatter\Configurator\TemplateChecks;
6327  use DOMElement;
6328  use DOMXPath;
6329  use s9e\TextFormatter\Configurator\Exceptions\UnsafeTemplateException;
6330  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
6331  use s9e\TextFormatter\Configurator\Items\Tag;
6332  use s9e\TextFormatter\Configurator\TemplateCheck;
6333  class DisallowXPathFunction extends TemplateCheck
6334  {
6335      public $funcName;
6336  	public function __construct($funcName)
6337      {
6338          $this->funcName = $funcName;
6339      }
6340  	public function check(DOMElement $template, Tag $tag)
6341      {
6342          $regexp = '#(?!<\\pL)' . \preg_quote($this->funcName, '#') . '\\s*\\(#iu';
6343          $regexp = \str_replace('\\:', '\\s*:\\s*', $regexp);
6344          foreach ($this->getExpressions($template) as $expr => $node)
6345          {
6346              $expr = \preg_replace('#([\'"]).*?\\1#s', '', $expr);
6347              if (\preg_match($regexp, $expr))
6348                  throw new UnsafeTemplateException('An XPath expression uses the ' . $this->funcName . '() function', $node);
6349          }
6350      }
6351  	protected function getExpressions(DOMElement $template)
6352      {
6353          $xpath = new DOMXPath($template->ownerDocument);
6354          $exprs = [];
6355          foreach ($xpath->query('//@*') as $attribute)
6356              if ($attribute->parentNode->namespaceURI === self::XMLNS_XSL)
6357              {
6358                  $expr = $attribute->value;
6359                  $exprs[$expr] = $attribute;
6360              }
6361              else
6362                  foreach (AVTHelper::parse($attribute->value) as $token)
6363                      if ($token[0] === 'expression')
6364                          $exprs[$token[1]] = $attribute;
6365          return $exprs;
6366      }
6367  }
6368  
6369  /*
6370  * @package   s9e\TextFormatter
6371  * @copyright Copyright (c) 2010-2019 The s9e Authors
6372  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6373  */
6374  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6375  use DOMElement;
6376  use DOMNode;
6377  abstract class AbstractChooseOptimization extends AbstractNormalization
6378  {
6379      protected $choose;
6380      protected $queries = ['//xsl:choose'];
6381  	protected function getAttributes(DOMElement $element)
6382      {
6383          $attributes = array();
6384          foreach ($element->attributes as $attribute)
6385          {
6386              $key = $attribute->namespaceURI . '#' . $attribute->nodeName;
6387              $attributes[$key] = $attribute->nodeValue;
6388          }
6389          return $attributes;
6390      }
6391  	protected function getBranches()
6392      {
6393          $query = 'xsl:when|xsl:otherwise';
6394          return $this->xpath($query, $this->choose);
6395      }
6396  	protected function hasOtherwise()
6397      {
6398          return (bool) $this->xpath->evaluate('count(xsl:otherwise)', $this->choose);
6399      }
6400  	protected function isEmpty()
6401      {
6402          $query = 'count(xsl:when/node() | xsl:otherwise/node())';
6403          return !$this->xpath->evaluate($query, $this->choose);
6404      }
6405  	protected function isEqualNode(DOMNode $node1, DOMNode $node2)
6406      {
6407          return ($node1->ownerDocument->saveXML($node1) === $node2->ownerDocument->saveXML($node2));
6408      }
6409  	protected function isEqualTag(DOMElement $el1, DOMElement $el2)
6410      {
6411          return ($el1->namespaceURI === $el2->namespaceURI && $el1->nodeName === $el2->nodeName && $this->getAttributes($el1) === $this->getAttributes($el2));
6412      }
6413  	protected function normalizeElement(DOMElement $element)
6414      {
6415          $this->choose = $element;
6416          $this->optimizeChoose();
6417      }
6418      abstract protected function optimizeChoose();
6419  	protected function reset()
6420      {
6421          $this->choose = \null;
6422          parent::reset();
6423      }
6424  }
6425  
6426  /*
6427  * @package   s9e\TextFormatter
6428  * @copyright Copyright (c) 2010-2019 The s9e Authors
6429  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6430  */
6431  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6432  use DOMAttr;
6433  use DOMElement;
6434  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
6435  abstract class AbstractConstantFolding extends AbstractNormalization
6436  {
6437      protected $queries = [
6438          '//*[namespace-uri() != $XSL]/@*[contains(.,"{")]',
6439          '//xsl:value-of'
6440      ];
6441      abstract protected function getOptimizationPasses();
6442      protected function evaluateExpression($expr)
6443      {
6444          $original = $expr;
6445          foreach ($this->getOptimizationPasses() as $regexp => $methodName)
6446          {
6447              $regexp = \str_replace(' ', '\\s*', $regexp);
6448              $expr   = \preg_replace_callback($regexp, [$this, $methodName], $expr);
6449          }
6450          return ($expr === $original) ? $expr : $this->evaluateExpression(\trim($expr));
6451      }
6452      protected function normalizeAttribute(DOMAttr $attribute)
6453      {
6454          AVTHelper::replace(
6455              $attribute,
6456              function ($token)
6457              {
6458                  if ($token[0] === 'expression')
6459                      $token[1] = $this->evaluateExpression($token[1]);
6460                  return $token;
6461              }
6462          );
6463      }
6464      protected function normalizeElement(DOMElement $valueOf)
6465      {
6466          $valueOf->setAttribute('select', $this->evaluateExpression($valueOf->getAttribute('select')));
6467      }
6468  }
6469  
6470  /*
6471  * @package   s9e\TextFormatter
6472  * @copyright Copyright (c) 2010-2019 The s9e Authors
6473  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6474  */
6475  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6476  use DOMElement;
6477  use s9e\TextFormatter\Configurator\Helpers\ElementInspector;
6478  class EnforceHTMLOmittedEndTags extends AbstractNormalization
6479  {
6480      protected $queries = ['//*[namespace-uri() = ""]/*[namespace-uri() = ""]'];
6481      protected function normalizeElement(DOMElement $element)
6482      {
6483          $parentNode = $element->parentNode;
6484          if (ElementInspector::isVoid($parentNode) || ElementInspector::closesParent($element, $parentNode))
6485              $this->reparentElement($element);
6486      }
6487      protected function reparentElement(DOMElement $element)
6488      {
6489          $parentNode = $element->parentNode;
6490          do
6491          {
6492              $lastChild = $parentNode->lastChild;
6493              $parentNode->parentNode->insertBefore($lastChild, $parentNode->nextSibling);
6494          }
6495          while (!$lastChild->isSameNode($element));
6496      }
6497  }
6498  
6499  /*
6500  * @package   s9e\TextFormatter
6501  * @copyright Copyright (c) 2010-2019 The s9e Authors
6502  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6503  */
6504  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6505  use DOMAttr;
6506  class FixUnescapedCurlyBracesInHtmlAttributes extends AbstractNormalization
6507  {
6508      protected $queries = ['//*[namespace-uri() != $XSL]/@*[contains(., "{")]'];
6509      protected function normalizeAttribute(DOMAttr $attribute)
6510      {
6511          $match = [
6512              '(\\b(?:do|else|(?:if|while)\\s*\\(.*?\\))\\s*\\{(?![{@]))',
6513              '(\\bfunction\\s*\\w*\\s*\\([^\\)]*\\)\\s*\\{(?!\\{))',
6514              '((?<!\\{)(?:\\{\\{)*\\{(?!\\{)[^}]*+$)',
6515              '((?<!\\{)\\{\\s*(?:"[^"]*"|\'[^\']*\'|[a-z]\\w*(?:\\s|:\\s|:(?:["\']|\\w+\\s*,))))i'
6516          ];
6517          $replace = [
6518              '$0{',
6519              '$0{',
6520              '{$0',
6521              '{$0'
6522          ];
6523          $attrValue        = \preg_replace($match, $replace, $attribute->value);
6524          $attribute->value = \htmlspecialchars($attrValue, \ENT_NOQUOTES, 'UTF-8');
6525      }
6526  }
6527  
6528  /*
6529  * @package   s9e\TextFormatter
6530  * @copyright Copyright (c) 2010-2019 The s9e Authors
6531  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6532  */
6533  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6534  use DOMElement;
6535  use DOMText;
6536  class InlineAttributes extends AbstractNormalization
6537  {
6538      protected $queries = ['//*[namespace-uri() != $XSL]/xsl:attribute'];
6539      protected function normalizeElement(DOMElement $element)
6540      {
6541          $value = '';
6542          foreach ($element->childNodes as $node)
6543              if ($node instanceof DOMText || $this->isXsl($node, 'text'))
6544                  $value .= \preg_replace('([{}])', '$0$0', $node->textContent);
6545              elseif ($this->isXsl($node, 'value-of'))
6546                  $value .= '{' . $node->getAttribute('select') . '}';
6547              else
6548                  return;
6549          $element->parentNode->setAttribute($element->getAttribute('name'), $value);
6550          $element->parentNode->removeChild($element);
6551      }
6552  }
6553  
6554  /*
6555  * @package   s9e\TextFormatter
6556  * @copyright Copyright (c) 2010-2019 The s9e Authors
6557  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6558  */
6559  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6560  use DOMNode;
6561  class InlineCDATA extends AbstractNormalization
6562  {
6563      protected $queries = ['//text()'];
6564  	protected function normalizeNode(DOMNode $node)
6565      {
6566          if ($node->nodeType === \XML_CDATA_SECTION_NODE)
6567              $node->parentNode->replaceChild($this->createText($node->textContent), $node);
6568      }
6569  }
6570  
6571  /*
6572  * @package   s9e\TextFormatter
6573  * @copyright Copyright (c) 2010-2019 The s9e Authors
6574  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6575  */
6576  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6577  use DOMElement;
6578  use DOMException;
6579  class InlineElements extends AbstractNormalization
6580  {
6581      protected $queries = ['//xsl:element'];
6582  	protected function normalizeElement(DOMElement $element)
6583      {
6584          $elName = $element->getAttribute('name');
6585          $dom    = $this->ownerDocument;
6586          try
6587          {
6588              $newElement = ($element->hasAttribute('namespace'))
6589                          ? $dom->createElementNS($element->getAttribute('namespace'), $elName)
6590                          : $dom->createElement($elName);
6591          }
6592          catch (DOMException $e)
6593          {
6594              return;
6595          }
6596          $element->parentNode->replaceChild($newElement, $element);
6597          while ($element->firstChild)
6598              $newElement->appendChild($element->removeChild($element->firstChild));
6599      }
6600  }
6601  
6602  /*
6603  * @package   s9e\TextFormatter
6604  * @copyright Copyright (c) 2010-2019 The s9e Authors
6605  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6606  */
6607  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6608  use DOMAttr;
6609  use DOMElement;
6610  use DOMNode;
6611  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
6612  use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
6613  class InlineInferredValues extends AbstractNormalization
6614  {
6615      protected $queries = ['//xsl:if', '//xsl:when'];
6616  	protected function normalizeElement(DOMElement $element)
6617      {
6618          $map = XPathHelper::parseEqualityExpr($element->getAttribute('test'));
6619          if ($map === \false || \count($map) !== 1 || \count($map[\key($map)]) !== 1)
6620              return;
6621          $expr  = \key($map);
6622          $value = \end($map[$expr]);
6623          $this->inlineInferredValue($element, $expr, $value);
6624      }
6625  	protected function inlineInferredValue(DOMNode $node, $expr, $value)
6626      {
6627          $query = './/xsl:value-of[@select="' . $expr . '"]';
6628          foreach ($this->xpath($query, $node) as $valueOf)
6629              $this->replaceValueOf($valueOf, $value);
6630          $query = './/*[namespace-uri() != $XSL]/@*[contains(., "{' . $expr . '}")]';
6631          foreach ($this->xpath($query, $node) as $attribute)
6632              $this->replaceAttribute($attribute, $expr, $value);
6633      }
6634      protected function replaceAttribute(DOMAttr $attribute, $expr, $value)
6635      {
6636          AVTHelper::replace(
6637              $attribute,
6638              function ($token) use ($expr, $value)
6639              {
6640                  if ($token[0] === 'expression' && $token[1] === $expr)
6641                      $token = ['literal', $value];
6642                  return $token;
6643              }
6644          );
6645      }
6646      protected function replaceValueOf(DOMElement $valueOf, $value)
6647      {
6648          $valueOf->parentNode->replaceChild($this->createText($value), $valueOf);
6649      }
6650  }
6651  
6652  /*
6653  * @package   s9e\TextFormatter
6654  * @copyright Copyright (c) 2010-2019 The s9e Authors
6655  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6656  */
6657  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6658  use DOMElement;
6659  class InlineTextElements extends AbstractNormalization
6660  {
6661      protected $queries = ['//xsl:text[not(@disable-output-escaping="yes")]'];
6662  	protected function isFollowedByText(DOMElement $element)
6663      {
6664          return ($element->nextSibling && $element->nextSibling->nodeType === \XML_TEXT_NODE);
6665      }
6666  	protected function isPrecededByText(DOMElement $element)
6667      {
6668          return ($element->previousSibling && $element->previousSibling->nodeType === \XML_TEXT_NODE);
6669      }
6670  	protected function normalizeElement(DOMElement $element)
6671      {
6672          if (\trim($element->textContent) === '')
6673              if (!$this->isFollowedByText($element) && !$this->isPrecededByText($element))
6674                  return;
6675          $element->parentNode->replaceChild($this->createTextNode($element->textContent), $element);
6676      }
6677  }
6678  
6679  /*
6680  * @package   s9e\TextFormatter
6681  * @copyright Copyright (c) 2010-2019 The s9e Authors
6682  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6683  */
6684  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6685  use DOMAttr;
6686  use DOMElement;
6687  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
6688  class InlineXPathLiterals extends AbstractNormalization
6689  {
6690      protected $queries = [
6691          '//xsl:value-of',
6692          '//*[namespace-uri() != $XSL]/@*[contains(., "{")]'
6693      ];
6694      protected function getTextContent($expr)
6695      {
6696          $expr = \trim($expr);
6697          if (\preg_match('(^(?:\'[^\']*\'|"[^"]*")$)', $expr))
6698              return \substr($expr, 1, -1);
6699          if (\preg_match('(^0*([0-9]+(?:\\.[0-9]+)?)$)', $expr, $m))
6700              return $m[1];
6701          return \false;
6702      }
6703      protected function normalizeAttribute(DOMAttr $attribute)
6704      {
6705          AVTHelper::replace(
6706              $attribute,
6707              function ($token)
6708              {
6709                  if ($token[0] === 'expression')
6710                  {
6711                      $textContent = $this->getTextContent($token[1]);
6712                      if ($textContent !== \false)
6713                          $token = ['literal', $textContent];
6714                  }
6715                  return $token;
6716              }
6717          );
6718      }
6719      protected function normalizeElement(DOMElement $element)
6720      {
6721          $textContent = $this->getTextContent($element->getAttribute('select'));
6722          if ($textContent !== \false)
6723              $element->parentNode->replaceChild($this->createText($textContent), $element);
6724      }
6725  }
6726  
6727  /*
6728  * @package   s9e\TextFormatter
6729  * @copyright Copyright (c) 2010-2019 The s9e Authors
6730  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6731  */
6732  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6733  use DOMAttr;
6734  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
6735  class MinifyInlineCSS extends AbstractNormalization
6736  {
6737      protected $queries = ['//*[namespace-uri() != $XSL]/@style'];
6738      protected function normalizeAttribute(DOMAttr $attribute)
6739      {
6740          $css = $attribute->nodeValue;
6741          if (!\preg_match('(\\{(?!@\\w+\\}))', $css))
6742              $attribute->nodeValue = $this->minify($css);
6743      }
6744      protected function minify($css)
6745      {
6746          $css = \trim($css, " \n\t;");
6747          $css = \preg_replace('(\\s*([,:;])\\s*)', '$1', $css);
6748          $css = \preg_replace_callback(
6749              '((?<=[\\s:])#[0-9a-f]{3,6})i',
6750              function ($m)
6751              {
6752                  return \strtolower($m[0]);
6753              },
6754              $css
6755          );
6756          $css = \preg_replace('((?<=[\\s:])#([0-9a-f])\\1([0-9a-f])\\2([0-9a-f])\\3)', '#$1$2$3', $css);
6757          $css = \preg_replace('((?<=[\\s:])#f00\\b)', 'red', $css);
6758          $css = \preg_replace('((?<=[\\s:])0px\\b)', '0', $css);
6759          return $css;
6760      }
6761  }
6762  
6763  /*
6764  * @package   s9e\TextFormatter
6765  * @copyright Copyright (c) 2010-2019 The s9e Authors
6766  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6767  */
6768  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6769  use DOMAttr;
6770  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
6771  use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
6772  class MinifyXPathExpressions extends AbstractNormalization
6773  {
6774      protected $queries = ['//@*[contains(., " ")]'];
6775  	protected function normalizeAttribute(DOMAttr $attribute)
6776      {
6777          $element = $attribute->parentNode;
6778          if (!$this->isXsl($element))
6779              $this->replaceAVT($attribute);
6780          elseif (\in_array($attribute->nodeName, ['match', 'select', 'test'], \true))
6781          {
6782              $expr = XPathHelper::minify($attribute->nodeValue);
6783              $element->setAttribute($attribute->nodeName, $expr);
6784          }
6785      }
6786  	protected function replaceAVT(DOMAttr $attribute)
6787      {
6788          AVTHelper::replace(
6789              $attribute,
6790              function ($token)
6791              {
6792                  if ($token[0] === 'expression')
6793                      $token[1] = XPathHelper::minify($token[1]);
6794                  return $token;
6795              }
6796          );
6797      }
6798  }
6799  
6800  /*
6801  * @package   s9e\TextFormatter
6802  * @copyright Copyright (c) 2010-2019 The s9e Authors
6803  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6804  */
6805  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6806  use DOMAttr;
6807  use DOMElement;
6808  class NormalizeAttributeNames extends AbstractNormalization
6809  {
6810      protected $queries = [
6811          '//@*[name() != translate(name(), "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")]',
6812          '//xsl:attribute[not(contains(@name, "{"))][@name != translate(@name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")]'
6813      ];
6814  	protected function normalizeAttribute(DOMAttr $attribute)
6815      {
6816          $attribute->parentNode->setAttribute($this->lowercase($attribute->localName), $attribute->value);
6817          $attribute->parentNode->removeAttributeNode($attribute);
6818      }
6819  	protected function normalizeElement(DOMElement $element)
6820      {
6821          $element->setAttribute('name', $this->lowercase($element->getAttribute('name')));
6822      }
6823  }
6824  
6825  /*
6826  * @package   s9e\TextFormatter
6827  * @copyright Copyright (c) 2010-2019 The s9e Authors
6828  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6829  */
6830  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6831  use DOMElement;
6832  class NormalizeElementNames extends AbstractNormalization
6833  {
6834      protected $queries = [
6835          '//*[namespace-uri() != $XSL]',
6836          '//xsl:element[not(contains(@name, "{"))]'
6837      ];
6838      protected function normalizeElement(DOMElement $element)
6839      {
6840          if ($this->isXsl($element, 'element'))
6841              $this->replaceXslElement($element);
6842          else
6843              $this->replaceElement($element);
6844      }
6845      protected function replaceElement(DOMElement $element)
6846      {
6847          $elName = $this->lowercase($element->localName);
6848          if ($elName === $element->localName)
6849              return;
6850          $newElement = (\is_null($element->namespaceURI))
6851                      ? $this->ownerDocument->createElement($elName)
6852                      : $this->ownerDocument->createElementNS($element->namespaceURI, $elName);
6853          while ($element->firstChild)
6854              $newElement->appendChild($element->removeChild($element->firstChild));
6855          foreach ($element->attributes as $attribute)
6856              $newElement->setAttributeNS(
6857                  $attribute->namespaceURI,
6858                  $attribute->nodeName,
6859                  $attribute->value
6860              );
6861          $element->parentNode->replaceChild($newElement, $element);
6862      }
6863      protected function replaceXslElement(DOMElement $element)
6864      {
6865          $elName = $this->lowercase($element->getAttribute('name'));
6866          $element->setAttribute('name', $elName);
6867      }
6868  }
6869  
6870  /*
6871  * @package   s9e\TextFormatter
6872  * @copyright Copyright (c) 2010-2019 The s9e Authors
6873  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6874  */
6875  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6876  use DOMAttr;
6877  use DOMElement;
6878  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
6879  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
6880  use s9e\TextFormatter\Parser\AttributeFilters\UrlFilter;
6881  class NormalizeUrls extends AbstractNormalization
6882  {
6883  	protected function getNodes()
6884      {
6885          return TemplateHelper::getURLNodes($this->ownerDocument);
6886      }
6887  	protected function normalizeAttribute(DOMAttr $attribute)
6888      {
6889          $tokens = AVTHelper::parse(\trim($attribute->value));
6890          $attrValue = '';
6891          foreach ($tokens as $_f6b3b659)
6892          {
6893              list($type, $content) = $_f6b3b659;
6894              if ($type === 'literal')
6895                  $attrValue .= UrlFilter::sanitizeUrl($content);
6896              else
6897                  $attrValue .= '{' . $content . '}';
6898          }
6899          $attrValue = $this->unescapeBrackets($attrValue);
6900          $attribute->value = \htmlspecialchars($attrValue);
6901      }
6902  	protected function normalizeElement(DOMElement $element)
6903      {
6904          $query = './/text()[normalize-space() != ""]';
6905          foreach ($this->xpath($query, $element) as $i => $node)
6906          {
6907              $value = UrlFilter::sanitizeUrl($node->nodeValue);
6908              if (!$i)
6909                  $value = $this->unescapeBrackets(\ltrim($value));
6910              $node->nodeValue = $value;
6911          }
6912          if (isset($node))
6913              $node->nodeValue = \rtrim($node->nodeValue);
6914      }
6915  	protected function unescapeBrackets($url)
6916      {
6917          return \preg_replace('#^(\\w+://)%5B([-\\w:._%]+)%5D#i', '$1[$2]', $url);
6918      }
6919  }
6920  
6921  /*
6922  * @package   s9e\TextFormatter
6923  * @copyright Copyright (c) 2010-2019 The s9e Authors
6924  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6925  */
6926  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6927  use DOMElement;
6928  class OptimizeConditionalAttributes extends AbstractNormalization
6929  {
6930      protected $queries = ['//xsl:if[starts-with(@test, "@")][count(descendant::node()) = 2][xsl:attribute[@name = substring(../@test, 2)][xsl:value-of[@select = ../../@test]]]'];
6931  	protected function normalizeElement(DOMElement $element)
6932      {
6933          $copyOf = $this->createElement('xsl:copy-of');
6934          $copyOf->setAttribute('select', $element->getAttribute('test'));
6935          $element->parentNode->replaceChild($copyOf, $element);
6936      }
6937  }
6938  
6939  /*
6940  * @package   s9e\TextFormatter
6941  * @copyright Copyright (c) 2010-2019 The s9e Authors
6942  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6943  */
6944  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6945  use DOMElement;
6946  class OptimizeConditionalValueOf extends AbstractNormalization
6947  {
6948      protected $queries = ['//xsl:if[count(descendant::node()) = 1]/xsl:value-of'];
6949  	protected function normalizeElement(DOMElement $element)
6950      {
6951          $if     = $element->parentNode;
6952          $test   = $if->getAttribute('test');
6953          $select = $element->getAttribute('select');
6954          if ($select !== $test || !\preg_match('#^@[-\\w]+$#D', $select))
6955              return;
6956          $if->parentNode->replaceChild($if->removeChild($element), $if);
6957      }
6958  }
6959  
6960  /*
6961  * @package   s9e\TextFormatter
6962  * @copyright Copyright (c) 2010-2019 The s9e Authors
6963  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6964  */
6965  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6966  use DOMNode;
6967  class PreserveSingleSpaces extends AbstractNormalization
6968  {
6969      protected $queries = ['//text()[. = " "][not(parent::xsl:text)]'];
6970  	protected function normalizeNode(DOMNode $node)
6971      {
6972          $node->parentNode->replaceChild($this->createElement('xsl:text', ' '), $node);
6973      }
6974  }
6975  
6976  /*
6977  * @package   s9e\TextFormatter
6978  * @copyright Copyright (c) 2010-2019 The s9e Authors
6979  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6980  */
6981  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6982  use DOMNode;
6983  class RemoveComments extends AbstractNormalization
6984  {
6985      protected $queries = ['//comment()'];
6986  	protected function normalizeNode(DOMNode $node)
6987      {
6988          $node->parentNode->removeChild($node);
6989      }
6990  }
6991  
6992  /*
6993  * @package   s9e\TextFormatter
6994  * @copyright Copyright (c) 2010-2019 The s9e Authors
6995  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
6996  */
6997  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
6998  use DOMNode;
6999  class RemoveInterElementWhitespace extends AbstractNormalization
7000  {
7001      protected $queries = ['//text()[normalize-space() = ""][. != " "][not(parent::xsl:text)]'];
7002  	protected function normalizeNode(DOMNode $node)
7003      {
7004          $node->parentNode->removeChild($node);
7005      }
7006  }
7007  
7008  /*
7009  * @package   s9e\TextFormatter
7010  * @copyright Copyright (c) 2010-2019 The s9e Authors
7011  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7012  */
7013  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
7014  use DOMAttr;
7015  use DOMElement;
7016  class RemoveLivePreviewAttributes extends AbstractNormalization
7017  {
7018      protected $queries = [
7019          '//@*           [starts-with(name(), "data-s9e-livepreview-")]',
7020          '//xsl:attribute[starts-with(@name,  "data-s9e-livepreview-")]'
7021      ];
7022  	protected function normalizeAttribute(DOMAttr $attribute)
7023      {
7024          $attribute->parentNode->removeAttributeNode($attribute);
7025      }
7026  	protected function normalizeElement(DOMElement $element)
7027      {
7028          $element->parentNode->removeChild($element);
7029      }
7030  }
7031  
7032  /*
7033  * @package   s9e\TextFormatter
7034  * @copyright Copyright (c) 2010-2019 The s9e Authors
7035  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7036  */
7037  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
7038  use DOMElement;
7039  class SetRelNoreferrerOnTargetedLinks extends AbstractNormalization
7040  {
7041      protected $queries = ['//a', '//area'];
7042  	protected function addRelAttribute(DOMElement $element)
7043      {
7044          $rel = $element->getAttribute('rel');
7045          if (\preg_match('(\\S$)', $rel))
7046              $rel .= ' ';
7047          $rel .= 'noreferrer';
7048          $element->setAttribute('rel', $rel);
7049      }
7050  	protected function linkTargetCanAccessOpener(DOMElement $element)
7051      {
7052          if (!$element->hasAttribute('target'))
7053              return \false;
7054          if (\preg_match('(\\bno(?:open|referr)er\\b)', $element->getAttribute('rel')))
7055              return \false;
7056          return \true;
7057      }
7058  	protected function normalizeElement(DOMElement $element)
7059      {
7060          if ($this->linkTargetCanAccessOpener($element))
7061              $this->addRelAttribute($element);
7062      }
7063  }
7064  
7065  /*
7066  * @package   s9e\TextFormatter
7067  * @copyright Copyright (c) 2010-2019 The s9e Authors
7068  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7069  */
7070  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
7071  use DOMAttr;
7072  use DOMElement;
7073  use s9e\TextFormatter\Configurator\Helpers\AVTHelper;
7074  class UninlineAttributes extends AbstractNormalization
7075  {
7076      protected $queries = ['//*[namespace-uri() != $XSL][@*]'];
7077      protected function normalizeElement(DOMElement $element)
7078      {
7079          $fragment = $element->ownerDocument->createDocumentFragment();
7080          while ($element->attributes->length > 0)
7081          {
7082              $attribute = $element->attributes->item(0);
7083              $fragment->appendChild($this->uninlineAttribute($attribute));
7084          }
7085          $element->insertBefore($fragment, $element->firstChild);
7086      }
7087      protected function uninlineAttribute(DOMAttr $attribute)
7088      {
7089          $xslAttribute = (\strpos($attribute->value, '{') === \false)
7090                        ? $this->uninlineStaticAttribute($attribute)
7091                        : $this->uninlineDynamicAttribute($attribute);
7092          $xslAttribute->setAttribute('name', $attribute->nodeName);
7093          $attribute->parentNode->removeAttributeNode($attribute);
7094          return $xslAttribute;
7095      }
7096      protected function uninlineDynamicAttribute(DOMAttr $attribute)
7097      {
7098          $xslAttribute = $this->createElement('xsl:attribute');
7099          foreach (AVTHelper::parse($attribute->value) as $_f6b3b659)
7100          {
7101              list($type, $content) = $_f6b3b659;
7102              if ($type === 'expression')
7103              {
7104                  $childNode = $this->createElement('xsl:value-of');
7105                  $childNode->setAttribute('select', $content);
7106              }
7107              else
7108                  $childNode = $this->createText($content);
7109              $xslAttribute->appendChild($childNode);
7110          }
7111          return $xslAttribute;
7112      }
7113      protected function uninlineStaticAttribute(DOMAttr $attribute)
7114      {
7115          return $this->createElement('xsl:attribute', \str_replace('}}', '}', $attribute->value));
7116      }
7117  }
7118  
7119  /*
7120  * @package   s9e\TextFormatter
7121  * @copyright Copyright (c) 2010-2019 The s9e Authors
7122  * @license   http://www.opensource.org/licenses/mit-license'); The MIT License
7123  */
7124  namespace s9e\TextFormatter\Configurator;
7125  use ArrayAccess;
7126  use Iterator;
7127  use s9e\TextFormatter\Configurator\Collections\TemplateNormalizationList;
7128  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
7129  use s9e\TextFormatter\Configurator\Items\Tag;
7130  use s9e\TextFormatter\Configurator\Traits\CollectionProxy;
7131  class TemplateNormalizer implements ArrayAccess, Iterator
7132  {
7133      use CollectionProxy;
7134      protected $collection;
7135      protected $maxIterations = 100;
7136  	public function __construct()
7137      {
7138          $this->collection = new TemplateNormalizationList;
7139          $this->collection->append('PreserveSingleSpaces');
7140          $this->collection->append('RemoveComments');
7141          $this->collection->append('RemoveInterElementWhitespace');
7142          $this->collection->append('NormalizeElementNames');
7143          $this->collection->append('FixUnescapedCurlyBracesInHtmlAttributes');
7144          $this->collection->append('EnforceHTMLOmittedEndTags');
7145          $this->collection->append('InlineCDATA');
7146          $this->collection->append('InlineElements');
7147          $this->collection->append('InlineTextElements');
7148          $this->collection->append('UninlineAttributes');
7149          $this->collection->append('MinifyXPathExpressions');
7150          $this->collection->append('NormalizeAttributeNames');
7151          $this->collection->append('OptimizeConditionalAttributes');
7152          $this->collection->append('FoldArithmeticConstants');
7153          $this->collection->append('FoldConstantXPathExpressions');
7154          $this->collection->append('InlineXPathLiterals');
7155          $this->collection->append('OptimizeChooseText');
7156          $this->collection->append('OptimizeConditionalValueOf');
7157          $this->collection->append('OptimizeChoose');
7158          $this->collection->append('InlineAttributes');
7159          $this->collection->append('NormalizeUrls');
7160          $this->collection->append('InlineInferredValues');
7161          $this->collection->append('SetRelNoreferrerOnTargetedLinks');
7162          $this->collection->append('MinifyInlineCSS');
7163      }
7164  	public function normalizeTag(Tag $tag)
7165      {
7166          if (isset($tag->template) && !$tag->template->isNormalized())
7167              $tag->template->normalize($this);
7168      }
7169  	public function normalizeTemplate($template)
7170      {
7171          $dom = TemplateHelper::loadTemplate($template);
7172          $i = 0;
7173          do
7174          {
7175              $old = $template;
7176              foreach ($this->collection as $k => $normalization)
7177              {
7178                  if ($i > 0 && !empty($normalization->onlyOnce))
7179                      continue;
7180                  $normalization->normalize($dom->documentElement);
7181              }
7182              $template = TemplateHelper::saveTemplate($dom);
7183          }
7184          while (++$i < $this->maxIterations && $template !== $old);
7185          return $template;
7186      }
7187  }
7188  
7189  /*
7190  * @package   s9e\TextFormatter
7191  * @copyright Copyright (c) 2010-2019 The s9e Authors
7192  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7193  */
7194  namespace s9e\TextFormatter\Configurator;
7195  use RuntimeException;
7196  use s9e\TextFormatter\Configurator\Collections\HostnameList;
7197  use s9e\TextFormatter\Configurator\Collections\SchemeList;
7198  use s9e\TextFormatter\Configurator\Helpers\ConfigHelper;
7199  class UrlConfig implements ConfigProvider
7200  {
7201      protected $allowedSchemes;
7202      protected $disallowedHosts;
7203      protected $restrictedHosts;
7204  	public function __construct()
7205      {
7206          $this->disallowedHosts = new HostnameList;
7207          $this->restrictedHosts = new HostnameList;
7208          $this->allowedSchemes   = new SchemeList;
7209          $this->allowedSchemes[] = 'http';
7210          $this->allowedSchemes[] = 'https';
7211      }
7212  	public function asConfig()
7213      {
7214          return ConfigHelper::toArray(\get_object_vars($this));
7215      }
7216  	public function allowScheme($scheme)
7217      {
7218          if (\strtolower($scheme) === 'javascript')
7219              throw new RuntimeException('The JavaScript URL scheme cannot be allowed');
7220          $this->allowedSchemes[] = $scheme;
7221      }
7222  	public function disallowHost($host, $matchSubdomains = \true)
7223      {
7224          $this->disallowedHosts[] = $host;
7225          if ($matchSubdomains && \substr($host, 0, 1) !== '*')
7226              $this->disallowedHosts[] = '*.' . $host;
7227      }
7228  	public function disallowScheme($scheme)
7229      {
7230          $this->allowedSchemes->remove($scheme);
7231      }
7232  	public function getAllowedSchemes()
7233      {
7234          return \iterator_to_array($this->allowedSchemes);
7235      }
7236  	public function restrictHost($host, $matchSubdomains = \true)
7237      {
7238          $this->restrictedHosts[] = $host;
7239          if ($matchSubdomains && \substr($host, 0, 1) !== '*')
7240              $this->restrictedHosts[] = '*.' . $host;
7241      }
7242  }
7243  
7244  /*
7245  * @package   s9e\TextFormatter
7246  * @copyright Copyright (c) 2010-2019 The s9e Authors
7247  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7248  */
7249  namespace s9e\TextFormatter\Configurator\Collections;
7250  use InvalidArgumentException;
7251  use s9e\TextFormatter\Configurator\Helpers\RegexpParser;
7252  use s9e\TextFormatter\Configurator\Items\AttributePreprocessor;
7253  use s9e\TextFormatter\Configurator\Items\Regexp;
7254  use s9e\TextFormatter\Configurator\JavaScript\RegexpConvertor;
7255  use s9e\TextFormatter\Configurator\Validators\AttributeName;
7256  class AttributePreprocessorCollection extends Collection
7257  {
7258  	public function add($attrName, $regexp)
7259      {
7260          $attrName = AttributeName::normalize($attrName);
7261          $k = \serialize([$attrName, $regexp]);
7262          $this->items[$k] = new AttributePreprocessor($regexp);
7263          return $this->items[$k];
7264      }
7265  	public function key()
7266      {
7267          list($attrName) = \unserialize(\key($this->items));
7268          return $attrName;
7269      }
7270  	public function merge($attributePreprocessors)
7271      {
7272          $error = \false;
7273          if ($attributePreprocessors instanceof AttributePreprocessorCollection)
7274              foreach ($attributePreprocessors as $attrName => $attributePreprocessor)
7275                  $this->add($attrName, $attributePreprocessor->getRegexp());
7276          elseif (\is_array($attributePreprocessors))
7277          {
7278              foreach ($attributePreprocessors as $values)
7279              {
7280                  if (!\is_array($values))
7281                  {
7282                      $error = \true;
7283                      break;
7284                  }
7285                  list($attrName, $value) = $values;
7286                  if ($value instanceof AttributePreprocessor)
7287                      $value = $value->getRegexp();
7288                  $this->add($attrName, $value);
7289              }
7290          }
7291          else
7292              $error = \true;
7293          if ($error)
7294              throw new InvalidArgumentException('merge() expects an instance of AttributePreprocessorCollection or a 2D array where each element is a [attribute name, regexp] pair');
7295      }
7296  	public function asConfig()
7297      {
7298          $config = [];
7299          foreach ($this->items as $k => $ap)
7300          {
7301              list($attrName) = \unserialize($k);
7302              $config[] = [$attrName, $ap, $ap->getCaptureNames()];
7303          }
7304          return $config;
7305      }
7306  }
7307  
7308  /*
7309  * @package   s9e\TextFormatter
7310  * @copyright Copyright (c) 2010-2019 The s9e Authors
7311  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7312  */
7313  namespace s9e\TextFormatter\Configurator\Collections;
7314  use ArrayAccess;
7315  use InvalidArgumentException;
7316  use RuntimeException;
7317  class NormalizedCollection extends Collection implements ArrayAccess
7318  {
7319      protected $onDuplicateAction = 'error';
7320  	public function asConfig()
7321      {
7322          $config = parent::asConfig();
7323          \ksort($config);
7324          return $config;
7325      }
7326  	public function onDuplicate($action = \null)
7327      {
7328          $old = $this->onDuplicateAction;
7329          if (\func_num_args() && $action !== 'error' && $action !== 'ignore' && $action !== 'replace')
7330              throw new InvalidArgumentException("Invalid onDuplicate action '" . $action . "'. Expected: 'error', 'ignore' or 'replace'");
7331          $this->onDuplicateAction = $action;
7332          return $old;
7333      }
7334  	protected function getAlreadyExistsException($key)
7335      {
7336          return new RuntimeException("Item '" . $key . "' already exists");
7337      }
7338  	protected function getNotExistException($key)
7339      {
7340          return new RuntimeException("Item '" . $key . "' does not exist");
7341      }
7342  	public function normalizeKey($key)
7343      {
7344          return $key;
7345      }
7346  	public function normalizeValue($value)
7347      {
7348          return $value;
7349      }
7350  	public function add($key, $value = \null)
7351      {
7352          if ($this->exists($key))
7353              if ($this->onDuplicateAction === 'ignore')
7354                  return $this->get($key);
7355              elseif ($this->onDuplicateAction === 'error')
7356                  throw $this->getAlreadyExistsException($key);
7357          return $this->set($key, $value);
7358      }
7359  	public function contains($value)
7360      {
7361          return \in_array($this->normalizeValue($value), $this->items);
7362      }
7363  	public function delete($key)
7364      {
7365          $key = $this->normalizeKey($key);
7366          unset($this->items[$key]);
7367      }
7368  	public function exists($key)
7369      {
7370          $key = $this->normalizeKey($key);
7371          return \array_key_exists($key, $this->items);
7372      }
7373  	public function get($key)
7374      {
7375          if (!$this->exists($key))
7376              throw $this->getNotExistException($key);
7377          $key = $this->normalizeKey($key);
7378          return $this->items[$key];
7379      }
7380  	public function indexOf($value)
7381      {
7382          return \array_search($this->normalizeValue($value), $this->items);
7383      }
7384  	public function set($key, $value)
7385      {
7386          $key = $this->normalizeKey($key);
7387          $this->items[$key] = $this->normalizeValue($value);
7388          return $this->items[$key];
7389      }
7390  	public function offsetExists($offset)
7391      {
7392          return $this->exists($offset);
7393      }
7394  	public function offsetGet($offset)
7395      {
7396          return $this->get($offset);
7397      }
7398  	public function offsetSet($offset, $value)
7399      {
7400          $this->set($offset, $value);
7401      }
7402  	public function offsetUnset($offset)
7403      {
7404          $this->delete($offset);
7405      }
7406  }
7407  
7408  /*
7409  * @package   s9e\TextFormatter
7410  * @copyright Copyright (c) 2010-2019 The s9e Authors
7411  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7412  */
7413  namespace s9e\TextFormatter\Configurator\Collections;
7414  use ArrayAccess;
7415  use BadMethodCallException;
7416  use InvalidArgumentException;
7417  use RuntimeException;
7418  use s9e\TextFormatter\Configurator\ConfigProvider;
7419  use s9e\TextFormatter\Configurator\JavaScript\Dictionary;
7420  use s9e\TextFormatter\Configurator\Validators\TagName;
7421  use s9e\TextFormatter\Parser;
7422  class Ruleset extends Collection implements ArrayAccess, ConfigProvider
7423  {
7424      protected $rules = [
7425          'allowChild'                  => 'addTargetedRule',
7426          'allowDescendant'             => 'addTargetedRule',
7427          'autoClose'                   => 'addBooleanRule',
7428          'autoReopen'                  => 'addBooleanRule',
7429          'breakParagraph'              => 'addBooleanRule',
7430          'closeAncestor'               => 'addTargetedRule',
7431          'closeParent'                 => 'addTargetedRule',
7432          'createChild'                 => 'addTargetedRule',
7433          'createParagraphs'            => 'addBooleanRule',
7434          'denyChild'                   => 'addTargetedRule',
7435          'denyDescendant'              => 'addTargetedRule',
7436          'disableAutoLineBreaks'       => 'addBooleanRule',
7437          'enableAutoLineBreaks'        => 'addBooleanRule',
7438          'fosterParent'                => 'addTargetedRule',
7439          'ignoreSurroundingWhitespace' => 'addBooleanRule',
7440          'ignoreTags'                  => 'addBooleanRule',
7441          'ignoreText'                  => 'addBooleanRule',
7442          'isTransparent'               => 'addBooleanRule',
7443          'preventLineBreaks'           => 'addBooleanRule',
7444          'requireParent'               => 'addTargetedRule',
7445          'requireAncestor'             => 'addTargetedRule',
7446          'suspendAutoLineBreaks'       => 'addBooleanRule',
7447          'trimFirstLine'               => 'addBooleanRule'
7448      ];
7449  	public function __construct()
7450      {
7451          $this->clear();
7452      }
7453  	public function __call($methodName, array $args)
7454      {
7455          if (!isset($this->rules[$methodName]))
7456              throw new BadMethodCallException("Undefined method '" . $methodName . "'");
7457          \array_unshift($args, $methodName);
7458          \call_user_func_array([$this, $this->rules[$methodName]], $args);
7459          return $this;
7460      }
7461  	public function offsetExists($k)
7462      {
7463          return isset($this->items[$k]);
7464      }
7465  	public function offsetGet($k)
7466      {
7467          return $this->items[$k];
7468      }
7469  	public function offsetSet($k, $v)
7470      {
7471          throw new RuntimeException('Not supported');
7472      }
7473  	public function offsetUnset($k)
7474      {
7475          return $this->remove($k);
7476      }
7477  	public function asConfig()
7478      {
7479          $config = $this->items;
7480          unset($config['allowChild']);
7481          unset($config['allowDescendant']);
7482          unset($config['denyChild']);
7483          unset($config['denyDescendant']);
7484          unset($config['requireParent']);
7485          $bitValues = [
7486              'autoClose'                   => Parser::RULE_AUTO_CLOSE,
7487              'autoReopen'                  => Parser::RULE_AUTO_REOPEN,
7488              'breakParagraph'              => Parser::RULE_BREAK_PARAGRAPH,
7489              'createParagraphs'            => Parser::RULE_CREATE_PARAGRAPHS,
7490              'disableAutoLineBreaks'       => Parser::RULE_DISABLE_AUTO_BR,
7491              'enableAutoLineBreaks'        => Parser::RULE_ENABLE_AUTO_BR,
7492              'ignoreSurroundingWhitespace' => Parser::RULE_IGNORE_WHITESPACE,
7493              'ignoreTags'                  => Parser::RULE_IGNORE_TAGS,
7494              'ignoreText'                  => Parser::RULE_IGNORE_TEXT,
7495              'isTransparent'               => Parser::RULE_IS_TRANSPARENT,
7496              'preventLineBreaks'           => Parser::RULE_PREVENT_BR,
7497              'suspendAutoLineBreaks'       => Parser::RULE_SUSPEND_AUTO_BR,
7498              'trimFirstLine'               => Parser::RULE_TRIM_FIRST_LINE
7499          ];
7500          $bitfield = 0;
7501          foreach ($bitValues as $ruleName => $bitValue)
7502          {
7503              if (!empty($config[$ruleName]))
7504                  $bitfield |= $bitValue;
7505              unset($config[$ruleName]);
7506          }
7507          foreach (['closeAncestor', 'closeParent', 'fosterParent'] as $ruleName)
7508              if (isset($config[$ruleName]))
7509              {
7510                  $targets = \array_fill_keys($config[$ruleName], 1);
7511                  $config[$ruleName] = new Dictionary($targets);
7512              }
7513          $config['flags'] = $bitfield;
7514          return $config;
7515      }
7516  	public function merge($rules, $overwrite = \true)
7517      {
7518          if (!\is_array($rules)
7519           && !($rules instanceof self))
7520              throw new InvalidArgumentException('merge() expects an array or an instance of Ruleset');
7521          foreach ($rules as $action => $value)
7522              if (\is_array($value))
7523                  foreach ($value as $tagName)
7524                      $this->$action($tagName);
7525              elseif ($overwrite || !isset($this->items[$action]))
7526                  $this->$action($value);
7527      }
7528  	public function remove($type, $tagName = \null)
7529      {
7530          if (\preg_match('(^default(?:Child|Descendant)Rule)', $type))
7531              throw new RuntimeException('Cannot remove ' . $type);
7532          if (isset($tagName))
7533          {
7534              $tagName = TagName::normalize($tagName);
7535              if (isset($this->items[$type]))
7536              {
7537                  $this->items[$type] = \array_diff(
7538                      $this->items[$type],
7539                      [$tagName]
7540                  );
7541                  if (empty($this->items[$type]))
7542                      unset($this->items[$type]);
7543                  else
7544                      $this->items[$type] = \array_values($this->items[$type]);
7545              }
7546          }
7547          else
7548              unset($this->items[$type]);
7549      }
7550  	protected function addBooleanRule($ruleName, $bool = \true)
7551      {
7552          if (!\is_bool($bool))
7553              throw new InvalidArgumentException($ruleName . '() expects a boolean');
7554          $this->items[$ruleName] = $bool;
7555      }
7556  	protected function addTargetedRule($ruleName, $tagName)
7557      {
7558          $this->items[$ruleName][] = TagName::normalize($tagName);
7559      }
7560  }
7561  
7562  /*
7563  * @package   s9e\TextFormatter
7564  * @copyright Copyright (c) 2010-2019 The s9e Authors
7565  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7566  */
7567  namespace s9e\TextFormatter\Configurator\Items;
7568  abstract class Filter extends ProgrammableCallback
7569  {
7570  }
7571  
7572  /*
7573  * @package   s9e\TextFormatter
7574  * @copyright Copyright (c) 2010-2019 The s9e Authors
7575  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7576  */
7577  namespace s9e\TextFormatter\Configurator\TemplateChecks;
7578  use DOMElement;
7579  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
7580  use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
7581  use s9e\TextFormatter\Configurator\Items\Attribute;
7582  class DisallowUnsafeDynamicCSS extends AbstractDynamicContentCheck
7583  {
7584  	protected function getNodes(DOMElement $template)
7585      {
7586          return TemplateHelper::getCSSNodes($template->ownerDocument);
7587      }
7588  	protected function isExpressionSafe($expr)
7589      {
7590          return XPathHelper::isExpressionNumeric($expr);
7591      }
7592  	protected function isSafe(Attribute $attribute)
7593      {
7594          return $attribute->isSafeInCSS();
7595      }
7596  }
7597  
7598  /*
7599  * @package   s9e\TextFormatter
7600  * @copyright Copyright (c) 2010-2019 The s9e Authors
7601  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7602  */
7603  namespace s9e\TextFormatter\Configurator\TemplateChecks;
7604  use DOMElement;
7605  use s9e\TextFormatter\Configurator\Helpers\XPathHelper;
7606  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
7607  use s9e\TextFormatter\Configurator\Items\Attribute;
7608  class DisallowUnsafeDynamicJS extends AbstractDynamicContentCheck
7609  {
7610  	protected function getNodes(DOMElement $template)
7611      {
7612          return TemplateHelper::getJSNodes($template->ownerDocument);
7613      }
7614  	protected function isExpressionSafe($expr)
7615      {
7616          return XPathHelper::isExpressionNumeric($expr);
7617      }
7618  	protected function isSafe(Attribute $attribute)
7619      {
7620          return $attribute->isSafeInJS();
7621      }
7622  }
7623  
7624  /*
7625  * @package   s9e\TextFormatter
7626  * @copyright Copyright (c) 2010-2019 The s9e Authors
7627  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7628  */
7629  namespace s9e\TextFormatter\Configurator\TemplateChecks;
7630  use DOMAttr;
7631  use DOMElement;
7632  use DOMText;
7633  use s9e\TextFormatter\Configurator\Helpers\TemplateHelper;
7634  use s9e\TextFormatter\Configurator\Items\Attribute;
7635  use s9e\TextFormatter\Configurator\Items\Tag;
7636  class DisallowUnsafeDynamicURL extends AbstractDynamicContentCheck
7637  {
7638      protected $exceptionRegexp = '(^(?:(?!data|\\w*script)\\w+:|[^:]*/|#))i';
7639  	protected function getNodes(DOMElement $template)
7640      {
7641          return TemplateHelper::getURLNodes($template->ownerDocument);
7642      }
7643  	protected function isSafe(Attribute $attribute)
7644      {
7645          return $attribute->isSafeAsURL();
7646      }
7647  	protected function checkAttributeNode(DOMAttr $attribute, Tag $tag)
7648      {
7649          if (\preg_match($this->exceptionRegexp, $attribute->value))
7650              return;
7651          parent::checkAttributeNode($attribute, $tag);
7652      }
7653  	protected function checkElementNode(DOMElement $element, Tag $tag)
7654      {
7655          if ($element->firstChild
7656           && $element->firstChild instanceof DOMText
7657           && \preg_match($this->exceptionRegexp, $element->firstChild->textContent))
7658              return;
7659          parent::checkElementNode($element, $tag);
7660      }
7661  }
7662  
7663  /*
7664  * @package   s9e\TextFormatter
7665  * @copyright Copyright (c) 2010-2019 The s9e Authors
7666  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7667  */
7668  namespace s9e\TextFormatter\Configurator\TemplateChecks;
7669  class RestrictFlashScriptAccess extends AbstractFlashRestriction
7670  {
7671      public $defaultSetting = 'sameDomain';
7672      protected $settingName = 'allowScriptAccess';
7673      protected $settings = [
7674          'always'     => 3,
7675          'samedomain' => 2,
7676          'never'      => 1
7677      ];
7678  }
7679  
7680  /*
7681  * @package   s9e\TextFormatter
7682  * @copyright Copyright (c) 2010-2019 The s9e Authors
7683  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7684  */
7685  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
7686  use s9e\TextFormatter\Utils\XPath;
7687  class FoldArithmeticConstants extends AbstractConstantFolding
7688  {
7689  	protected function getOptimizationPasses()
7690      {
7691          $n = '-?\\.[0-9]++|-?[0-9]++(?:\\.[0-9]++)?';
7692          return [
7693              '(^[0-9\\s]*[-+][-+0-9\\s]+$)'                               => 'foldOperation',
7694              '( \\+ 0(?! [^+\\)])|(?<![-\\w])0 \\+ )'                     => 'foldAdditiveIdentity',
7695              '(^((?>' . $n . ' [-+] )*)(' . $n . ') div (' . $n . '))'    => 'foldDivision',
7696              '(^((?>' . $n . ' [-+] )*)(' . $n . ') \\* (' . $n . '))'    => 'foldMultiplication',
7697              '(\\( (?:' . $n . ') (?>(?>[-+*]|div) (?:' . $n . ') )+\\))' => 'foldSubExpression',
7698              '((?<=[-+*\\(]|\\bdiv|^) \\( ([@$][-\\w]+|' . $n . ') \\) (?=[-+*\\)]|div|$))' => 'removeParentheses'
7699          ];
7700      }
7701  	protected function evaluateExpression($expr)
7702      {
7703          $expr = \preg_replace_callback(
7704              '(([\'"])(.*?)\\1)s',
7705              function ($m)
7706              {
7707                  return $m[1] . \bin2hex($m[2]) . $m[1];
7708              },
7709              $expr
7710          );
7711          $expr = parent::evaluateExpression($expr);
7712          $expr = \preg_replace_callback(
7713              '(([\'"])(.*?)\\1)s',
7714              function ($m)
7715              {
7716                  return $m[1] . \hex2bin($m[2]) . $m[1];
7717              },
7718              $expr
7719          );
7720          return $expr;
7721      }
7722  	protected function foldAdditiveIdentity(array $m)
7723      {
7724          return '';
7725      }
7726  	protected function foldDivision(array $m)
7727      {
7728          return $m[1] . XPath::export($m[2] / $m[3]);
7729      }
7730  	protected function foldMultiplication(array $m)
7731      {
7732          return $m[1] . XPath::export($m[2] * $m[3]);
7733      }
7734  	protected function foldOperation(array $m)
7735      {
7736          return XPath::export($this->xpath->evaluate($m[0]));
7737      }
7738  	protected function foldSubExpression(array $m)
7739      {
7740          return '(' . $this->evaluateExpression(\trim(\substr($m[0], 1, -1))) . ')';
7741      }
7742  	protected function removeParentheses(array $m)
7743      {
7744          return ' ' . $m[1] . ' ';
7745      }
7746  }
7747  
7748  /*
7749  * @package   s9e\TextFormatter
7750  * @copyright Copyright (c) 2010-2019 The s9e Authors
7751  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7752  */
7753  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
7754  use s9e\TextFormatter\Utils\XPath;
7755  class FoldConstantXPathExpressions extends AbstractConstantFolding
7756  {
7757      protected $supportedFunctions = [
7758          'ceiling',
7759          'concat',
7760          'contains',
7761          'floor',
7762          'normalize-space',
7763          'number',
7764          'round',
7765          'starts-with',
7766          'string',
7767          'string-length',
7768          'substring',
7769          'substring-after',
7770          'substring-before',
7771          'sum',
7772          'translate'
7773      ];
7774  	protected function getOptimizationPasses()
7775      {
7776          return [
7777              '(^(?:"[^"]*"|\'[^\']*\'|\\.[0-9]|[^"$&\'./:<=>@[\\]])++$)' => 'foldConstantXPathExpression'
7778          ];
7779      }
7780  	protected function canBeSerialized($value)
7781      {
7782          return (\is_string($value) || \is_integer($value) || \is_float($value));
7783      }
7784  	protected function evaluate($expr)
7785      {
7786          $useErrors = \libxml_use_internal_errors(\true);
7787          $result    = $this->xpath->evaluate($expr);
7788          \libxml_use_internal_errors($useErrors);
7789          return $result;
7790      }
7791  	protected function foldConstantXPathExpression(array $m)
7792      {
7793          $expr = $m[0];
7794          if ($this->isConstantExpression($expr))
7795          {
7796              $result = $this->evaluate($expr);
7797              if ($this->canBeSerialized($result))
7798              {
7799                  $foldedExpr = XPath::export($result);
7800                  if (\strlen($foldedExpr) < \strlen($expr))
7801                      $expr = $foldedExpr;
7802              }
7803          }
7804          return $expr;
7805      }
7806  	protected function isConstantExpression($expr)
7807      {
7808          $expr = \preg_replace('("[^"]*"|\'[^\']*\')', '0', $expr);
7809          \preg_match_all('(\\w[-\\w]+(?=\\())', $expr, $m);
7810          if (\count(\array_diff($m[0], $this->supportedFunctions)) > 0)
7811              return \false;
7812          return !\preg_match('([^\\s\\-0-9a-z\\(-.]|\\.(?![0-9])|\\b[-a-z](?![-\\w]+\\()|\\(\\s*\\))i', $expr);
7813      }
7814  }
7815  
7816  /*
7817  * @package   s9e\TextFormatter
7818  * @copyright Copyright (c) 2010-2019 The s9e Authors
7819  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7820  */
7821  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
7822  use DOMElement;
7823  class OptimizeChoose extends AbstractChooseOptimization
7824  {
7825  	protected function adoptChildren(DOMElement $branch)
7826      {
7827          while ($branch->firstChild->firstChild)
7828              $branch->appendChild($branch->firstChild->removeChild($branch->firstChild->firstChild));
7829          $branch->removeChild($branch->firstChild);
7830      }
7831  	protected function matchBranches($childType)
7832      {
7833          $branches = $this->getBranches();
7834          if (!isset($branches[0]->$childType))
7835              return \false;
7836          $childNode = $branches[0]->$childType;
7837          foreach ($branches as $branch)
7838              if (!isset($branch->$childType) || !$this->isEqualNode($childNode, $branch->$childType))
7839                  return \false;
7840          return \true;
7841      }
7842  	protected function matchOnlyChild()
7843      {
7844          $branches = $this->getBranches();
7845          if (!isset($branches[0]->firstChild))
7846              return \false;
7847          $firstChild = $branches[0]->firstChild;
7848          if ($this->isXsl($firstChild, 'choose'))
7849              return \false;
7850          foreach ($branches as $branch)
7851          {
7852              if ($branch->childNodes->length !== 1 || !($branch->firstChild instanceof DOMElement))
7853                  return \false;
7854              if (!$this->isEqualTag($firstChild, $branch->firstChild))
7855                  return \false;
7856          }
7857          return \true;
7858      }
7859  	protected function moveFirstChildBefore()
7860      {
7861          $branches = $this->getBranches();
7862          $this->choose->parentNode->insertBefore(\array_pop($branches)->firstChild, $this->choose);
7863          foreach ($branches as $branch)
7864              $branch->removeChild($branch->firstChild);
7865      }
7866  	protected function moveLastChildAfter()
7867      {
7868          $branches = $this->getBranches();
7869          $node     = \array_pop($branches)->lastChild;
7870          if (isset($this->choose->nextSibling))
7871              $this->choose->parentNode->insertBefore($node, $this->choose->nextSibling);
7872          else
7873              $this->choose->parentNode->appendChild($node);
7874          foreach ($branches as $branch)
7875              $branch->removeChild($branch->lastChild);
7876      }
7877  	protected function optimizeChoose()
7878      {
7879          if ($this->hasOtherwise())
7880          {
7881              $this->optimizeCommonFirstChild();
7882              $this->optimizeCommonLastChild();
7883              $this->optimizeCommonOnlyChild();
7884              $this->optimizeEmptyOtherwise();
7885          }
7886          if ($this->isEmpty())
7887              $this->choose->parentNode->removeChild($this->choose);
7888          else
7889              $this->optimizeSingleBranch();
7890      }
7891  	protected function optimizeCommonFirstChild()
7892      {
7893          while ($this->matchBranches('firstChild'))
7894              $this->moveFirstChildBefore();
7895      }
7896  	protected function optimizeCommonLastChild()
7897      {
7898          while ($this->matchBranches('lastChild'))
7899              $this->moveLastChildAfter();
7900      }
7901  	protected function optimizeCommonOnlyChild()
7902      {
7903          while ($this->matchOnlyChild())
7904              $this->reparentChild();
7905      }
7906  	protected function optimizeEmptyOtherwise()
7907      {
7908          $query = 'xsl:otherwise[count(node()) = 0]';
7909          foreach ($this->xpath($query, $this->choose) as $otherwise)
7910              $this->choose->removeChild($otherwise);
7911      }
7912  	protected function optimizeSingleBranch()
7913      {
7914          $query = 'count(xsl:when) = 1 and not(xsl:otherwise)';
7915          if (!$this->xpath->evaluate($query, $this->choose))
7916              return;
7917          $when = $this->xpath('xsl:when', $this->choose)[0];
7918          $if   = $this->createElement('xsl:if');
7919          $if->setAttribute('test', $when->getAttribute('test'));
7920          while ($when->firstChild)
7921              $if->appendChild($when->removeChild($when->firstChild));
7922          $this->choose->parentNode->replaceChild($if, $this->choose);
7923      }
7924  	protected function reparentChild()
7925      {
7926          $branches  = $this->getBranches();
7927          $childNode = $branches[0]->firstChild->cloneNode();
7928          $childNode->appendChild($this->choose->parentNode->replaceChild($childNode, $this->choose));
7929          foreach ($branches as $branch)
7930              $this->adoptChildren($branch);
7931      }
7932  }
7933  
7934  /*
7935  * @package   s9e\TextFormatter
7936  * @copyright Copyright (c) 2010-2019 The s9e Authors
7937  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
7938  */
7939  namespace s9e\TextFormatter\Configurator\TemplateNormalizations;
7940  use DOMElement;
7941  use DOMText;
7942  class OptimizeChooseText extends AbstractChooseOptimization
7943  {
7944  	protected function adjustTextNodes($childType, $pos, $len = \PHP_INT_MAX)
7945      {
7946          foreach ($this->getBranches() as $branch)
7947          {
7948              $node            = $branch->$childType;
7949              $node->nodeValue = \substr($node->textContent, $pos, $len);
7950          }
7951      }
7952  	protected function getPrefixLength(array $strings)
7953      {
7954          $i      = 0;
7955          $len    = 0;
7956          $maxLen = \min(\array_map('strlen', $strings));
7957          while ($i < $maxLen)
7958          {
7959              $c = $strings[0][$i];
7960              foreach ($strings as $string)
7961                  if ($string[$i] !== $c)
7962                      break 2;
7963              $len = ++$i;
7964          }
7965          return $len;
7966      }
7967  	protected function getTextContent($childType)
7968      {
7969          $strings = [];
7970          foreach ($this->getBranches() as $branch)
7971          {
7972              if (!($branch->$childType instanceof DOMText))
7973                  return [];
7974              $strings[] = $branch->$childType->textContent;
7975          }
7976          return $strings;
7977      }
7978  	protected function optimizeChoose()
7979      {
7980          if (!$this->hasOtherwise())
7981              return;
7982          $this->optimizeLeadingText();
7983          $this->optimizeTrailingText();
7984      }
7985  	protected function optimizeLeadingText()
7986      {
7987          $strings = $this->getTextContent('firstChild');
7988          if (empty($strings))
7989              return;
7990          $len = $this->getPrefixLength($strings);
7991          if ($len)
7992          {
7993              $this->adjustTextNodes('firstChild', $len);
7994              $this->choose->parentNode->insertBefore(
7995                  $this->createText(\substr($strings[0], 0, $len)),
7996                  $this->choose
7997              );
7998          }
7999      }
8000  	protected function optimizeTrailingText()
8001      {
8002          $strings = $this->getTextContent('lastChild');
8003          if (empty($strings))
8004              return;
8005          $len = $this->getPrefixLength(\array_map('strrev', $strings));
8006          if ($len)
8007          {
8008              $this->adjustTextNodes('lastChild', 0, -$len);
8009              $this->choose->parentNode->insertBefore(
8010                  $this->createText(\substr($strings[0], -$len)),
8011                  $this->choose->nextSibling
8012              );
8013          }
8014      }
8015  }
8016  
8017  /*
8018  * @package   s9e\TextFormatter
8019  * @copyright Copyright (c) 2010-2019 The s9e Authors
8020  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8021  */
8022  namespace s9e\TextFormatter\Configurator\Collections;
8023  use RuntimeException;
8024  use s9e\TextFormatter\Configurator\Items\Attribute;
8025  use s9e\TextFormatter\Configurator\Validators\AttributeName;
8026  class AttributeCollection extends NormalizedCollection
8027  {
8028      protected $onDuplicateAction = 'replace';
8029  	protected function getAlreadyExistsException($key)
8030      {
8031          return new RuntimeException("Attribute '" . $key . "' already exists");
8032      }
8033  	protected function getNotExistException($key)
8034      {
8035          return new RuntimeException("Attribute '" . $key . "' does not exist");
8036      }
8037  	public function normalizeKey($key)
8038      {
8039          return AttributeName::normalize($key);
8040      }
8041  	public function normalizeValue($value)
8042      {
8043          return ($value instanceof Attribute)
8044               ? $value
8045               : new Attribute($value);
8046      }
8047  }
8048  
8049  /*
8050  * @package   s9e\TextFormatter
8051  * @copyright Copyright (c) 2010-2019 The s9e Authors
8052  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8053  */
8054  namespace s9e\TextFormatter\Configurator\Collections;
8055  use InvalidArgumentException;
8056  use s9e\TextFormatter\Configurator\Items\AttributeFilter;
8057  class AttributeFilterCollection extends NormalizedCollection
8058  {
8059  	public function get($key)
8060      {
8061          $key = $this->normalizeKey($key);
8062          if (!$this->exists($key))
8063              if ($key[0] === '#')
8064                  $this->set($key, self::getDefaultFilter(\substr($key, 1)));
8065              else
8066                  $this->set($key, new AttributeFilter($key));
8067          $filter = parent::get($key);
8068          $filter = clone $filter;
8069          return $filter;
8070      }
8071  	public static function getDefaultFilter($filterName)
8072      {
8073          $filterName = \ucfirst(\strtolower($filterName));
8074          $className  = 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilters\\' . $filterName . 'Filter';
8075          if (!\class_exists($className))
8076              throw new InvalidArgumentException("Unknown attribute filter '" . $filterName . "'");
8077          return new $className;
8078      }
8079  	public function normalizeKey($key)
8080      {
8081          if (\preg_match('/^#[a-z_0-9]+$/Di', $key))
8082              return \strtolower($key);
8083          if (\is_string($key) && \is_callable($key))
8084              return $key;
8085          throw new InvalidArgumentException("Invalid filter name '" . $key . "'");
8086      }
8087  	public function normalizeValue($value)
8088      {
8089          if ($value instanceof AttributeFilter)
8090              return $value;
8091          if (\is_callable($value))
8092              return new AttributeFilter($value);
8093          throw new InvalidArgumentException('Argument 1 passed to ' . __METHOD__ . ' must be a valid callback or an instance of s9e\\TextFormatter\\Configurator\\Items\\AttributeFilter');
8094      }
8095  }
8096  
8097  /*
8098  * @package   s9e\TextFormatter
8099  * @copyright Copyright (c) 2010-2019 The s9e Authors
8100  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8101  */
8102  namespace s9e\TextFormatter\Configurator\Collections;
8103  use InvalidArgumentException;
8104  class NormalizedList extends NormalizedCollection
8105  {
8106  	public function add($value, $void = \null)
8107      {
8108          return $this->append($value);
8109      }
8110  	public function append($value)
8111      {
8112          $value = $this->normalizeValue($value);
8113          $this->items[] = $value;
8114          return $value;
8115      }
8116  	public function delete($key)
8117      {
8118          parent::delete($key);
8119          $this->items = \array_values($this->items);
8120      }
8121  	public function insert($offset, $value)
8122      {
8123          $offset = $this->normalizeKey($offset);
8124          $value  = $this->normalizeValue($value);
8125          \array_splice($this->items, $offset, 0, [$value]);
8126          return $value;
8127      }
8128  	public function normalizeKey($key)
8129      {
8130          $normalizedKey = \filter_var(
8131              (\preg_match('(^-\\d+$)D', $key)) ? \count($this->items) + $key : $key,
8132              \FILTER_VALIDATE_INT,
8133              [
8134                  'options' => [
8135                      'min_range' => 0,
8136                      'max_range' => \count($this->items)
8137                  ]
8138              ]
8139          );
8140          if ($normalizedKey === \false)
8141              throw new InvalidArgumentException("Invalid offset '" . $key . "'");
8142          return $normalizedKey;
8143      }
8144  	public function offsetSet($offset, $value)
8145      {
8146          if ($offset === \null)
8147              $this->append($value);
8148          else
8149              parent::offsetSet($offset, $value);
8150      }
8151  	public function prepend($value)
8152      {
8153          $value = $this->normalizeValue($value);
8154          \array_unshift($this->items, $value);
8155          return $value;
8156      }
8157  	public function remove($value)
8158      {
8159          $keys = \array_keys($this->items, $this->normalizeValue($value));
8160          foreach ($keys as $k)
8161              unset($this->items[$k]);
8162          $this->items = \array_values($this->items);
8163          return \count($keys);
8164      }
8165  }
8166  
8167  /*
8168  * @package   s9e\TextFormatter
8169  * @copyright Copyright (c) 2010-2019 The s9e Authors
8170  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8171  */
8172  namespace s9e\TextFormatter\Configurator\Collections;
8173  use InvalidArgumentException;
8174  use RuntimeException;
8175  use s9e\TextFormatter\Configurator;
8176  use s9e\TextFormatter\Plugins\ConfiguratorBase;
8177  class PluginCollection extends NormalizedCollection
8178  {
8179      protected $configurator;
8180  	public function __construct(Configurator $configurator)
8181      {
8182          $this->configurator = $configurator;
8183      }
8184  	public function finalize()
8185      {
8186          foreach ($this->items as $plugin)
8187              $plugin->finalize();
8188      }
8189  	public function normalizeKey($pluginName)
8190      {
8191          if (!\preg_match('#^[A-Z][A-Za-z_0-9]+$#D', $pluginName))
8192              throw new InvalidArgumentException("Invalid plugin name '" . $pluginName . "'");
8193          return $pluginName;
8194      }
8195  	public function normalizeValue($value)
8196      {
8197          if (\is_string($value) && \class_exists($value))
8198              $value = new $value($this->configurator);
8199          if ($value instanceof ConfiguratorBase)
8200              return $value;
8201          throw new InvalidArgumentException('PluginCollection::normalizeValue() expects a class name or an object that implements s9e\\TextFormatter\\Plugins\\ConfiguratorBase');
8202      }
8203  	public function load($pluginName, array $overrideProps = [])
8204      {
8205          $pluginName = $this->normalizeKey($pluginName);
8206          $className  = 's9e\\TextFormatter\\Plugins\\' . $pluginName . '\\Configurator';
8207          if (!\class_exists($className))
8208              throw new RuntimeException("Class '" . $className . "' does not exist");
8209          $plugin = new $className($this->configurator, $overrideProps);
8210          $this->set($pluginName, $plugin);
8211          return $plugin;
8212      }
8213  	public function asConfig()
8214      {
8215          $plugins = parent::asConfig();
8216          foreach ($plugins as $pluginName => &$pluginConfig)
8217          {
8218              $plugin = $this->get($pluginName);
8219              $pluginConfig += $plugin->getBaseProperties();
8220              if ($pluginConfig['quickMatch'] === \false)
8221                  unset($pluginConfig['quickMatch']);
8222              if (!isset($pluginConfig['regexp']))
8223                  unset($pluginConfig['regexpLimit']);
8224              $className = 's9e\\TextFormatter\\Plugins\\' . $pluginName . '\\Parser';
8225              if ($pluginConfig['className'] === $className)
8226                  unset($pluginConfig['className']);
8227          }
8228          unset($pluginConfig);
8229          return $plugins;
8230      }
8231  }
8232  
8233  /*
8234  * @package   s9e\TextFormatter
8235  * @copyright Copyright (c) 2010-2019 The s9e Authors
8236  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8237  */
8238  namespace s9e\TextFormatter\Configurator\Collections;
8239  use RuntimeException;
8240  use s9e\TextFormatter\Configurator\Items\Tag;
8241  use s9e\TextFormatter\Configurator\Validators\TagName;
8242  class TagCollection extends NormalizedCollection
8243  {
8244      protected $onDuplicateAction = 'replace';
8245  	protected function getAlreadyExistsException($key)
8246      {
8247          return new RuntimeException("Tag '" . $key . "' already exists");
8248      }
8249  	protected function getNotExistException($key)
8250      {
8251          return new RuntimeException("Tag '" . $key . "' does not exist");
8252      }
8253  	public function normalizeKey($key)
8254      {
8255          return TagName::normalize($key);
8256      }
8257  	public function normalizeValue($value)
8258      {
8259          return ($value instanceof Tag)
8260               ? $value
8261               : new Tag($value);
8262      }
8263  }
8264  
8265  /*
8266  * @package   s9e\TextFormatter
8267  * @copyright Copyright (c) 2010-2019 The s9e Authors
8268  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8269  */
8270  namespace s9e\TextFormatter\Configurator\Collections;
8271  use s9e\TextFormatter\Configurator\Validators\TemplateParameterName;
8272  class TemplateParameterCollection extends NormalizedCollection
8273  {
8274  	public function normalizeKey($key)
8275      {
8276          return TemplateParameterName::normalize($key);
8277      }
8278  	public function normalizeValue($value)
8279      {
8280          return (string) $value;
8281      }
8282  }
8283  
8284  /*
8285  * @package   s9e\TextFormatter
8286  * @copyright Copyright (c) 2010-2019 The s9e Authors
8287  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8288  */
8289  namespace s9e\TextFormatter\Configurator\Items;
8290  use s9e\TextFormatter\Configurator\Traits\TemplateSafeness;
8291  class AttributeFilter extends Filter
8292  {
8293      use TemplateSafeness;
8294  	public function __construct($callback)
8295      {
8296          parent::__construct($callback);
8297          $this->resetParameters();
8298          $this->addParameterByName('attrValue');
8299      }
8300  	public function isSafeInJS()
8301      {
8302          $safeCallbacks = [
8303              'urlencode',
8304              'strtotime',
8305              'rawurlencode'
8306          ];
8307          if (\in_array($this->callback, $safeCallbacks, \true))
8308              return \true;
8309          return $this->isSafe('InJS');
8310      }
8311  }
8312  
8313  /*
8314  * @package   s9e\TextFormatter
8315  * @copyright Copyright (c) 2010-2019 The s9e Authors
8316  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8317  */
8318  namespace s9e\TextFormatter\Configurator\Items;
8319  class TagFilter extends Filter
8320  {
8321  	public function __construct($callback)
8322      {
8323          parent::__construct($callback);
8324          $this->resetParameters();
8325          $this->addParameterByName('tag');
8326      }
8327  }
8328  
8329  /*
8330  * @package   s9e\TextFormatter
8331  * @copyright Copyright (c) 2010-2019 The s9e Authors
8332  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8333  */
8334  namespace s9e\TextFormatter\Configurator\Collections;
8335  use InvalidArgumentException;
8336  use s9e\TextFormatter\Configurator\Items\ProgrammableCallback;
8337  abstract class FilterChain extends NormalizedList
8338  {
8339      abstract protected function getFilterClassName();
8340  	public function containsCallback(callable $callback)
8341      {
8342          $pc = new ProgrammableCallback($callback);
8343          $callback = $pc->getCallback();
8344          foreach ($this->items as $filter)
8345              if ($callback === $filter->getCallback())
8346                  return \true;
8347          return \false;
8348      }
8349  	public function normalizeValue($value)
8350      {
8351          $className  = $this->getFilterClassName();
8352          if ($value instanceof $className)
8353              return $value;
8354          if (!\is_callable($value))
8355              throw new InvalidArgumentException('Filter ' . \var_export($value, \true) . ' is neither callable nor an instance of ' . $className);
8356          return new $className($value);
8357      }
8358  }
8359  
8360  /*
8361  * @package   s9e\TextFormatter
8362  * @copyright Copyright (c) 2010-2019 The s9e Authors
8363  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8364  */
8365  namespace s9e\TextFormatter\Configurator\Collections;
8366  use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
8367  use s9e\TextFormatter\Configurator\Items\Regexp;
8368  class HostnameList extends NormalizedList
8369  {
8370  	public function asConfig()
8371      {
8372          if (empty($this->items))
8373              return \null;
8374          return new Regexp($this->getRegexp());
8375      }
8376  	public function getRegexp()
8377      {
8378          $hosts = [];
8379          foreach ($this->items as $host)
8380              $hosts[] = $this->normalizeHostmask($host);
8381          $regexp = RegexpBuilder::fromList(
8382              $hosts,
8383              [
8384                  'specialChars' => [
8385                      '*' => '.*',
8386                      '^' => '^',
8387                      '$' => '$'
8388                  ]
8389              ]
8390          );
8391          return '/' . $regexp . '/DSis';
8392      }
8393  	protected function normalizeHostmask($host)
8394      {
8395          if (\preg_match('#[\\x80-\xff]#', $host) && \function_exists('idn_to_ascii'))
8396          {
8397              $variant = (\defined('INTL_IDNA_VARIANT_UTS46')) ? \INTL_IDNA_VARIANT_UTS46 : 0;
8398              $host = \idn_to_ascii($host, 0, $variant);
8399          }
8400          if (\substr($host, 0, 1) === '*')
8401              $host = \ltrim($host, '*');
8402          else
8403              $host = '^' . $host;
8404          if (\substr($host, -1) === '*')
8405              $host = \rtrim($host, '*');
8406          else
8407              $host .= '$';
8408          return $host;
8409      }
8410  }
8411  
8412  /*
8413  * @package   s9e\TextFormatter
8414  * @copyright Copyright (c) 2010-2019 The s9e Authors
8415  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8416  */
8417  namespace s9e\TextFormatter\Configurator\Collections;
8418  use InvalidArgumentException;
8419  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\BooleanRulesGenerator;
8420  use s9e\TextFormatter\Configurator\RulesGenerators\Interfaces\TargetedRulesGenerator;
8421  class RulesGeneratorList extends NormalizedList
8422  {
8423  	public function normalizeValue($generator)
8424      {
8425          if (\is_string($generator))
8426          {
8427              $className = 's9e\\TextFormatter\\Configurator\\RulesGenerators\\' . $generator;
8428              if (\class_exists($className))
8429                  $generator = new $className;
8430          }
8431          if (!($generator instanceof BooleanRulesGenerator)
8432           && !($generator instanceof TargetedRulesGenerator))
8433              throw new InvalidArgumentException('Invalid rules generator ' . \var_export($generator, \true));
8434          return $generator;
8435      }
8436  }
8437  
8438  /*
8439  * @package   s9e\TextFormatter
8440  * @copyright Copyright (c) 2010-2019 The s9e Authors
8441  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8442  */
8443  namespace s9e\TextFormatter\Configurator\Collections;
8444  use InvalidArgumentException;
8445  use s9e\TextFormatter\Configurator\Helpers\RegexpBuilder;
8446  use s9e\TextFormatter\Configurator\Items\Regexp;
8447  class SchemeList extends NormalizedList
8448  {
8449  	public function asConfig()
8450      {
8451          return new Regexp('/^' . RegexpBuilder::fromList($this->items) . '$/Di');
8452      }
8453  	public function normalizeValue($scheme)
8454      {
8455          if (!\preg_match('#^[a-z][a-z0-9+\\-.]*$#Di', $scheme))
8456              throw new InvalidArgumentException("Invalid scheme name '" . $scheme . "'");
8457          return \strtolower($scheme);
8458      }
8459  }
8460  
8461  /*
8462  * @package   s9e\TextFormatter
8463  * @copyright Copyright (c) 2010-2019 The s9e Authors
8464  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8465  */
8466  namespace s9e\TextFormatter\Configurator\Collections;
8467  use s9e\TextFormatter\Configurator\TemplateCheck;
8468  class TemplateCheckList extends NormalizedList
8469  {
8470  	public function normalizeValue($check)
8471      {
8472          if (!($check instanceof TemplateCheck))
8473          {
8474              $className = 's9e\\TextFormatter\\Configurator\\TemplateChecks\\' . $check;
8475              $check     = new $className;
8476          }
8477          return $check;
8478      }
8479  }
8480  
8481  /*
8482  * @package   s9e\TextFormatter
8483  * @copyright Copyright (c) 2010-2019 The s9e Authors
8484  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8485  */
8486  namespace s9e\TextFormatter\Configurator\Collections;
8487  use s9e\TextFormatter\Configurator\TemplateNormalizations\AbstractNormalization;
8488  use s9e\TextFormatter\Configurator\TemplateNormalizations\Custom;
8489  class TemplateNormalizationList extends NormalizedList
8490  {
8491  	public function normalizeValue($value)
8492      {
8493          if ($value instanceof AbstractNormalization)
8494              return $value;
8495          if (\is_callable($value))
8496              return new Custom($value);
8497          $className = 's9e\\TextFormatter\\Configurator\\TemplateNormalizations\\' . $value;
8498          return new $className;
8499      }
8500  }
8501  
8502  /*
8503  * @package   s9e\TextFormatter
8504  * @copyright Copyright (c) 2010-2019 The s9e Authors
8505  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8506  */
8507  namespace s9e\TextFormatter\Configurator\Items\AttributeFilters;
8508  use s9e\TextFormatter\Configurator\Items\AttributeFilter;
8509  class UrlFilter extends AttributeFilter
8510  {
8511  	public function __construct()
8512      {
8513          parent::__construct('s9e\\TextFormatter\\Parser\\AttributeFilters\\UrlFilter::filter');
8514          $this->resetParameters();
8515          $this->addParameterByName('attrValue');
8516          $this->addParameterByName('urlConfig');
8517          $this->addParameterByName('logger');
8518          $this->setJS('UrlFilter.filter');
8519      }
8520  	public function isSafeInCSS()
8521      {
8522          return \true;
8523      }
8524  	public function isSafeInJS()
8525      {
8526          return \true;
8527      }
8528  	public function isSafeAsURL()
8529      {
8530          return \true;
8531      }
8532  }
8533  
8534  /*
8535  * @package   s9e\TextFormatter
8536  * @copyright Copyright (c) 2010-2019 The s9e Authors
8537  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8538  */
8539  namespace s9e\TextFormatter\Configurator\Collections;
8540  class AttributeFilterChain extends FilterChain
8541  {
8542  	public function getFilterClassName()
8543      {
8544          return 's9e\\TextFormatter\\Configurator\\Items\\AttributeFilter';
8545      }
8546  	public function normalizeValue($value)
8547      {
8548          if (\is_string($value) && \preg_match('(^#\\w+$)', $value))
8549              $value = AttributeFilterCollection::getDefaultFilter(\substr($value, 1));
8550          return parent::normalizeValue($value);
8551      }
8552  }
8553  
8554  /*
8555  * @package   s9e\TextFormatter
8556  * @copyright Copyright (c) 2010-2019 The s9e Authors
8557  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
8558  */
8559  namespace s9e\TextFormatter\Configurator\Collections;
8560  class TagFilterChain extends FilterChain
8561  {
8562  	public function getFilterClassName()
8563      {
8564          return 's9e\\TextFormatter\\Configurator\\Items\\TagFilter';
8565      }
8566  }


Generated: Wed Nov 11 20:33:01 2020 Cross-referenced by PHPXref 0.7.1