Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 69 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
SimpleStack | |
0.00% |
0 / 69 |
|
0.00% |
0 / 15 |
1640 | |
0.00% |
0 / 1 |
push | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
pop | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
replace | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
remove | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
isInScope | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isElementInScope | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
20 | |||
isOneOfSetInScope | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
isInListScope | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
isInButtonScope | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
isInTableScope | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isInSelectScope | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
56 | |||
isInSpecificScope | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
30 | |||
item | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
length | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
hasTemplate | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | namespace Wikimedia\RemexHtml\TreeBuilder; |
3 | |
4 | use Wikimedia\RemexHtml\HTMLData; |
5 | |
6 | /** |
7 | * An implementation of the "stack of open elements" which, unlike CachingStack, |
8 | * iterates through the stack in order to answer queries about which elements |
9 | * are in scope. This is presumably faster for best case input. |
10 | */ |
11 | class SimpleStack extends Stack { |
12 | private $elements; |
13 | |
14 | /** |
15 | * A 2-d array giving the element types which break a scope region for the |
16 | * default scope, i.e. the one for phrases of the form "has an X element |
17 | * in scope". |
18 | * @var array<string,array<string,bool>> |
19 | */ |
20 | private static $defaultScope = [ |
21 | HTMLData::NS_HTML => [ |
22 | 'applet' => true, |
23 | 'caption' => true, |
24 | 'html' => true, |
25 | 'table' => true, |
26 | 'td' => true, |
27 | 'th' => true, |
28 | 'marquee' => true, |
29 | 'object' => true, |
30 | 'template' => true, |
31 | ], |
32 | HTMLData::NS_MATHML => [ |
33 | 'mi' => true, |
34 | 'mo' => true, |
35 | 'mn' => true, |
36 | 'ms' => true, |
37 | 'mtext' => true, |
38 | 'annotation-xml' => true, |
39 | ], |
40 | HTMLData::NS_SVG => [ |
41 | 'foreignObject' => true, |
42 | 'desc' => true, |
43 | 'title' => true, |
44 | ], |
45 | ]; |
46 | |
47 | /** |
48 | * The element types which break the table scope. |
49 | * @var array<string,array<string,bool>> |
50 | */ |
51 | private static $tableScope = [ |
52 | HTMLData::NS_HTML => [ |
53 | 'html' => true, |
54 | 'table' => true, |
55 | 'template' => true, |
56 | ] |
57 | ]; |
58 | |
59 | /** |
60 | * The element types which break the list scope. This is lazy-initialised. |
61 | * @var array<string,array<string,bool>> |
62 | */ |
63 | private static $listScope; |
64 | |
65 | /** |
66 | * The element types which break the button scope. This is lazy-initialised. |
67 | * @var array<string,array<string,bool>> |
68 | */ |
69 | private static $buttonScope; |
70 | |
71 | public function push( Element $elt ) { |
72 | $n = count( $this->elements ); |
73 | $this->elements[$n] = $elt; |
74 | $this->current = $elt; |
75 | $elt->stackIndex = $n; |
76 | } |
77 | |
78 | public function pop() { |
79 | $elt = array_pop( $this->elements ); |
80 | $elt->stackIndex = null; |
81 | $n = count( $this->elements ); |
82 | $this->current = $n ? $this->elements[$n - 1] : null; |
83 | return $elt; |
84 | } |
85 | |
86 | public function replace( Element $oldElt, Element $elt ) { |
87 | $idx = $oldElt->stackIndex; |
88 | $this->elements[$idx] = $elt; |
89 | $oldElt->stackIndex = null; |
90 | $elt->stackIndex = $idx; |
91 | if ( $idx === count( $this->elements ) - 1 ) { |
92 | $this->current = $elt; |
93 | } |
94 | } |
95 | |
96 | public function remove( Element $elt ) { |
97 | $eltIndex = $elt->stackIndex; |
98 | $n = count( $this->elements ); |
99 | for ( $i = $eltIndex + 1; $i < $n; $i++ ) { |
100 | $this->elements[$i]->stackIndex--; |
101 | } |
102 | $elt->stackIndex = null; |
103 | } |
104 | |
105 | public function isInScope( $name ) { |
106 | return $this->isInSpecificScope( $name, self::$defaultScope ); |
107 | } |
108 | |
109 | public function isElementInScope( Element $elt ) { |
110 | for ( $i = count( $this->elements ) - 1; $i >= 0; $i-- ) { |
111 | $node = $this->elements[$i]; |
112 | if ( $node === $elt ) { |
113 | return true; |
114 | } |
115 | if ( isset( self::$defaultScope[$node->namespace][$node->name] ) ) { |
116 | return false; |
117 | } |
118 | } |
119 | return false; |
120 | } |
121 | |
122 | public function isOneOfSetInScope( $names ) { |
123 | for ( $i = count( $this->elements ) - 1; $i >= 0; $i-- ) { |
124 | $node = $this->elements[$i]; |
125 | if ( $node->namespace === HTMLData::NS_HTML && isset( $names[$node->name] ) ) { |
126 | return true; |
127 | } |
128 | if ( isset( self::$defaultScope[$node->namespace][$node->name] ) ) { |
129 | return false; |
130 | } |
131 | } |
132 | return false; |
133 | } |
134 | |
135 | public function isInListScope( $name ) { |
136 | if ( self::$listScope === null ) { |
137 | self::$listScope = self::$defaultScope; |
138 | self::$listScope[HTMLData::NS_HTML] += [ |
139 | 'ol' => true, |
140 | 'li' => true |
141 | ]; |
142 | } |
143 | return $this->isInSpecificScope( $name, self::$listScope ); |
144 | } |
145 | |
146 | public function isInButtonScope( $name ) { |
147 | if ( self::$buttonScope === null ) { |
148 | self::$buttonScope = self::$defaultScope; |
149 | self::$buttonScope[HTMLData::NS_HTML]['button'] = true; |
150 | } |
151 | return $this->isInSpecificScope( $name, self::$buttonScope ); |
152 | } |
153 | |
154 | public function isInTableScope( $name ) { |
155 | return $this->isInSpecificScope( $name, self::$tableScope ); |
156 | } |
157 | |
158 | public function isInSelectScope( $name ) { |
159 | for ( $i = count( $this->elements ) - 1; $i >= 0; $i-- ) { |
160 | $node = $this->elements[$i]; |
161 | if ( $node->namespace === HTMLData::NS_HTML && $node->name === $name ) { |
162 | return true; |
163 | } |
164 | if ( $node->namespace !== HTMLData::NS_HTML ) { |
165 | return false; |
166 | } |
167 | if ( $node->name !== 'optgroup' && $node->name !== 'option' ) { |
168 | return false; |
169 | } |
170 | } |
171 | return false; |
172 | } |
173 | |
174 | private function isInSpecificScope( $name, $set ) { |
175 | for ( $i = count( $this->elements ) - 1; $i >= 0; $i-- ) { |
176 | $node = $this->elements[$i]; |
177 | if ( $node->namespace === HTMLData::NS_HTML && $node->name === $name ) { |
178 | return true; |
179 | } |
180 | if ( isset( $set[$node->namespace][$node->name] ) ) { |
181 | return false; |
182 | } |
183 | } |
184 | return false; |
185 | } |
186 | |
187 | public function item( $idx ) { |
188 | return $this->elements[$idx]; |
189 | } |
190 | |
191 | public function length() { |
192 | return count( $this->elements ); |
193 | } |
194 | |
195 | public function hasTemplate() { |
196 | foreach ( $this->elements as $elt ) { |
197 | if ( $elt->namespace === HTMLData::NS_HTML && $elt->name === 'template' ) { |
198 | return true; |
199 | } |
200 | } |
201 | return false; |
202 | } |
203 | } |