[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

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

   1  <?php
   2  
   3  /**
   4  * @package   s9e\TextFormatter
   5  * @copyright Copyright (c) 2010-2022 The s9e authors
   6  * @license   http://www.opensource.org/licenses/mit-license.php The MIT License
   7  */
   8  namespace s9e\TextFormatter\Configurator\Helpers;
   9  
  10  use RuntimeException;
  11  use s9e\TextFormatter\Configurator\RecursiveParser;
  12  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\BooleanFunctions;
  13  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\BooleanOperators;
  14  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\Comparisons;
  15  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\Core;
  16  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\Math;
  17  use s9e\TextFormatter\Configurator\RendererGenerators\PHP\XPathConvertor\Convertors\SingleByteStringFunctions;
  18  use s9e\TextFormatter\Utils\XPath;
  19  
  20  abstract class XPathHelper
  21  {
  22      /**
  23      * Decode strings inside of an XPath expression
  24      *
  25      * @param  string $expr
  26      * @return string
  27      */
  28  	public static function decodeStrings($expr)
  29      {
  30          return preg_replace_callback(
  31              '(\'[^\']*+\'|"[^"]*+")',
  32              function ($m)
  33              {
  34                  return $m[0][0] . hex2bin(substr($m[0], 1, -1)) . $m[0][0];
  35              },
  36              $expr
  37          );
  38      }
  39  
  40      /**
  41      * Encode strings inside of an XPath expression
  42      *
  43      * @param  string $expr
  44      * @return string
  45      */
  46  	public static function encodeStrings($expr)
  47      {
  48          return preg_replace_callback(
  49              '(\'[^\']*+\'|"[^"]*+")',
  50              function ($m)
  51              {
  52                  return $m[0][0] . bin2hex(substr($m[0], 1, -1)) . $m[0][0];
  53              },
  54              $expr
  55          );
  56      }
  57  
  58      /**
  59      * Return the list of variables used in a given XPath expression
  60      *
  61      * @param  string $expr XPath expression
  62      * @return array        Alphabetically sorted list of unique variable names
  63      */
  64  	public static function getVariables($expr)
  65      {
  66          // First, remove strings' contents to prevent false-positives
  67          $expr = preg_replace('/(["\']).*?\\1/s', '$1$1', $expr);
  68  
  69          // Capture all the variable names
  70          preg_match_all('/\\$(\\w+)/', $expr, $matches);
  71  
  72          // Dedupe and sort names
  73          $varNames = array_unique($matches[1]);
  74          sort($varNames);
  75  
  76          return $varNames;
  77      }
  78  
  79      /**
  80      * Determine whether given XPath expression definitely evaluates to a number
  81      *
  82      * @param  string $expr XPath expression
  83      * @return bool         Whether given XPath expression definitely evaluates to a number
  84      */
  85  	public static function isExpressionNumeric($expr)
  86      {
  87          // Detect simple arithmetic operations
  88          if (preg_match('(^([$@][-\\w]++|-?[.\\d]++)(?: *(?:[-*+]|div) *(?1))+$)', $expr))
  89          {
  90              return true;
  91          }
  92  
  93          // Try parsing the expression as a math expression
  94          try
  95          {
  96              return (bool) self::getXPathParser()->parse($expr, 'Math');
  97          }
  98          catch (RuntimeException $e)
  99          {
 100              // Do nothing
 101          }
 102  
 103          return false;
 104      }
 105  
 106      /**
 107      * Remove extraneous space in a given XPath expression
 108      *
 109      * @param  string $expr Original XPath expression
 110      * @return string       Minified XPath expression
 111      */
 112  	public static function minify($expr)
 113      {
 114          $expr = trim($expr);
 115  
 116          // Test whether there's any characters that can be removed
 117          if (!preg_match('([\\s\\)])', $expr))
 118          {
 119              return $expr;
 120          }
 121  
 122          // Temporarily encode the content of literal strings
 123          $expr = self::encodeStrings(trim($expr));
 124  
 125          // Normalize whitespace to a single space
 126          $expr = preg_replace('(\\s+)', ' ', $expr);
 127  
 128          $regexps = [
 129              // Remove the space between a non-word character and a word character
 130              '([-a-z_0-9]\\K (?=[^-a-z_0-9]))i',
 131              '([^-a-z_0-9]\\K (?=[-a-z_0-9]))i',
 132  
 133              // Remove the space between two non-word characters as long as they're not two -
 134              '((?!- -)[^-a-z_0-9]\\K (?=[^-a-z_0-9]))i',
 135  
 136              // Remove the space between a - and a word character as long as there's a space before -
 137              '( -\\K (?=[a-z_0-9]))i',
 138  
 139              // Remove the space between an operator and the next token if it's a left parenthesis
 140              '([ \\)](?:and|div|mod|or)\\K (?=\\())',
 141  
 142              // Remove the space after a number
 143              '(\\b\\d+\\K )'
 144          ];
 145          $expr = preg_replace($regexps, '', $expr);
 146  
 147          // Remove consecutive parentheses where redundant
 148          $expr = self::removeRedundantParentheses($expr);
 149  
 150          // Restore the literals
 151          $expr = self::decodeStrings($expr);
 152  
 153          return $expr;
 154      }
 155  
 156      /**
 157      * Remove consecutive parentheses where redundant
 158      */
 159  	protected static function removeRedundantParentheses(string $expr): string
 160      {
 161          // Add parentheses around the original expression and terminate the expression with a space
 162          preg_match_all('(([\\(\\)])|[^\\(\\)]++)', '(' . $expr . ') ', $m);
 163          $tokens = $m[0];
 164          $parens = array_filter($m[1]);
 165  
 166          // Iterate over parentheses and remove the inner pair when consecutive parentheses are found
 167          $depth = 0;
 168          $left  = [];
 169          foreach ($parens as $k => $token)
 170          {
 171              if ($token === '(')
 172              {
 173                  $left[$depth++] = $k;
 174              }
 175              elseif (--$depth > 0 && $tokens[$k + 1] === ')' && $left[$depth - 1] === $left[$depth] - 1)
 176              {
 177                  unset($tokens[$k], $tokens[$left[$depth]]);
 178              }
 179          }
 180  
 181          // Remove the extra parentheses as well as the last token before serializing them
 182          return implode('', array_slice($tokens, 1, -2));
 183      }
 184  
 185      /**
 186      * Parse an XPath expression that is composed entirely of equality tests between a variable part
 187      * and a constant part
 188      *
 189      * @param  string      $expr
 190      * @return array|false
 191      */
 192  	public static function parseEqualityExpr($expr)
 193      {
 194          // Match an equality between a variable and a literal or the concatenation of strings
 195          $eq = '(?<equality>'
 196              . '(?<key>@[-\\w]+|\\$\\w+|\\.)'
 197              . '(?<operator>\\s*=\\s*)'
 198              . '(?:'
 199              . '(?<literal>(?<string>"[^"]*"|\'[^\']*\')|0|[1-9][0-9]*)'
 200              . '|'
 201              . '(?<concat>concat\\(\\s*(?&string)\\s*(?:,\\s*(?&string)\\s*)+\\))'
 202              . ')'
 203              . '|'
 204              . '(?:(?<literal>(?&literal))|(?<concat>(?&concat)))(?&operator)(?<key>(?&key))'
 205              . ')';
 206  
 207          // Match a string that is entirely composed of equality checks separated with "or"
 208          $regexp = '(^(?J)\\s*' . $eq . '\\s*(?:or\\s*(?&equality)\\s*)*$)';
 209          if (!preg_match($regexp, $expr))
 210          {
 211              return false;
 212          }
 213  
 214          preg_match_all("((?J)$eq)", $expr, $matches, PREG_SET_ORDER);
 215  
 216          $map = [];
 217          foreach ($matches as $m)
 218          {
 219              $key   = $m['key'];
 220              $value = (!empty($m['concat']))
 221                     ? self::evaluateConcat($m['concat'])
 222                     : self::evaluateLiteral($m['literal']);
 223  
 224              $map[$key][] = $value;
 225          }
 226  
 227          return $map;
 228      }
 229  
 230      /**
 231      * Evaluate a concat() expression where all arguments are string literals
 232      *
 233      * @param  string $expr concat() expression
 234      * @return string       Expression's value
 235      */
 236  	protected static function evaluateConcat($expr)
 237      {
 238          preg_match_all('(\'[^\']*\'|"[^"]*")', $expr, $strings);
 239  
 240          $value = '';
 241          foreach ($strings[0] as $string)
 242          {
 243              $value .= substr($string, 1, -1);
 244          }
 245  
 246          return $value;
 247      }
 248  
 249      /**
 250      * Evaluate an XPath literal
 251      *
 252      * @param  string $expr XPath literal
 253      * @return string       Literal's string value
 254      */
 255  	protected static function evaluateLiteral($expr)
 256      {
 257          if ($expr[0] === '"' || $expr[0] === "'")
 258          {
 259              $expr = substr($expr, 1, -1);
 260          }
 261  
 262          return $expr;
 263      }
 264  
 265      /**
 266      * Generate and return a cached XPath parser with a default set of matchers
 267      *
 268      * @return RecursiveParser
 269      */
 270  	protected static function getXPathParser()
 271      {
 272          static $parser;
 273          if (!isset($parser))
 274          {
 275              $parser     = new RecursiveParser;
 276              $matchers   = [];
 277              $matchers[] = new BooleanFunctions($parser);
 278              $matchers[] = new BooleanOperators($parser);
 279              $matchers[] = new Comparisons($parser);
 280              $matchers[] = new Core($parser);
 281              $matchers[] = new Math($parser);
 282              $matchers[] = new SingleByteStringFunctions($parser);
 283  
 284              $parser->setMatchers($matchers);
 285          }
 286  
 287          return $parser;
 288      }
 289  }


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1