]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - vendor/wikimedia/css-sanitizer/src/Grammar/Juxtaposition.php
MediaWiki 1.30.2
[autoinstallsdev/mediawiki.git] / vendor / wikimedia / css-sanitizer / src / Grammar / Juxtaposition.php
1 <?php
2 /**
3  * @file
4  * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
5  */
6
7 namespace Wikimedia\CSS\Grammar;
8
9 use Wikimedia\CSS\Objects\ComponentValueList;
10 use Wikimedia\CSS\Objects\Token;
11 use Wikimedia\CSS\Util;
12
13 /**
14  * Matcher that groups other matchers (juxtaposition)
15  * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#component-combinators
16  * @see https://www.w3.org/TR/2016/CR-css-values-3-20160929/#comb-comma
17  */
18 class Juxtaposition extends Matcher {
19         /** @var Matcher[] */
20         protected $matchers;
21
22         /** @var bool Whether non-empty matches are comma-separated */
23         protected $commas;
24
25         /**
26          * @param Matcher[] $matchers
27          * @param bool $commas Whether matches are comma-separated
28          */
29         public function __construct( array $matchers, $commas = false ) {
30                 Util::assertAllInstanceOf( $matchers, Matcher::class, '$matchers' );
31                 $this->matchers = $matchers;
32                 $this->commas = (bool)$commas;
33         }
34
35         protected function generateMatches( ComponentValueList $values, $start, array $options ) {
36                 $used = [];
37
38                 // Match each of our matchers in turn, pushing each one onto a stack as
39                 // we process it and popping a match once its exhausted.
40                 $stack = [
41                         [
42                                 new Match( $values, $start, 0 ),
43                                 $start,
44                                 $this->matchers[0]->generateMatches( $values, $start, $options ),
45                                 false
46                         ]
47                 ];
48                 do {
49                         /** @var $lastMatch Match */
50                         /** @var $lastEnd int */
51                         /** @var $iter \Iterator<Match> */
52                         /** @var $needEmpty bool */
53                         list( $lastMatch, $lastEnd, $iter, $needEmpty ) = $stack[count( $stack ) - 1];
54
55                         // If the top of the stack has no more matches, pop it and loop.
56                         if ( !$iter->valid() ) {
57                                 array_pop( $stack );
58                                 continue;
59                         }
60
61                         // Find the next match for the current top of the stack.
62                         $match = $iter->current();
63                         $iter->next();
64
65                         // In some cases, we can only match if the rest of the pattern
66                         // is empty. If we're in that situation, ignore all non-empty
67                         // matches.
68                         if ( $needEmpty && $match->getLength() !== 0 ) {
69                                 continue;
70                         }
71
72                         $thisEnd = $nextFrom = $match->getNext();
73
74                         // Dealing with commas is a bit tricky. There are three cases:
75                         //  1. If the current match is empty, don't look for a following
76                         //     comma now and reset $thisEnd to $lastEnd.
77                         //  2. If there is a comma following, update $nextFrom to be after
78                         //     the comma.
79                         //  3. If there's no comma following, every subsequent Matcher must
80                         //     be empty in order for the group as a whole to match, so set
81                         //     the flag.
82                         // Unlike '#', this doesn't specify skipping whitespace around the
83                         // commas if the production isn't already skipping whitespace.
84                         if ( $this->commas ) {
85                                 if ( $match->getLength() === 0 ) {
86                                         $thisEnd = $lastEnd;
87                                 } else {
88                                         if ( isset( $values[$nextFrom] ) && $values[$nextFrom] instanceof Token &&
89                                                 $values[$nextFrom]->type() === Token::T_COMMA
90                                         ) {
91                                                 $nextFrom = $this->next( $values, $nextFrom, $options );
92                                         } else {
93                                                 $needEmpty = true;
94                                         }
95                                 }
96                         }
97
98                         // If we ran out of Matchers, yield the final position. Otherwise
99                         // push the next matcher onto the stack.
100                         if ( count( $stack ) >= count( $this->matchers ) ) {
101                                 $newMatch = $this->makeMatch( $values, $start, $thisEnd, $match, $stack );
102                                 $mid = $newMatch->getUniqueID();
103                                 if ( !isset( $used[$mid] ) ) {
104                                         $used[$mid] = 1;
105                                         yield $newMatch;
106                                 }
107                         } else {
108                                 $stack[] = [
109                                         $match,
110                                         $thisEnd,
111                                         $this->matchers[count( $stack )]->generateMatches( $values, $nextFrom, $options ),
112                                         $needEmpty
113                                 ];
114                         }
115                 } while ( $stack );
116         }
117 }