[ Index ]

PHP Cross Reference of phpBB-3.2.8-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(