Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
121 / 121 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
1 / 1 |
InTable | |
100.00% |
121 / 121 |
|
100.00% |
4 / 4 |
41 | |
100.00% |
1 / 1 |
characters | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
2 | |||
startTag | |
100.00% |
74 / 74 |
|
100.00% |
1 / 1 |
22 | |||
endTag | |
100.00% |
30 / 30 |
|
100.00% |
1 / 1 |
16 | |||
endDocument | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace Wikimedia\RemexHtml\TreeBuilder; |
4 | |
5 | use Wikimedia\RemexHtml\Tokenizer\Attributes; |
6 | use Wikimedia\RemexHtml\Tokenizer\PlainAttributes; |
7 | |
8 | /** |
9 | * The "in table" insertion mode |
10 | */ |
11 | class InTable extends InsertionMode { |
12 | /** |
13 | * The tag names that are cleared when we "clear the stack back to a table context" |
14 | * @var array<string,bool> |
15 | */ |
16 | private static $tableContext = [ |
17 | 'table' => true, |
18 | 'template' => true, |
19 | 'html' => true |
20 | ]; |
21 | |
22 | public function characters( $text, $start, $length, $sourceStart, $sourceLength ) { |
23 | $allowed = [ |
24 | 'table' => true, |
25 | 'tbody' => true, |
26 | 'tfoot' => true, |
27 | 'thead' => true, |
28 | 'tr' => true |
29 | ]; |
30 | if ( isset( $allowed[$this->builder->stack->current->htmlName] ) ) { |
31 | $this->builder->pendingTableCharacters = []; |
32 | $this->dispatcher->switchAndSave( Dispatcher::IN_TABLE_TEXT ) |
33 | ->characters( $text, $start, $length, $sourceStart, $sourceLength ); |
34 | } else { |
35 | $this->builder->error( 'unexpected text in table, fostering', $sourceStart ); |
36 | $this->builder->fosterParenting = true; |
37 | $this->dispatcher->inBody->characters( |
38 | $text, $start, $length, $sourceStart, $sourceLength ); |
39 | $this->builder->fosterParenting = false; |
40 | } |
41 | } |
42 | |
43 | public function startTag( $name, Attributes $attrs, $selfClose, $sourceStart, $sourceLength ) { |
44 | $builder = $this->builder; |
45 | $dispatcher = $this->dispatcher; |
46 | $stack = $builder->stack; |
47 | |
48 | switch ( $name ) { |
49 | case 'caption': |
50 | $builder->clearStackBack( self::$tableContext, $sourceStart ); |
51 | $builder->afe->insertMarker(); |
52 | $dispatcher->switchMode( Dispatcher::IN_CAPTION ); |
53 | $builder->insertElement( $name, $attrs, false, |
54 | $sourceStart, $sourceLength ); |
55 | break; |
56 | |
57 | case 'colgroup': |
58 | $builder->clearStackBack( self::$tableContext, $sourceStart ); |
59 | $dispatcher->switchMode( Dispatcher::IN_COLUMN_GROUP ); |
60 | $builder->insertElement( $name, $attrs, false, |
61 | $sourceStart, $sourceLength ); |
62 | break; |
63 | |
64 | case 'col': |
65 | $builder->clearStackBack( self::$tableContext, $sourceStart ); |
66 | $builder->insertElement( 'colgroup', new PlainAttributes, false, |
67 | $sourceStart, 0 ); |
68 | $dispatcher->switchMode( Dispatcher::IN_COLUMN_GROUP ) |
69 | ->startTag( $name, $attrs, $selfClose, $sourceStart, $sourceLength ); |
70 | break; |
71 | |
72 | case 'tbody': |
73 | case 'tfoot': |
74 | case 'thead': |
75 | $builder->clearStackBack( self::$tableContext, $sourceStart ); |
76 | $builder->insertElement( $name, $attrs, false, |
77 | $sourceStart, $sourceLength ); |
78 | $dispatcher->switchMode( Dispatcher::IN_TABLE_BODY ); |
79 | break; |
80 | |
81 | case 'td': |
82 | case 'th': |
83 | case 'tr': |
84 | $builder->clearStackBack( self::$tableContext, $sourceStart ); |
85 | $builder->insertElement( 'tbody', new PlainAttributes, false, |
86 | $sourceStart, $sourceLength ); |
87 | $dispatcher->switchMode( Dispatcher::IN_TABLE_BODY ) |
88 | ->startTag( $name, $attrs, $selfClose, $sourceStart, $sourceLength ); |
89 | break; |
90 | |
91 | case 'table': |
92 | $builder->error( 'unexpected <table> in table', $sourceStart ); |
93 | if ( !$stack->isInTableScope( 'table' ) ) { |
94 | // Ignore |
95 | break; |
96 | } |
97 | $builder->popAllUpToName( 'table', $sourceStart, 0 ); |
98 | $dispatcher->reset() |
99 | ->startTag( $name, $attrs, $selfClose, $sourceStart, $sourceLength ); |
100 | break; |
101 | |
102 | case 'style': |
103 | case 'script': |
104 | case 'template': |
105 | $dispatcher->inHead->startTag( $name, $attrs, $selfClose, $sourceStart, $sourceLength ); |
106 | break; |
107 | |
108 | case 'form': |
109 | if ( $stack->hasTemplate() || $builder->formElement !== null ) { |
110 | $builder->error( 'invalid form in table, ignoring', $sourceStart ); |
111 | // Ignore |
112 | break; |
113 | } |
114 | $builder->error( 'invalid form in table, inserting void element', $sourceStart ); |
115 | $elt = $builder->insertElement( 'form', $attrs, true, |
116 | $sourceStart, $sourceLength ); |
117 | $builder->formElement = $elt; |
118 | break; |
119 | |
120 | case 'input': |
121 | if ( isset( $attrs['type'] ) && strcasecmp( $attrs['type'], 'hidden' ) === 0 ) { |
122 | $builder->error( 'begrudgingly accepting a hidden input in table mode', |
123 | $sourceStart ); |
124 | $dispatcher->ack = true; |
125 | $builder->insertElement( $name, $attrs, true, $sourceStart, $sourceLength ); |
126 | break; |
127 | } |
128 | // Fall through |
129 | |
130 | default: |
131 | $builder->error( 'invalid start tag in table, fostering', $sourceStart ); |
132 | $builder->fosterParenting = true; |
133 | $dispatcher->inBody->startTag( $name, $attrs, $selfClose, $sourceStart, $sourceLength ); |
134 | $builder->fosterParenting = false; |
135 | break; |
136 | } |
137 | } |
138 | |
139 | public function endTag( $name, $sourceStart, $sourceLength ) { |
140 | $builder = $this->builder; |
141 | $stack = $builder->stack; |
142 | $dispatcher = $this->dispatcher; |
143 | |
144 | switch ( $name ) { |
145 | case 'table': |
146 | if ( !$stack->isInTableScope( 'table' ) ) { |
147 | $builder->error( '</table> found but no table element in scope, ignoring', $sourceStart ); |
148 | // Ignore |
149 | break; |
150 | } |
151 | $builder->popAllUpToName( 'table', $sourceStart, $sourceLength ); |
152 | $dispatcher->reset(); |
153 | break; |
154 | |
155 | case 'body': |
156 | case 'caption': |
157 | case 'col': |
158 | case 'colgroup': |
159 | case 'html': |
160 | case 'tbody': |
161 | case 'td': |
162 | case 'tfoot': |
163 | case 'th': |
164 | case 'thead': |
165 | case 'tr': |
166 | $builder->error( 'ignoring invalid end tag inside table', $sourceStart ); |
167 | break; |
168 | |
169 | case 'template': |
170 | $dispatcher->inHead->endTag( $name, $sourceStart, $sourceLength ); |
171 | break; |
172 | |
173 | default: |
174 | $builder->error( 'unexpected end tag in table, fostering', $sourceStart ); |
175 | $builder->fosterParenting = true; |
176 | $dispatcher->inBody->endTag( $name, $sourceStart, $sourceLength ); |
177 | $builder->fosterParenting = false; |
178 | } |
179 | } |
180 | |
181 | public function endDocument( $pos ) { |
182 | $this->dispatcher->inBody->endDocument( $pos ); |
183 | } |
184 | } |