Seditio Source
Root |
./othercms/phpBB3/vendor/s9e/text-formatter/src/Configurator/RecursiveParser.php
<?php declare(strict_types=1);

/**
* @package   s9e\TextFormatter
* @copyright Copyright (c) 2010-2021 The s9e authors
* @license   http://www.opensource.org/licenses/mit-license.php The MIT License
*/
namespace s9e\TextFormatter\Configurator;

use
RuntimeException;
use
s9e\TextFormatter\Configurator\RecursiveParser\MatcherInterface;

class
RecursiveParser
{
   
/**
    * @var array Callback associated with each match name
    */
   
protected $callbacks = [];

   
/**
    * @var array Match names associated with each group
    */
   
protected $groupMatches = [];

   
/**
    * @var array Groups associated with each match name
    */
   
protected $matchGroups = [];

   
/**
    * @var string Regexp used to match input
    */
   
protected $regexp;

   
/**
    * Parse given string
    *
    * @param  string $str
    * @param  string $name Allowed match, either match name or group name (default: allow all)
    * @return mixed
    */
   
public function parse(string $str, string $name = '')
    {
       
$regexp = $this->regexp;
        if (
$name !== '')
        {
           
$restrict = (isset($this->groupMatches[$name])) ? implode('|', $this->groupMatches[$name]) : $name;
           
$regexp   = preg_replace('(\\(\\?<(?!(?:' . $restrict . '|\\w+\\d+)>))', '(*F)$0', $regexp);
        }

       
preg_match($regexp, $str, $m);
        if (!isset(
$m['MARK']))
        {
            throw new
RuntimeException('Cannot parse ' . var_export($str, true));
        }

       
$name = $m['MARK'];
       
$args = $this->getArguments($m, $name);

        return [
           
'groups' => $this->matchGroups[$name] ?? [],
           
'match'  => $name,
           
'value'  => call_user_func_array($this->callbacks[$name], $args)
        ];
    }

   
/**
    * Set the list of matchers used by this parser
    *
    * @param  MatcherInterface[]
    * @return void
    */
   
public function setMatchers(array $matchers): void
   
{
       
$matchRegexps       = [];
       
$this->groupMatches = [];
       
$this->matchGroups  = [];
        foreach (
$this->getMatchersConfig($matchers) as $matchName => $matchConfig)
        {
            foreach (
$matchConfig['groups'] as $group)
            {
               
$this->groupMatches[$group][] = $matchName;
            }

           
$regexp = $matchConfig['regexp'];
           
$regexp = $this->insertCaptureNames($matchName , $regexp);
           
$regexp = str_replace(' ', '\\s*+', $regexp);
           
$regexp = '(?<' . $matchName  . '>' . $regexp . ')(*:' . $matchName  . ')';

           
$matchRegexps[]                = $regexp;
           
$this->callbacks[$matchName]   = $matchConfig['callback'];
           
$this->matchGroups[$matchName] = $matchConfig['groups'];
        }

       
$groupRegexps = [];
        foreach (
$this->groupMatches as $group => $names)
        {
           
$groupRegexps[] = '(?<' . $group . '>(?&' . implode(')|(?&', $names) . '))';
        }

       
$this->regexp = '((?(DEFINE)' . implode('', $groupRegexps). ')'
                     
. '^(?:' . implode('|', $matchRegexps) . ')$)s';
    }

   
/**
    * Get the list of arguments produced by a regexp's match
    *
    * @param  string[] $matches Regexp matches
    * @param  string   $name    Regexp name
    * @return string[]
    */
   
protected function getArguments(array $matches, string $name): array
    {
       
$args = [];
       
$i    = 0;
        while (isset(
$matches[$name . $i]))
        {
           
$args[] = $matches[$name . $i];
            ++
$i;
        }

        return
$args;
    }

   
/**
    * Collect, normalize, sort and return the config for all matchers
    *
    * @param  MatcherInterface[] $matchers
    * @return array
    */
   
protected function getMatchersConfig(array $matchers): array
    {
       
$matchersConfig = [];
        foreach (
$matchers as $matcher)
        {
            foreach (
$matcher->getMatchers() as $matchName => $matchConfig)
            {
                if (
is_string($matchConfig))
                {
                   
$matchConfig = ['regexp' => $matchConfig];
                }
               
$parts       = explode(':', $matchName);
               
$matchName   = array_pop($parts);
               
$matchConfig += [
                   
'callback' => [$matcher, 'parse' . $matchName],
                   
'groups'   => [],
                   
'order'    => 0
               
];
               
$matchConfig['name']   = $matchName;
               
$matchConfig['groups'] = array_unique(array_merge($matchConfig['groups'], $parts));
               
sort($matchConfig['groups']);

               
$matchersConfig[$matchName] = $matchConfig;
            }
        }
       
uasort($matchersConfig, 'static::sortMatcherConfig');

        return
$matchersConfig;
    }

   
/**
    * Insert capture names into given regexp
    *
    * @param  string $name   Name of the regexp, used to name captures
    * @param  string $regexp Original regexp
    * @return string         Modified regexp
    */
   
protected function insertCaptureNames(string $name, string $regexp): string
   
{
       
$i = 0;

        return
preg_replace_callback(
           
'((?<!\\\\)\\((?!\\?))',
            function (
$m) use (&$i, $name)
            {
                return
'(?<' . $name . $i++ . '>';
            },
           
$regexp
       
);
    }

   
/**
    * Compare two matchers' config
    *
    * @param  array $a
    * @param  array $b
    * @return integer
    */
   
protected static function sortMatcherConfig(array $a, array $b): int
   
{
        if (
$a['order'] !== $b['order'])
        {
            return
$a['order'] - $b['order'];
        }

        return
strcmp($a['name'], $b['name']);
    }
}