]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blobdiff - vendor/wikimedia/css-sanitizer/src/Grammar/Quantifier.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / vendor / wikimedia / css-sanitizer / src / Grammar / Quantifier.php
diff --git a/vendor/wikimedia/css-sanitizer/src/Grammar/Quantifier.php b/vendor/wikimedia/css-sanitizer/src/Grammar/Quantifier.php
new file mode 100644 (file)
index 0000000..06c8740
--- /dev/null
@@ -0,0 +1,176 @@
+<?php
+/**
+ * @file
+ * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
+ */
+
+namespace Wikimedia\CSS\Grammar;
+
+use Wikimedia\CSS\Objects\ComponentValueList;
+use Wikimedia\CSS\Objects\Token;
+
+/**
+ * Matcher that matches a sub-Matcher a certain number of times
+ * ("?", "*", "+", "#", "{A,B}" multipliers)
+ * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#component-multipliers
+ */
+class Quantifier extends Matcher {
+       /** @var Matcher */
+       protected $matcher;
+
+       /** @var int */
+       protected $min, $max;
+
+       /** @var bool Whether matches are comma-separated */
+       protected $commas;
+
+       /**
+        * @param Matcher $matcher
+        * @param int|float $min Minimum number of matches
+        * @param int|float $max Maximum number of matches
+        * @param bool $commas Whether matches are comma-separated
+        */
+       public function __construct( Matcher $matcher, $min, $max, $commas ) {
+               $this->matcher = $matcher;
+               $this->min = $min;
+               $this->max = $max;
+               $this->commas = (bool)$commas;
+       }
+
+       /**
+        * Implements "?": 0 or 1 matches
+        * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#mult-opt
+        * @param Matcher $matcher
+        * @return static
+        */
+       public static function optional( Matcher $matcher ) {
+               return new static( $matcher, 0, 1, false );
+       }
+
+       /**
+        * Implements "*": 0 or more matches
+        * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#mult-zero-plus
+        * @param Matcher $matcher
+        * @return static
+        */
+       public static function star( Matcher $matcher ) {
+               return new static( $matcher, 0, INF, false );
+       }
+
+       /**
+        * Implements "+": 1 or more matches
+        * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#mult-one-plus
+        * @param Matcher $matcher
+        * @return static
+        */
+       public static function plus( Matcher $matcher ) {
+               return new static( $matcher, 1, INF, false );
+       }
+
+       /**
+        * Implements "{A,B}": Between A and B matches
+        * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#mult-num-range
+        * @param Matcher $matcher
+        * @param int|float $min Minimum number of matches
+        * @param int|float $max Maximum number of matches
+        * @return static
+        */
+       public static function count( Matcher $matcher, $min, $max ) {
+               return new static( $matcher, $min, $max, false );
+       }
+
+       /**
+        * Implements "#" and "#{A,B}": Between A and B matches, comma-separated
+        * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#mult-comma
+        * @param Matcher $matcher
+        * @param int|float $min Minimum number of matches
+        * @param int|float $max Maximum number of matches
+        * @return static
+        */
+       public static function hash( Matcher $matcher, $min = 1, $max = INF ) {
+               return new static( $matcher, $min, $max, true );
+       }
+
+       protected function generateMatches( ComponentValueList $values, $start, array $options ) {
+               $used = [];
+
+               // Maintain a stack of matches for backtracking purposes.
+               $stack = [
+                       [ new Match( $values, $start, 0 ), $this->matcher->generateMatches( $values, $start, $options ) ]
+               ];
+               do {
+                       /** @var $lastMatch Match */
+                       /** @var $iter \Iterator<Match> */
+                       list( $lastMatch, $iter ) = $stack[count( $stack ) - 1];
+
+                       // If the top of the stack has no more matches, pop it, maybe
+                       // yield the last matched position, and loop.
+                       if ( !$iter->valid() ) {
+                               array_pop( $stack );
+                               $ct = count( $stack );
+                               $pos = $lastMatch->getNext();
+                               if ( $ct >= $this->min && $ct <= $this->max ) {
+                                       $newMatch = $this->makeMatch( $values, $start, $pos, $lastMatch, $stack );
+                                       $mid = $newMatch->getUniqueID();
+                                       if ( !isset( $used[$mid] ) ) {
+                                               $used[$mid] = 1;
+                                               yield $newMatch;
+                                       }
+                               }
+                               continue;
+                       }
+
+                       // Find the next match for the current top of the stack.
+                       $match = $iter->current();
+                       $iter->next();
+
+                       // Quantifiers don't work well when the quantified thing can be empty.
+                       if ( $match->getLength() === 0 ) {
+                               throw new \UnexpectedValueException( 'Empty match in quantifier!' );
+                       }
+
+                       $nextFrom = $match->getNext();
+
+                       // There can only be more matches after this one if we haven't
+                       // reached our maximum yet.
+                       $canBeMore = count( $stack ) < $this->max;
+
+                       // Commas are slightly tricky:
+                       //  1. If there is a following comma, start the next Matcher after it.
+                       //  2. If not, there can't be any more Matchers following.
+                       // And in either case optional whitespace is always allowed.
+                       if ( $this->commas ) {
+                               $n = $nextFrom;
+                               if ( isset( $values[$n] ) && $values[$n] instanceof Token &&
+                                       $values[$n]->type() === Token::T_WHITESPACE
+                               ) {
+                                       $n = $this->next( $values, $n, [ 'skip-whitespace' => true ] + $options );
+                               }
+                               if ( isset( $values[$n] ) && $values[$n] instanceof Token &&
+                                       $values[$n]->type() === Token::T_COMMA
+                               ) {
+                                       $nextFrom = $this->next( $values, $n, [ 'skip-whitespace' => true ] + $options );
+                               } else {
+                                       $canBeMore = false;
+                               }
+                       }
+
+                       // If there can be more matches, push another one onto the stack
+                       // and try it. Otherwise yield and continue with the current match.
+                       if ( $canBeMore ) {
+                               $stack[] = [ $match, $this->matcher->generateMatches( $values, $nextFrom, $options ) ];
+                       } else {
+                               $ct = count( $stack );
+                               $pos = $match->getNext();
+                               if ( $ct >= $this->min && $ct <= $this->max ) {
+                                       $newMatch = $this->makeMatch( $values, $start, $pos, $match, $stack );
+                                       $mid = $newMatch->getUniqueID();
+                                       if ( !isset( $used[$mid] ) ) {
+                                               $used[$mid] = 1;
+                                               yield $newMatch;
+                                       }
+                               }
+                       }
+               } while ( $stack );
+       }
+}