3 namespace RemexHtml\TreeBuilder;
4 use RemexHtml\HTMLData;
5 use RemexHtml\Tokenizer\Attributes;
8 * The rules for parsing tokens in foreign content.
10 * This is not referred to as an insertion mode in the spec, but is
11 * sufficiently similar to one that we can inherit from InsertionMode here.
13 class InForeignContent extends InsertionMode {
15 * The list of tag names which unconditionally generate a parse error when
16 * seen in foreign content.
19 private static $notAllowed = [
67 * The table for correcting the tag names of SVG elements, given in the
68 * "Any other start tag" section of the spec.
70 private static $svgElementCase = [
71 'altglyph' => 'altGlyph',
72 'altglyphdef' => 'altGlyphDef',
73 'altglyphitem' => 'altGlyphItem',
74 'animatecolor' => 'animateColor',
75 'animatemotion' => 'animateMotion',
76 'animatetransform' => 'animateTransform',
77 'clippath' => 'clipPath',
78 'feblend' => 'feBlend',
79 'fecolormatrix' => 'feColorMatrix',
80 'fecomponenttransfer' => 'feComponentTransfer',
81 'fecomposite' => 'feComposite',
82 'feconvolvematrix' => 'feConvolveMatrix',
83 'fediffuselighting' => 'feDiffuseLighting',
84 'fedisplacementmap' => 'feDisplacementMap',
85 'fedistantlight' => 'feDistantLight',
86 'fedropshadow' => 'feDropShadow',
87 'feflood' => 'feFlood',
88 'fefunca' => 'feFuncA',
89 'fefuncb' => 'feFuncB',
90 'fefuncg' => 'feFuncG',
91 'fefuncr' => 'feFuncR',
92 'fegaussianblur' => 'feGaussianBlur',
93 'feimage' => 'feImage',
94 'femerge' => 'feMerge',
95 'femergenode' => 'feMergeNode',
96 'femorphology' => 'feMorphology',
97 'feoffset' => 'feOffset',
98 'fepointlight' => 'fePointLight',
99 'fespecularlighting' => 'feSpecularLighting',
100 'fespotlight' => 'feSpotLight',
101 'fetile' => 'feTile',
102 'feturbulence' => 'feTurbulence',
103 'foreignobject' => 'foreignObject',
104 'glyphref' => 'glyphRef',
105 'lineargradient' => 'linearGradient',
106 'radialgradient' => 'radialGradient',
107 'textpath' => 'textPath',
110 public function characters( $text, $start, $length, $sourceStart, $sourceLength ) {
111 $builder = $this->builder;
114 $normalLength = strcspn( $text, "\0\t\n\f\r ", $start, $length );
115 if ( $normalLength ) {
116 $builder->framesetOK = false;
117 $builder->insertCharacters( $text, $start, $normalLength,
118 $sourceStart, $sourceLength );
120 $start += $normalLength;
121 $length -= $normalLength;
122 $sourceStart += $normalLength;
123 $sourceLength -= $normalLength;
128 $char = $text[$start];
129 if ( $char === "\0" ) {
130 $builder->error( "replaced null character", $sourceStart );
131 $builder->insertCharacters( "\xef\xbf\xbd", 0, 3, $sourceStart, $sourceLength );
138 $wsLength = strspn( $text, "\t\n\f\r ", $start, $length );
139 $builder->insertCharacters( $text, $start, $wsLength, $sourceStart, $wsLength );
141 $length -= $wsLength;
142 $sourceStart += $wsLength;
143 $sourceLength -= $wsLength;
148 private function isIntegrationPoint( Element $element ) {
149 return $element->namespace === HTMLData::NS_HTML
150 || $element->isMathmlTextIntegration()
151 || $element->isHtmlIntegration();
154 public function startTag( $name, Attributes $attrs, $selfClose, $sourceStart, $sourceLength ) {
155 $builder = $this->builder;
156 $stack = $builder->stack;
157 $dispatcher = $this->dispatcher;
159 if ( isset( self::$notAllowed[$name] ) ) {
161 } elseif ( $name === 'font' && (
162 isset( $attrs['color'] ) || isset( $attrs['face'] ) || isset( $attrs['size'] ) )
170 $builder->error( "unexpected <$name> tag in foreign content", $sourceStart );
171 if ( !$builder->isFragment ) {
173 $builder->pop( $sourceStart, 0 );
174 } while ( $stack->current && !$this->isIntegrationPoint( $stack->current ) );
175 $dispatcher->startTag( $name, $attrs, $selfClose, $sourceStart, $sourceLength );
180 $acnNs = $builder->adjustedCurrentNode()->namespace;
181 if ( $acnNs === HTMLData::NS_MATHML ) {
182 $attrs = new ForeignAttributes( $attrs, 'math' );
183 } elseif ( $acnNs === HTMLData::NS_SVG ) {
184 $attrs = new ForeignAttributes( $attrs, 'svg' );
185 if ( isset( self::$svgElementCase[$name] ) ) {
186 $name = self::$svgElementCase[$name];
189 $attrs = new ForeignAttributes( $attrs, 'other' );
191 $dispatcher->ack = true;
192 $builder->insertForeign( $acnNs, $name, $attrs, $selfClose, $sourceStart, $sourceLength );
195 public function endTag( $name, $sourceStart, $sourceLength ) {
196 $builder = $this->builder;
197 $stack = $builder->stack;
198 $dispatcher = $this->dispatcher;
200 $node = $stack->current;
201 if ( strcasecmp( $node->name, $name ) !== 0 ) {
202 $builder->error( "mismatched end tag in foreign content", $sourceStart );
204 for ( $idx = $stack->length() - 1; $idx > 0; $idx-- ) {
205 if ( strcasecmp( $node->name, $name ) === 0 ) {
206 $builder->popAllUpToElement( $node, $sourceStart, $sourceLength );
209 $node = $stack->item( $idx - 1 );
210 if ( $node->namespace === HTMLData::NS_HTML ) {
211 $dispatcher->getHandler()->endTag( $name, $sourceStart, $sourceLength );
217 public function endDocument( $pos ) {
218 throw new TreeBuilderError( "unspecified, presumed unreachable" );