Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
SimpleStack
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 15
1640
0.00% covered (danger)
0.00%
0 / 1
 push
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 pop
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 replace
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 remove
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 isInScope
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isElementInScope
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 isOneOfSetInScope
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 isInListScope
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 isInButtonScope
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isInTableScope
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isInSelectScope
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
56
 isInSpecificScope
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 item
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 length
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasTemplate
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2namespace Wikimedia\RemexHtml\TreeBuilder;
3
4use 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 */
11class 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}