Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(485)

Side by Side Diff: html/lib/parser.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « html/lib/dom_parsing.dart ('k') | html/lib/parser_console.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /// This library has a parser for HTML5 documents, that lets you parse HTML
2 /// easily from a script or server side application:
3 ///
4 /// import 'package:html/parser.dart' show parse;
5 /// import 'package:html/dom.dart';
6 /// main() {
7 /// var document = parse(
8 /// '<body>Hello world! <a href="www.html5rocks.com">HTML5 rocks!');
9 /// print(document.outerHtml);
10 /// }
11 ///
12 /// The resulting document you get back has a DOM-like API for easy tree
13 /// traversal and manipulation.
14 library parser;
15
16 import 'dart:collection';
17 import 'dart:math';
18 import 'package:source_span/source_span.dart';
19
20 import 'src/treebuilder.dart';
21 import 'src/constants.dart';
22 import 'src/encoding_parser.dart';
23 import 'src/token.dart';
24 import 'src/tokenizer.dart';
25 import 'src/utils.dart';
26 import 'dom.dart';
27
28 /// Parse the [input] html5 document into a tree. The [input] can be
29 /// a [String], [List<int>] of bytes or an [HtmlTokenizer].
30 ///
31 /// If [input] is not a [HtmlTokenizer], you can optionally specify the file's
32 /// [encoding], which must be a string. If specified that encoding will be
33 /// used regardless of any BOM or later declaration (such as in a meta element).
34 ///
35 /// Set [generateSpans] if you want to generate [SourceSpan]s, otherwise the
36 /// [Node.sourceSpan] property will be `null`. When using [generateSpans] you
37 /// can additionally pass [sourceUrl] to indicate where the [input] was
38 /// extracted from.
39 Document parse(input,
40 {String encoding, bool generateSpans: false, String sourceUrl}) {
41 var p = new HtmlParser(input,
42 encoding: encoding, generateSpans: generateSpans, sourceUrl: sourceUrl);
43 return p.parse();
44 }
45
46 /// Parse the [input] html5 document fragment into a tree. The [input] can be
47 /// a [String], [List<int>] of bytes or an [HtmlTokenizer]. The [container]
48 /// element can optionally be specified, otherwise it defaults to "div".
49 ///
50 /// If [input] is not a [HtmlTokenizer], you can optionally specify the file's
51 /// [encoding], which must be a string. If specified, that encoding will be used ,
52 /// regardless of any BOM or later declaration (such as in a meta element).
53 ///
54 /// Set [generateSpans] if you want to generate [SourceSpan]s, otherwise the
55 /// [Node.sourceSpan] property will be `null`. When using [generateSpans] you ca n
56 /// additionally pass [sourceUrl] to indicate where the [input] was extracted
57 /// from.
58 DocumentFragment parseFragment(input, {String container: "div", String encoding,
59 bool generateSpans: false, String sourceUrl}) {
60 var p = new HtmlParser(input,
61 encoding: encoding, generateSpans: generateSpans, sourceUrl: sourceUrl);
62 return p.parseFragment(container);
63 }
64
65 /// Parser for HTML, which generates a tree structure from a stream of
66 /// (possibly malformed) characters.
67 class HtmlParser {
68 /// Raise an exception on the first error encountered.
69 final bool strict;
70
71 /// True to generate [SourceSpan]s for the [Node.sourceSpan] property.
72 final bool generateSpans;
73
74 final HtmlTokenizer tokenizer;
75
76 final TreeBuilder tree;
77
78 final List<ParseError> errors = <ParseError>[];
79
80 String container;
81
82 bool firstStartTag = false;
83
84 // TODO(jmesserly): use enum?
85 /// "quirks" / "limited quirks" / "no quirks"
86 String compatMode = "no quirks";
87
88 /// innerHTML container when parsing document fragment.
89 String innerHTML;
90
91 Phase phase;
92
93 Phase lastPhase;
94
95 Phase originalPhase;
96
97 Phase beforeRCDataPhase;
98
99 bool framesetOK;
100
101 // These fields hold the different phase singletons. At any given time one
102 // of them will be active.
103 InitialPhase _initialPhase;
104 BeforeHtmlPhase _beforeHtmlPhase;
105 BeforeHeadPhase _beforeHeadPhase;
106 InHeadPhase _inHeadPhase;
107 AfterHeadPhase _afterHeadPhase;
108 InBodyPhase _inBodyPhase;
109 TextPhase _textPhase;
110 InTablePhase _inTablePhase;
111 InTableTextPhase _inTableTextPhase;
112 InCaptionPhase _inCaptionPhase;
113 InColumnGroupPhase _inColumnGroupPhase;
114 InTableBodyPhase _inTableBodyPhase;
115 InRowPhase _inRowPhase;
116 InCellPhase _inCellPhase;
117 InSelectPhase _inSelectPhase;
118 InSelectInTablePhase _inSelectInTablePhase;
119 InForeignContentPhase _inForeignContentPhase;
120 AfterBodyPhase _afterBodyPhase;
121 InFramesetPhase _inFramesetPhase;
122 AfterFramesetPhase _afterFramesetPhase;
123 AfterAfterBodyPhase _afterAfterBodyPhase;
124 AfterAfterFramesetPhase _afterAfterFramesetPhase;
125
126 /// Create an HtmlParser and configure the [tree] builder and [strict] mode.
127 /// The [input] can be a [String], [List<int>] of bytes or an [HtmlTokenizer].
128 ///
129 /// If [input] is not a [HtmlTokenizer], you can specify a few more arguments.
130 ///
131 /// The [encoding] must be a string that indicates the encoding. If specified,
132 /// that encoding will be used, regardless of any BOM or later declaration
133 /// (such as in a meta element).
134 ///
135 /// Set [parseMeta] to false if you want to disable parsing the meta element.
136 ///
137 /// Set [lowercaseElementName] or [lowercaseAttrName] to false to disable the
138 /// automatic conversion of element and attribute names to lower case. Note
139 /// that standard way to parse HTML is to lowercase, which is what the browser
140 /// DOM will do if you request [Node.outerHTML], for example.
141 HtmlParser(input, {String encoding, bool parseMeta: true,
142 bool lowercaseElementName: true, bool lowercaseAttrName: true,
143 this.strict: false, bool generateSpans: false, String sourceUrl,
144 TreeBuilder tree})
145 : generateSpans = generateSpans,
146 tree = tree != null ? tree : new TreeBuilder(true),
147 tokenizer = (input is HtmlTokenizer
148 ? input
149 : new HtmlTokenizer(input,
150 encoding: encoding,
151 parseMeta: parseMeta,
152 lowercaseElementName: lowercaseElementName,
153 lowercaseAttrName: lowercaseAttrName,
154 generateSpans: generateSpans,
155 sourceUrl: sourceUrl)) {
156 tokenizer.parser = this;
157 _initialPhase = new InitialPhase(this);
158 _beforeHtmlPhase = new BeforeHtmlPhase(this);
159 _beforeHeadPhase = new BeforeHeadPhase(this);
160 _inHeadPhase = new InHeadPhase(this);
161 // TODO(jmesserly): html5lib did not implement the no script parsing mode
162 // More information here:
163 // http://www.whatwg.org/specs/web-apps/current-work/multipage/parsing.html# scripting-flag
164 // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construc tion.html#parsing-main-inheadnoscript
165 // "inHeadNoscript": new InHeadNoScriptPhase(this);
166 _afterHeadPhase = new AfterHeadPhase(this);
167 _inBodyPhase = new InBodyPhase(this);
168 _textPhase = new TextPhase(this);
169 _inTablePhase = new InTablePhase(this);
170 _inTableTextPhase = new InTableTextPhase(this);
171 _inCaptionPhase = new InCaptionPhase(this);
172 _inColumnGroupPhase = new InColumnGroupPhase(this);
173 _inTableBodyPhase = new InTableBodyPhase(this);
174 _inRowPhase = new InRowPhase(this);
175 _inCellPhase = new InCellPhase(this);
176 _inSelectPhase = new InSelectPhase(this);
177 _inSelectInTablePhase = new InSelectInTablePhase(this);
178 _inForeignContentPhase = new InForeignContentPhase(this);
179 _afterBodyPhase = new AfterBodyPhase(this);
180 _inFramesetPhase = new InFramesetPhase(this);
181 _afterFramesetPhase = new AfterFramesetPhase(this);
182 _afterAfterBodyPhase = new AfterAfterBodyPhase(this);
183 _afterAfterFramesetPhase = new AfterAfterFramesetPhase(this);
184 }
185
186 bool get innerHTMLMode => innerHTML != null;
187
188 /// Parse an html5 document into a tree.
189 /// After parsing, [errors] will be populated with parse errors, if any.
190 Document parse() {
191 innerHTML = null;
192 _parse();
193 return tree.getDocument();
194 }
195
196 /// Parse an html5 document fragment into a tree.
197 /// Pass a [container] to change the type of the containing element.
198 /// After parsing, [errors] will be populated with parse errors, if any.
199 DocumentFragment parseFragment([String container = "div"]) {
200 if (container == null) throw new ArgumentError('container');
201 innerHTML = container.toLowerCase();
202 _parse();
203 return tree.getFragment();
204 }
205
206 void _parse() {
207 reset();
208
209 while (true) {
210 try {
211 mainLoop();
212 break;
213 } on ReparseException catch (e) {
214 // Note: this happens if we start parsing but the character encoding
215 // changes. So we should only need to restart very early in the parse.
216 reset();
217 }
218 }
219 }
220
221 void reset() {
222 tokenizer.reset();
223
224 tree.reset();
225 firstStartTag = false;
226 errors.clear();
227 // "quirks" / "limited quirks" / "no quirks"
228 compatMode = "no quirks";
229
230 if (innerHTMLMode) {
231 if (cdataElements.contains(innerHTML)) {
232 tokenizer.state = tokenizer.rcdataState;
233 } else if (rcdataElements.contains(innerHTML)) {
234 tokenizer.state = tokenizer.rawtextState;
235 } else if (innerHTML == 'plaintext') {
236 tokenizer.state = tokenizer.plaintextState;
237 } else {
238 // state already is data state
239 // tokenizer.state = tokenizer.dataState;
240 }
241 phase = _beforeHtmlPhase;
242 _beforeHtmlPhase.insertHtmlElement();
243 resetInsertionMode();
244 } else {
245 phase = _initialPhase;
246 }
247
248 lastPhase = null;
249 beforeRCDataPhase = null;
250 framesetOK = true;
251 }
252
253 bool isHTMLIntegrationPoint(Element element) {
254 if (element.localName == "annotation-xml" &&
255 element.namespaceUri == Namespaces.mathml) {
256 var enc = element.attributes["encoding"];
257 if (enc != null) enc = asciiUpper2Lower(enc);
258 return enc == "text/html" || enc == "application/xhtml+xml";
259 } else {
260 return htmlIntegrationPointElements
261 .contains(new Pair(element.namespaceUri, element.localName));
262 }
263 }
264
265 bool isMathMLTextIntegrationPoint(Element element) {
266 return mathmlTextIntegrationPointElements
267 .contains(new Pair(element.namespaceUri, element.localName));
268 }
269
270 bool inForeignContent(Token token, int type) {
271 if (tree.openElements.length == 0) return false;
272
273 var node = tree.openElements.last;
274 if (node.namespaceUri == tree.defaultNamespace) return false;
275
276 if (isMathMLTextIntegrationPoint(node)) {
277 if (type == TokenKind.startTag &&
278 (token as StartTagToken).name != "mglyph" &&
279 (token as StartTagToken).name != "malignmark") {
280 return false;
281 }
282 if (type == TokenKind.characters || type == TokenKind.spaceCharacters) {
283 return false;
284 }
285 }
286
287 if (node.localName == "annotation-xml" &&
288 type == TokenKind.startTag &&
289 (token as StartTagToken).name == "svg") {
290 return false;
291 }
292
293 if (isHTMLIntegrationPoint(node)) {
294 if (type == TokenKind.startTag ||
295 type == TokenKind.characters ||
296 type == TokenKind.spaceCharacters) {
297 return false;
298 }
299 }
300
301 return true;
302 }
303
304 void mainLoop() {
305 while (tokenizer.moveNext()) {
306 var token = tokenizer.current;
307 var newToken = token;
308 int type;
309 while (newToken != null) {
310 type = newToken.kind;
311
312 // Note: avoid "is" test here, see http://dartbug.com/4795
313 if (type == TokenKind.parseError) {
314 ParseErrorToken error = newToken;
315 parseError(error.span, error.data, error.messageParams);
316 newToken = null;
317 } else {
318 Phase phase_ = phase;
319 if (inForeignContent(token, type)) {
320 phase_ = _inForeignContentPhase;
321 }
322
323 switch (type) {
324 case TokenKind.characters:
325 newToken = phase_.processCharacters(newToken);
326 break;
327 case TokenKind.spaceCharacters:
328 newToken = phase_.processSpaceCharacters(newToken);
329 break;
330 case TokenKind.startTag:
331 newToken = phase_.processStartTag(newToken);
332 break;
333 case TokenKind.endTag:
334 newToken = phase_.processEndTag(newToken);
335 break;
336 case TokenKind.comment:
337 newToken = phase_.processComment(newToken);
338 break;
339 case TokenKind.doctype:
340 newToken = phase_.processDoctype(newToken);
341 break;
342 }
343 }
344 }
345
346 if (token is StartTagToken) {
347 if (token.selfClosing && !token.selfClosingAcknowledged) {
348 parseError(token.span, "non-void-element-with-trailing-solidus", {
349 "name": token.name
350 });
351 }
352 }
353 }
354
355 // When the loop finishes it's EOF
356 var reprocess = true;
357 var reprocessPhases = [];
358 while (reprocess) {
359 reprocessPhases.add(phase);
360 reprocess = phase.processEOF();
361 if (reprocess) {
362 assert(!reprocessPhases.contains(phase));
363 }
364 }
365 }
366
367 /// The last span available. Used for EOF errors if we don't have something
368 /// better.
369 SourceSpan get _lastSpan {
370 if (tokenizer.stream.fileInfo == null) return null;
371 var pos = tokenizer.stream.position;
372 return tokenizer.stream.fileInfo.location(pos).pointSpan();
373 }
374
375 void parseError(SourceSpan span, String errorcode,
376 [Map datavars = const {}]) {
377 if (!generateSpans && span == null) {
378 span = _lastSpan;
379 }
380
381 var err = new ParseError(errorcode, span, datavars);
382 errors.add(err);
383 if (strict) throw err;
384 }
385
386 void adjustMathMLAttributes(StartTagToken token) {
387 var orig = token.data.remove("definitionurl");
388 if (orig != null) {
389 token.data["definitionURL"] = orig;
390 }
391 }
392
393 void adjustSVGAttributes(StartTagToken token) {
394 final replacements = const {
395 "attributename": "attributeName",
396 "attributetype": "attributeType",
397 "basefrequency": "baseFrequency",
398 "baseprofile": "baseProfile",
399 "calcmode": "calcMode",
400 "clippathunits": "clipPathUnits",
401 "contentscripttype": "contentScriptType",
402 "contentstyletype": "contentStyleType",
403 "diffuseconstant": "diffuseConstant",
404 "edgemode": "edgeMode",
405 "externalresourcesrequired": "externalResourcesRequired",
406 "filterres": "filterRes",
407 "filterunits": "filterUnits",
408 "glyphref": "glyphRef",
409 "gradienttransform": "gradientTransform",
410 "gradientunits": "gradientUnits",
411 "kernelmatrix": "kernelMatrix",
412 "kernelunitlength": "kernelUnitLength",
413 "keypoints": "keyPoints",
414 "keysplines": "keySplines",
415 "keytimes": "keyTimes",
416 "lengthadjust": "lengthAdjust",
417 "limitingconeangle": "limitingConeAngle",
418 "markerheight": "markerHeight",
419 "markerunits": "markerUnits",
420 "markerwidth": "markerWidth",
421 "maskcontentunits": "maskContentUnits",
422 "maskunits": "maskUnits",
423 "numoctaves": "numOctaves",
424 "pathlength": "pathLength",
425 "patterncontentunits": "patternContentUnits",
426 "patterntransform": "patternTransform",
427 "patternunits": "patternUnits",
428 "pointsatx": "pointsAtX",
429 "pointsaty": "pointsAtY",
430 "pointsatz": "pointsAtZ",
431 "preservealpha": "preserveAlpha",
432 "preserveaspectratio": "preserveAspectRatio",
433 "primitiveunits": "primitiveUnits",
434 "refx": "refX",
435 "refy": "refY",
436 "repeatcount": "repeatCount",
437 "repeatdur": "repeatDur",
438 "requiredextensions": "requiredExtensions",
439 "requiredfeatures": "requiredFeatures",
440 "specularconstant": "specularConstant",
441 "specularexponent": "specularExponent",
442 "spreadmethod": "spreadMethod",
443 "startoffset": "startOffset",
444 "stddeviation": "stdDeviation",
445 "stitchtiles": "stitchTiles",
446 "surfacescale": "surfaceScale",
447 "systemlanguage": "systemLanguage",
448 "tablevalues": "tableValues",
449 "targetx": "targetX",
450 "targety": "targetY",
451 "textlength": "textLength",
452 "viewbox": "viewBox",
453 "viewtarget": "viewTarget",
454 "xchannelselector": "xChannelSelector",
455 "ychannelselector": "yChannelSelector",
456 "zoomandpan": "zoomAndPan"
457 };
458 for (var originalName in token.data.keys.toList()) {
459 var svgName = replacements[originalName];
460 if (svgName != null) {
461 token.data[svgName] = token.data.remove(originalName);
462 }
463 }
464 }
465
466 void adjustForeignAttributes(StartTagToken token) {
467 // TODO(jmesserly): I don't like mixing non-string objects with strings in
468 // the Node.attributes Map. Is there another solution?
469 final replacements = const {
470 "xlink:actuate":
471 const AttributeName("xlink", "actuate", Namespaces.xlink),
472 "xlink:arcrole":
473 const AttributeName("xlink", "arcrole", Namespaces.xlink),
474 "xlink:href": const AttributeName("xlink", "href", Namespaces.xlink),
475 "xlink:role": const AttributeName("xlink", "role", Namespaces.xlink),
476 "xlink:show": const AttributeName("xlink", "show", Namespaces.xlink),
477 "xlink:title": const AttributeName("xlink", "title", Namespaces.xlink),
478 "xlink:type": const AttributeName("xlink", "type", Namespaces.xlink),
479 "xml:base": const AttributeName("xml", "base", Namespaces.xml),
480 "xml:lang": const AttributeName("xml", "lang", Namespaces.xml),
481 "xml:space": const AttributeName("xml", "space", Namespaces.xml),
482 "xmlns": const AttributeName(null, "xmlns", Namespaces.xmlns),
483 "xmlns:xlink": const AttributeName("xmlns", "xlink", Namespaces.xmlns)
484 };
485
486 for (var originalName in token.data.keys.toList()) {
487 var foreignName = replacements[originalName];
488 if (foreignName != null) {
489 token.data[foreignName] = token.data.remove(originalName);
490 }
491 }
492 }
493
494 void resetInsertionMode() {
495 // The name of this method is mostly historical. (It's also used in the
496 // specification.)
497 for (var node in tree.openElements.reversed) {
498 var nodeName = node.localName;
499 bool last = node == tree.openElements[0];
500 if (last) {
501 assert(innerHTMLMode);
502 nodeName = innerHTML;
503 }
504 // Check for conditions that should only happen in the innerHTML
505 // case
506 switch (nodeName) {
507 case "select":
508 case "colgroup":
509 case "head":
510 case "html":
511 assert(innerHTMLMode);
512 break;
513 }
514 if (!last && node.namespaceUri != tree.defaultNamespace) {
515 continue;
516 }
517 switch (nodeName) {
518 case "select":
519 phase = _inSelectPhase;
520 return;
521 case "td":
522 phase = _inCellPhase;
523 return;
524 case "th":
525 phase = _inCellPhase;
526 return;
527 case "tr":
528 phase = _inRowPhase;
529 return;
530 case "tbody":
531 phase = _inTableBodyPhase;
532 return;
533 case "thead":
534 phase = _inTableBodyPhase;
535 return;
536 case "tfoot":
537 phase = _inTableBodyPhase;
538 return;
539 case "caption":
540 phase = _inCaptionPhase;
541 return;
542 case "colgroup":
543 phase = _inColumnGroupPhase;
544 return;
545 case "table":
546 phase = _inTablePhase;
547 return;
548 case "head":
549 phase = _inBodyPhase;
550 return;
551 case "body":
552 phase = _inBodyPhase;
553 return;
554 case "frameset":
555 phase = _inFramesetPhase;
556 return;
557 case "html":
558 phase = _beforeHeadPhase;
559 return;
560 }
561 }
562 phase = _inBodyPhase;
563 }
564
565 /// Generic RCDATA/RAWTEXT Parsing algorithm
566 /// [contentType] - RCDATA or RAWTEXT
567 void parseRCDataRawtext(Token token, String contentType) {
568 assert(contentType == "RAWTEXT" || contentType == "RCDATA");
569
570 tree.insertElement(token);
571
572 if (contentType == "RAWTEXT") {
573 tokenizer.state = tokenizer.rawtextState;
574 } else {
575 tokenizer.state = tokenizer.rcdataState;
576 }
577
578 originalPhase = phase;
579 phase = _textPhase;
580 }
581 }
582
583 /// Base class for helper object that implements each phase of processing.
584 class Phase {
585 // Order should be (they can be omitted):
586 // * EOF
587 // * Comment
588 // * Doctype
589 // * SpaceCharacters
590 // * Characters
591 // * StartTag
592 // - startTag* methods
593 // * EndTag
594 // - endTag* methods
595
596 final HtmlParser parser;
597
598 final TreeBuilder tree;
599
600 Phase(HtmlParser parser)
601 : parser = parser,
602 tree = parser.tree;
603
604 bool processEOF() {
605 throw new UnimplementedError();
606 }
607
608 Token processComment(CommentToken token) {
609 // For most phases the following is correct. Where it's not it will be
610 // overridden.
611 tree.insertComment(token, tree.openElements.last);
612 return null;
613 }
614
615 Token processDoctype(DoctypeToken token) {
616 parser.parseError(token.span, "unexpected-doctype");
617 return null;
618 }
619
620 Token processCharacters(CharactersToken token) {
621 tree.insertText(token.data, token.span);
622 return null;
623 }
624
625 Token processSpaceCharacters(SpaceCharactersToken token) {
626 tree.insertText(token.data, token.span);
627 return null;
628 }
629
630 Token processStartTag(StartTagToken token) {
631 throw new UnimplementedError();
632 }
633
634 Token startTagHtml(StartTagToken token) {
635 if (parser.firstStartTag == false && token.name == "html") {
636 parser.parseError(token.span, "non-html-root");
637 }
638 // XXX Need a check here to see if the first start tag token emitted is
639 // this token... If it's not, invoke parser.parseError().
640 tree.openElements[0].sourceSpan = token.span;
641 token.data.forEach((attr, value) {
642 tree.openElements[0].attributes.putIfAbsent(attr, () => value);
643 });
644 parser.firstStartTag = false;
645 return null;
646 }
647
648 Token processEndTag(EndTagToken token) {
649 throw new UnimplementedError();
650 }
651
652 /// Helper method for popping openElements.
653 void popOpenElementsUntil(EndTagToken token) {
654 String name = token.name;
655 var node = tree.openElements.removeLast();
656 while (node.localName != name) {
657 node = tree.openElements.removeLast();
658 }
659 if (node != null) {
660 node.endSourceSpan = token.span;
661 }
662 }
663 }
664
665 class InitialPhase extends Phase {
666 InitialPhase(parser) : super(parser);
667
668 Token processSpaceCharacters(SpaceCharactersToken token) {
669 return null;
670 }
671
672 Token processComment(CommentToken token) {
673 tree.insertComment(token, tree.document);
674 return null;
675 }
676
677 Token processDoctype(DoctypeToken token) {
678 var name = token.name;
679 String publicId = token.publicId;
680 var systemId = token.systemId;
681 var correct = token.correct;
682
683 if ((name != "html" ||
684 publicId != null ||
685 systemId != null && systemId != "about:legacy-compat")) {
686 parser.parseError(token.span, "unknown-doctype");
687 }
688
689 if (publicId == null) {
690 publicId = "";
691 }
692
693 tree.insertDoctype(token);
694
695 if (publicId != "") {
696 publicId = asciiUpper2Lower(publicId);
697 }
698
699 if (!correct || token.name != "html" || startsWithAny(publicId, const [
700 "+//silmaril//dtd html pro v0r11 19970101//",
701 "-//advasoft ltd//dtd html 3.0 aswedit + extensions//",
702 "-//as//dtd html 3.0 aswedit + extensions//",
703 "-//ietf//dtd html 2.0 level 1//",
704 "-//ietf//dtd html 2.0 level 2//",
705 "-//ietf//dtd html 2.0 strict level 1//",
706 "-//ietf//dtd html 2.0 strict level 2//",
707 "-//ietf//dtd html 2.0 strict//",
708 "-//ietf//dtd html 2.0//",
709 "-//ietf//dtd html 2.1e//",
710 "-//ietf//dtd html 3.0//",
711 "-//ietf//dtd html 3.2 final//",
712 "-//ietf//dtd html 3.2//",
713 "-//ietf//dtd html 3//",
714 "-//ietf//dtd html level 0//",
715 "-//ietf//dtd html level 1//",
716 "-//ietf//dtd html level 2//",
717 "-//ietf//dtd html level 3//",
718 "-//ietf//dtd html strict level 0//",
719 "-//ietf//dtd html strict level 1//",
720 "-//ietf//dtd html strict level 2//",
721 "-//ietf//dtd html strict level 3//",
722 "-//ietf//dtd html strict//",
723 "-//ietf//dtd html//",
724 "-//metrius//dtd metrius presentational//",
725 "-//microsoft//dtd internet explorer 2.0 html strict//",
726 "-//microsoft//dtd internet explorer 2.0 html//",
727 "-//microsoft//dtd internet explorer 2.0 tables//",
728 "-//microsoft//dtd internet explorer 3.0 html strict//",
729 "-//microsoft//dtd internet explorer 3.0 html//",
730 "-//microsoft//dtd internet explorer 3.0 tables//",
731 "-//netscape comm. corp.//dtd html//",
732 "-//netscape comm. corp.//dtd strict html//",
733 "-//o'reilly and associates//dtd html 2.0//",
734 "-//o'reilly and associates//dtd html extended 1.0//",
735 "-//o'reilly and associates//dtd html extended relaxed 1.0//",
736 "-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//",
737 "-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//",
738 "-//spyglass//dtd html 2.0 extended//",
739 "-//sq//dtd html 2.0 hotmetal + extensions//",
740 "-//sun microsystems corp.//dtd hotjava html//",
741 "-//sun microsystems corp.//dtd hotjava strict html//",
742 "-//w3c//dtd html 3 1995-03-24//",
743 "-//w3c//dtd html 3.2 draft//",
744 "-//w3c//dtd html 3.2 final//",
745 "-//w3c//dtd html 3.2//",
746 "-//w3c//dtd html 3.2s draft//",
747 "-//w3c//dtd html 4.0 frameset//",
748 "-//w3c//dtd html 4.0 transitional//",
749 "-//w3c//dtd html experimental 19960712//",
750 "-//w3c//dtd html experimental 970421//",
751 "-//w3c//dtd w3 html//",
752 "-//w3o//dtd w3 html 3.0//",
753 "-//webtechs//dtd mozilla html 2.0//",
754 "-//webtechs//dtd mozilla html//"
755 ]) ||
756 const [
757 "-//w3o//dtd w3 html strict 3.0//en//",
758 "-/w3c/dtd html 4.0 transitional/en",
759 "html"
760 ].contains(publicId) ||
761 startsWithAny(publicId, const [
762 "-//w3c//dtd html 4.01 frameset//",
763 "-//w3c//dtd html 4.01 transitional//"
764 ]) &&
765 systemId == null ||
766 systemId != null &&
767 systemId.toLowerCase() ==
768 "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd") {
769 parser.compatMode = "quirks";
770 } else if (startsWithAny(publicId, const [
771 "-//w3c//dtd xhtml 1.0 frameset//",
772 "-//w3c//dtd xhtml 1.0 transitional//"
773 ]) ||
774 startsWithAny(publicId, const [
775 "-//w3c//dtd html 4.01 frameset//",
776 "-//w3c//dtd html 4.01 transitional//"
777 ]) &&
778 systemId != null) {
779 parser.compatMode = "limited quirks";
780 }
781 parser.phase = parser._beforeHtmlPhase;
782 return null;
783 }
784
785 void anythingElse() {
786 parser.compatMode = "quirks";
787 parser.phase = parser._beforeHtmlPhase;
788 }
789
790 Token processCharacters(CharactersToken token) {
791 parser.parseError(token.span, "expected-doctype-but-got-chars");
792 anythingElse();
793 return token;
794 }
795
796 Token processStartTag(StartTagToken token) {
797 parser.parseError(
798 token.span, "expected-doctype-but-got-start-tag", {"name": token.name});
799 anythingElse();
800 return token;
801 }
802
803 Token processEndTag(EndTagToken token) {
804 parser.parseError(
805 token.span, "expected-doctype-but-got-end-tag", {"name": token.name});
806 anythingElse();
807 return token;
808 }
809
810 bool processEOF() {
811 parser.parseError(parser._lastSpan, "expected-doctype-but-got-eof");
812 anythingElse();
813 return true;
814 }
815 }
816
817 class BeforeHtmlPhase extends Phase {
818 BeforeHtmlPhase(parser) : super(parser);
819
820 // helper methods
821 void insertHtmlElement() {
822 tree.insertRoot(new StartTagToken("html", data: {}));
823 parser.phase = parser._beforeHeadPhase;
824 }
825
826 // other
827 bool processEOF() {
828 insertHtmlElement();
829 return true;
830 }
831
832 Token processComment(CommentToken token) {
833 tree.insertComment(token, tree.document);
834 return null;
835 }
836
837 Token processSpaceCharacters(SpaceCharactersToken token) {
838 return null;
839 }
840
841 Token processCharacters(CharactersToken token) {
842 insertHtmlElement();
843 return token;
844 }
845
846 Token processStartTag(StartTagToken token) {
847 if (token.name == "html") {
848 parser.firstStartTag = true;
849 }
850 insertHtmlElement();
851 return token;
852 }
853
854 Token processEndTag(EndTagToken token) {
855 switch (token.name) {
856 case "head":
857 case "body":
858 case "html":
859 case "br":
860 insertHtmlElement();
861 return token;
862 default:
863 parser.parseError(
864 token.span, "unexpected-end-tag-before-html", {"name": token.name});
865 return null;
866 }
867 }
868 }
869
870 class BeforeHeadPhase extends Phase {
871 BeforeHeadPhase(parser) : super(parser);
872
873 processStartTag(StartTagToken token) {
874 switch (token.name) {
875 case 'html':
876 return startTagHtml(token);
877 case 'head':
878 return startTagHead(token);
879 default:
880 return startTagOther(token);
881 }
882 }
883
884 processEndTag(EndTagToken token) {
885 switch (token.name) {
886 case "head":
887 case "body":
888 case "html":
889 case "br":
890 return endTagImplyHead(token);
891 default:
892 return endTagOther(token);
893 }
894 }
895
896 bool processEOF() {
897 startTagHead(new StartTagToken("head", data: {}));
898 return true;
899 }
900
901 Token processSpaceCharacters(SpaceCharactersToken token) {
902 return null;
903 }
904
905 Token processCharacters(CharactersToken token) {
906 startTagHead(new StartTagToken("head", data: {}));
907 return token;
908 }
909
910 Token startTagHtml(StartTagToken token) {
911 return parser._inBodyPhase.processStartTag(token);
912 }
913
914 void startTagHead(StartTagToken token) {
915 tree.insertElement(token);
916 tree.headPointer = tree.openElements.last;
917 parser.phase = parser._inHeadPhase;
918 }
919
920 Token startTagOther(StartTagToken token) {
921 startTagHead(new StartTagToken("head", data: {}));
922 return token;
923 }
924
925 Token endTagImplyHead(EndTagToken token) {
926 startTagHead(new StartTagToken("head", data: {}));
927 return token;
928 }
929
930 void endTagOther(EndTagToken token) {
931 parser.parseError(
932 token.span, "end-tag-after-implied-root", {"name": token.name});
933 }
934 }
935
936 class InHeadPhase extends Phase {
937 InHeadPhase(parser) : super(parser);
938
939 processStartTag(StartTagToken token) {
940 switch (token.name) {
941 case "html":
942 return startTagHtml(token);
943 case "title":
944 return startTagTitle(token);
945 case "noscript":
946 case "noframes":
947 case "style":
948 return startTagNoScriptNoFramesStyle(token);
949 case "script":
950 return startTagScript(token);
951 case "base":
952 case "basefont":
953 case "bgsound":
954 case "command":
955 case "link":
956 return startTagBaseLinkCommand(token);
957 case "meta":
958 return startTagMeta(token);
959 case "head":
960 return startTagHead(token);
961 default:
962 return startTagOther(token);
963 }
964 }
965
966 processEndTag(EndTagToken token) {
967 switch (token.name) {
968 case "head":
969 return endTagHead(token);
970 case "br":
971 case "html":
972 case "body":
973 return endTagHtmlBodyBr(token);
974 default:
975 return endTagOther(token);
976 }
977 }
978
979 // the real thing
980 bool processEOF() {
981 anythingElse();
982 return true;
983 }
984
985 Token processCharacters(CharactersToken token) {
986 anythingElse();
987 return token;
988 }
989
990 Token startTagHtml(StartTagToken token) {
991 return parser._inBodyPhase.processStartTag(token);
992 }
993
994 void startTagHead(StartTagToken token) {
995 parser.parseError(token.span, "two-heads-are-not-better-than-one");
996 }
997
998 void startTagBaseLinkCommand(StartTagToken token) {
999 tree.insertElement(token);
1000 tree.openElements.removeLast();
1001 token.selfClosingAcknowledged = true;
1002 }
1003
1004 void startTagMeta(StartTagToken token) {
1005 tree.insertElement(token);
1006 tree.openElements.removeLast();
1007 token.selfClosingAcknowledged = true;
1008
1009 var attributes = token.data;
1010 if (!parser.tokenizer.stream.charEncodingCertain) {
1011 var charset = attributes["charset"];
1012 var content = attributes["content"];
1013 if (charset != null) {
1014 parser.tokenizer.stream.changeEncoding(charset);
1015 } else if (content != null) {
1016 var data = new EncodingBytes(content);
1017 var codec = new ContentAttrParser(data).parse();
1018 parser.tokenizer.stream.changeEncoding(codec);
1019 }
1020 }
1021 }
1022
1023 void startTagTitle(StartTagToken token) {
1024 parser.parseRCDataRawtext(token, "RCDATA");
1025 }
1026
1027 void startTagNoScriptNoFramesStyle(StartTagToken token) {
1028 // Need to decide whether to implement the scripting-disabled case
1029 parser.parseRCDataRawtext(token, "RAWTEXT");
1030 }
1031
1032 void startTagScript(StartTagToken token) {
1033 tree.insertElement(token);
1034 parser.tokenizer.state = parser.tokenizer.scriptDataState;
1035 parser.originalPhase = parser.phase;
1036 parser.phase = parser._textPhase;
1037 }
1038
1039 Token startTagOther(StartTagToken token) {
1040 anythingElse();
1041 return token;
1042 }
1043
1044 void endTagHead(EndTagToken token) {
1045 var node = parser.tree.openElements.removeLast();
1046 assert(node.localName == "head");
1047 node.endSourceSpan = token.span;
1048 parser.phase = parser._afterHeadPhase;
1049 }
1050
1051 Token endTagHtmlBodyBr(EndTagToken token) {
1052 anythingElse();
1053 return token;
1054 }
1055
1056 void endTagOther(EndTagToken token) {
1057 parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
1058 }
1059
1060 void anythingElse() {
1061 endTagHead(new EndTagToken("head"));
1062 }
1063 }
1064
1065 // XXX If we implement a parser for which scripting is disabled we need to
1066 // implement this phase.
1067 //
1068 // class InHeadNoScriptPhase extends Phase {
1069
1070 class AfterHeadPhase extends Phase {
1071 AfterHeadPhase(parser) : super(parser);
1072
1073 processStartTag(StartTagToken token) {
1074 switch (token.name) {
1075 case "html":
1076 return startTagHtml(token);
1077 case "body":
1078 return startTagBody(token);
1079 case "frameset":
1080 return startTagFrameset(token);
1081 case "base":
1082 case "basefont":
1083 case "bgsound":
1084 case "link":
1085 case "meta":
1086 case "noframes":
1087 case "script":
1088 case "style":
1089 case "title":
1090 return startTagFromHead(token);
1091 case "head":
1092 return startTagHead(token);
1093 default:
1094 return startTagOther(token);
1095 }
1096 }
1097
1098 processEndTag(EndTagToken token) {
1099 switch (token.name) {
1100 case "body":
1101 case "html":
1102 case "br":
1103 return endTagHtmlBodyBr(token);
1104 default:
1105 return endTagOther(token);
1106 }
1107 }
1108
1109 bool processEOF() {
1110 anythingElse();
1111 return true;
1112 }
1113
1114 Token processCharacters(CharactersToken token) {
1115 anythingElse();
1116 return token;
1117 }
1118
1119 Token startTagHtml(StartTagToken token) {
1120 return parser._inBodyPhase.processStartTag(token);
1121 }
1122
1123 void startTagBody(StartTagToken token) {
1124 parser.framesetOK = false;
1125 tree.insertElement(token);
1126 parser.phase = parser._inBodyPhase;
1127 }
1128
1129 void startTagFrameset(StartTagToken token) {
1130 tree.insertElement(token);
1131 parser.phase = parser._inFramesetPhase;
1132 }
1133
1134 void startTagFromHead(StartTagToken token) {
1135 parser.parseError(token.span, "unexpected-start-tag-out-of-my-head", {
1136 "name": token.name
1137 });
1138 tree.openElements.add(tree.headPointer);
1139 parser._inHeadPhase.processStartTag(token);
1140 for (var node in tree.openElements.reversed) {
1141 if (node.localName == "head") {
1142 tree.openElements.remove(node);
1143 break;
1144 }
1145 }
1146 }
1147
1148 void startTagHead(StartTagToken token) {
1149 parser.parseError(token.span, "unexpected-start-tag", {"name": token.name});
1150 }
1151
1152 Token startTagOther(StartTagToken token) {
1153 anythingElse();
1154 return token;
1155 }
1156
1157 Token endTagHtmlBodyBr(EndTagToken token) {
1158 anythingElse();
1159 return token;
1160 }
1161
1162 void endTagOther(EndTagToken token) {
1163 parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
1164 }
1165
1166 void anythingElse() {
1167 tree.insertElement(new StartTagToken("body", data: {}));
1168 parser.phase = parser._inBodyPhase;
1169 parser.framesetOK = true;
1170 }
1171 }
1172
1173 typedef Token TokenProccessor(Token token);
1174
1175 class InBodyPhase extends Phase {
1176 bool dropNewline = false;
1177
1178 // http://www.whatwg.org/specs/web-apps/current-work///parsing-main-inbody
1179 // the really-really-really-very crazy mode
1180 InBodyPhase(parser) : super(parser);
1181
1182 processStartTag(StartTagToken token) {
1183 switch (token.name) {
1184 case "html":
1185 return startTagHtml(token);
1186 case "base":
1187 case "basefont":
1188 case "bgsound":
1189 case "command":
1190 case "link":
1191 case "meta":
1192 case "noframes":
1193 case "script":
1194 case "style":
1195 case "title":
1196 return startTagProcessInHead(token);
1197 case "body":
1198 return startTagBody(token);
1199 case "frameset":
1200 return startTagFrameset(token);
1201 case "address":
1202 case "article":
1203 case "aside":
1204 case "blockquote":
1205 case "center":
1206 case "details":
1207 case "details":
1208 case "dir":
1209 case "div":
1210 case "dl":
1211 case "fieldset":
1212 case "figcaption":
1213 case "figure":
1214 case "footer":
1215 case "header":
1216 case "hgroup":
1217 case "menu":
1218 case "nav":
1219 case "ol":
1220 case "p":
1221 case "section":
1222 case "summary":
1223 case "ul":
1224 return startTagCloseP(token);
1225 // headingElements
1226 case "h1":
1227 case "h2":
1228 case "h3":
1229 case "h4":
1230 case "h5":
1231 case "h6":
1232 return startTagHeading(token);
1233 case "pre":
1234 case "listing":
1235 return startTagPreListing(token);
1236 case "form":
1237 return startTagForm(token);
1238 case "li":
1239 case "dd":
1240 case "dt":
1241 return startTagListItem(token);
1242 case "plaintext":
1243 return startTagPlaintext(token);
1244 case "a":
1245 return startTagA(token);
1246 case "b":
1247 case "big":
1248 case "code":
1249 case "em":
1250 case "font":
1251 case "i":
1252 case "s":
1253 case "small":
1254 case "strike":
1255 case "strong":
1256 case "tt":
1257 case "u":
1258 return startTagFormatting(token);
1259 case "nobr":
1260 return startTagNobr(token);
1261 case "button":
1262 return startTagButton(token);
1263 case "applet":
1264 case "marquee":
1265 case "object":
1266 return startTagAppletMarqueeObject(token);
1267 case "xmp":
1268 return startTagXmp(token);
1269 case "table":
1270 return startTagTable(token);
1271 case "area":
1272 case "br":
1273 case "embed":
1274 case "img":
1275 case "keygen":
1276 case "wbr":
1277 return startTagVoidFormatting(token);
1278 case "param":
1279 case "source":
1280 case "track":
1281 return startTagParamSource(token);
1282 case "input":
1283 return startTagInput(token);
1284 case "hr":
1285 return startTagHr(token);
1286 case "image":
1287 return startTagImage(token);
1288 case "isindex":
1289 return startTagIsIndex(token);
1290 case "textarea":
1291 return startTagTextarea(token);
1292 case "iframe":
1293 return startTagIFrame(token);
1294 case "noembed":
1295 case "noframes":
1296 case "noscript":
1297 return startTagRawtext(token);
1298 case "select":
1299 return startTagSelect(token);
1300 case "rp":
1301 case "rt":
1302 return startTagRpRt(token);
1303 case "option":
1304 case "optgroup":
1305 return startTagOpt(token);
1306 case "math":
1307 return startTagMath(token);
1308 case "svg":
1309 return startTagSvg(token);
1310 case "caption":
1311 case "col":
1312 case "colgroup":
1313 case "frame":
1314 case "head":
1315 case "tbody":
1316 case "td":
1317 case "tfoot":
1318 case "th":
1319 case "thead":
1320 case "tr":
1321 return startTagMisplaced(token);
1322 default:
1323 return startTagOther(token);
1324 }
1325 }
1326
1327 processEndTag(EndTagToken token) {
1328 switch (token.name) {
1329 case "body":
1330 return endTagBody(token);
1331 case "html":
1332 return endTagHtml(token);
1333 case "address":
1334 case "article":
1335 case "aside":
1336 case "blockquote":
1337 case "center":
1338 case "details":
1339 case "dir":
1340 case "div":
1341 case "dl":
1342 case "fieldset":
1343 case "figcaption":
1344 case "figure":
1345 case "footer":
1346 case "header":
1347 case "hgroup":
1348 case "listing":
1349 case "menu":
1350 case "nav":
1351 case "ol":
1352 case "pre":
1353 case "section":
1354 case "summary":
1355 case "ul":
1356 return endTagBlock(token);
1357 case "form":
1358 return endTagForm(token);
1359 case "p":
1360 return endTagP(token);
1361 case "dd":
1362 case "dt":
1363 case "li":
1364 return endTagListItem(token);
1365 // headingElements
1366 case "h1":
1367 case "h2":
1368 case "h3":
1369 case "h4":
1370 case "h5":
1371 case "h6":
1372 return endTagHeading(token);
1373 case "a":
1374 case "b":
1375 case "big":
1376 case "code":
1377 case "em":
1378 case "font":
1379 case "i":
1380 case "nobr":
1381 case "s":
1382 case "small":
1383 case "strike":
1384 case "strong":
1385 case "tt":
1386 case "u":
1387 return endTagFormatting(token);
1388 case "applet":
1389 case "marquee":
1390 case "object":
1391 return endTagAppletMarqueeObject(token);
1392 case "br":
1393 return endTagBr(token);
1394 default:
1395 return endTagOther(token);
1396 }
1397 }
1398
1399 bool isMatchingFormattingElement(Element node1, Element node2) {
1400 if (node1.localName != node2.localName ||
1401 node1.namespaceUri != node2.namespaceUri) {
1402 return false;
1403 } else if (node1.attributes.length != node2.attributes.length) {
1404 return false;
1405 } else {
1406 for (var key in node1.attributes.keys) {
1407 if (node1.attributes[key] != node2.attributes[key]) {
1408 return false;
1409 }
1410 }
1411 }
1412 return true;
1413 }
1414
1415 // helper
1416 void addFormattingElement(token) {
1417 tree.insertElement(token);
1418 var element = tree.openElements.last;
1419
1420 var matchingElements = [];
1421 for (Node node in tree.activeFormattingElements.reversed) {
1422 if (node == Marker) {
1423 break;
1424 } else if (isMatchingFormattingElement(node, element)) {
1425 matchingElements.add(node);
1426 }
1427 }
1428
1429 assert(matchingElements.length <= 3);
1430 if (matchingElements.length == 3) {
1431 tree.activeFormattingElements.remove(matchingElements.last);
1432 }
1433 tree.activeFormattingElements.add(element);
1434 }
1435
1436 // the real deal
1437 bool processEOF() {
1438 for (var node in tree.openElements.reversed) {
1439 switch (node.localName) {
1440 case "dd":
1441 case "dt":
1442 case "li":
1443 case "p":
1444 case "tbody":
1445 case "td":
1446 case "tfoot":
1447 case "th":
1448 case "thead":
1449 case "tr":
1450 case "body":
1451 case "html":
1452 continue;
1453 }
1454 parser.parseError(node.sourceSpan, "expected-closing-tag-but-got-eof");
1455 break;
1456 }
1457 //Stop parsing
1458 return false;
1459 }
1460
1461 void processSpaceCharactersDropNewline(StringToken token) {
1462 // Sometimes (start of <pre>, <listing>, and <textarea> blocks) we
1463 // want to drop leading newlines
1464 var data = token.data;
1465 dropNewline = false;
1466 if (data.startsWith("\n")) {
1467 var lastOpen = tree.openElements.last;
1468 if (const ["pre", "listing", "textarea"].contains(lastOpen.localName) &&
1469 !lastOpen.hasContent()) {
1470 data = data.substring(1);
1471 }
1472 }
1473 if (data.length > 0) {
1474 tree.reconstructActiveFormattingElements();
1475 tree.insertText(data, token.span);
1476 }
1477 }
1478
1479 Token processCharacters(CharactersToken token) {
1480 if (token.data == "\u0000") {
1481 //The tokenizer should always emit null on its own
1482 return null;
1483 }
1484 tree.reconstructActiveFormattingElements();
1485 tree.insertText(token.data, token.span);
1486 if (parser.framesetOK && !allWhitespace(token.data)) {
1487 parser.framesetOK = false;
1488 }
1489 return null;
1490 }
1491
1492 Token processSpaceCharacters(SpaceCharactersToken token) {
1493 if (dropNewline) {
1494 processSpaceCharactersDropNewline(token);
1495 } else {
1496 tree.reconstructActiveFormattingElements();
1497 tree.insertText(token.data, token.span);
1498 }
1499 return null;
1500 }
1501
1502 Token startTagProcessInHead(StartTagToken token) {
1503 return parser._inHeadPhase.processStartTag(token);
1504 }
1505
1506 void startTagBody(StartTagToken token) {
1507 parser.parseError(token.span, "unexpected-start-tag", {"name": "body"});
1508 if (tree.openElements.length == 1 ||
1509 tree.openElements[1].localName != "body") {
1510 assert(parser.innerHTMLMode);
1511 } else {
1512 parser.framesetOK = false;
1513 token.data.forEach((attr, value) {
1514 tree.openElements[1].attributes.putIfAbsent(attr, () => value);
1515 });
1516 }
1517 }
1518
1519 void startTagFrameset(StartTagToken token) {
1520 parser.parseError(token.span, "unexpected-start-tag", {"name": "frameset"});
1521 if ((tree.openElements.length == 1 ||
1522 tree.openElements[1].localName != "body")) {
1523 assert(parser.innerHTMLMode);
1524 } else if (parser.framesetOK) {
1525 if (tree.openElements[1].parentNode != null) {
1526 tree.openElements[1].parentNode.nodes.remove(tree.openElements[1]);
1527 }
1528 while (tree.openElements.last.localName != "html") {
1529 tree.openElements.removeLast();
1530 }
1531 tree.insertElement(token);
1532 parser.phase = parser._inFramesetPhase;
1533 }
1534 }
1535
1536 void startTagCloseP(StartTagToken token) {
1537 if (tree.elementInScope("p", variant: "button")) {
1538 endTagP(new EndTagToken("p"));
1539 }
1540 tree.insertElement(token);
1541 }
1542
1543 void startTagPreListing(StartTagToken token) {
1544 if (tree.elementInScope("p", variant: "button")) {
1545 endTagP(new EndTagToken("p"));
1546 }
1547 tree.insertElement(token);
1548 parser.framesetOK = false;
1549 dropNewline = true;
1550 }
1551
1552 void startTagForm(StartTagToken token) {
1553 if (tree.formPointer != null) {
1554 parser.parseError(token.span, "unexpected-start-tag", {"name": "form"});
1555 } else {
1556 if (tree.elementInScope("p", variant: "button")) {
1557 endTagP(new EndTagToken("p"));
1558 }
1559 tree.insertElement(token);
1560 tree.formPointer = tree.openElements.last;
1561 }
1562 }
1563
1564 void startTagListItem(StartTagToken token) {
1565 parser.framesetOK = false;
1566
1567 final stopNamesMap = const {
1568 "li": const ["li"],
1569 "dt": const ["dt", "dd"],
1570 "dd": const ["dt", "dd"]
1571 };
1572 var stopNames = stopNamesMap[token.name];
1573 for (var node in tree.openElements.reversed) {
1574 if (stopNames.contains(node.localName)) {
1575 parser.phase.processEndTag(new EndTagToken(node.localName));
1576 break;
1577 }
1578 if (specialElements.contains(getElementNameTuple(node)) &&
1579 !const ["address", "div", "p"].contains(node.localName)) {
1580 break;
1581 }
1582 }
1583
1584 if (tree.elementInScope("p", variant: "button")) {
1585 parser.phase.processEndTag(new EndTagToken("p"));
1586 }
1587
1588 tree.insertElement(token);
1589 }
1590
1591 void startTagPlaintext(StartTagToken token) {
1592 if (tree.elementInScope("p", variant: "button")) {
1593 endTagP(new EndTagToken("p"));
1594 }
1595 tree.insertElement(token);
1596 parser.tokenizer.state = parser.tokenizer.plaintextState;
1597 }
1598
1599 void startTagHeading(StartTagToken token) {
1600 if (tree.elementInScope("p", variant: "button")) {
1601 endTagP(new EndTagToken("p"));
1602 }
1603 if (headingElements.contains(tree.openElements.last.localName)) {
1604 parser.parseError(
1605 token.span, "unexpected-start-tag", {"name": token.name});
1606 tree.openElements.removeLast();
1607 }
1608 tree.insertElement(token);
1609 }
1610
1611 void startTagA(StartTagToken token) {
1612 var afeAElement = tree.elementInActiveFormattingElements("a");
1613 if (afeAElement != null) {
1614 parser.parseError(token.span, "unexpected-start-tag-implies-end-tag", {
1615 "startName": "a",
1616 "endName": "a"
1617 });
1618 endTagFormatting(new EndTagToken("a"));
1619 tree.openElements.remove(afeAElement);
1620 tree.activeFormattingElements.remove(afeAElement);
1621 }
1622 tree.reconstructActiveFormattingElements();
1623 addFormattingElement(token);
1624 }
1625
1626 void startTagFormatting(StartTagToken token) {
1627 tree.reconstructActiveFormattingElements();
1628 addFormattingElement(token);
1629 }
1630
1631 void startTagNobr(StartTagToken token) {
1632 tree.reconstructActiveFormattingElements();
1633 if (tree.elementInScope("nobr")) {
1634 parser.parseError(token.span, "unexpected-start-tag-implies-end-tag", {
1635 "startName": "nobr",
1636 "endName": "nobr"
1637 });
1638 processEndTag(new EndTagToken("nobr"));
1639 // XXX Need tests that trigger the following
1640 tree.reconstructActiveFormattingElements();
1641 }
1642 addFormattingElement(token);
1643 }
1644
1645 Token startTagButton(StartTagToken token) {
1646 if (tree.elementInScope("button")) {
1647 parser.parseError(token.span, "unexpected-start-tag-implies-end-tag", {
1648 "startName": "button",
1649 "endName": "button"
1650 });
1651 processEndTag(new EndTagToken("button"));
1652 return token;
1653 } else {
1654 tree.reconstructActiveFormattingElements();
1655 tree.insertElement(token);
1656 parser.framesetOK = false;
1657 }
1658 return null;
1659 }
1660
1661 void startTagAppletMarqueeObject(StartTagToken token) {
1662 tree.reconstructActiveFormattingElements();
1663 tree.insertElement(token);
1664 tree.activeFormattingElements.add(Marker);
1665 parser.framesetOK = false;
1666 }
1667
1668 void startTagXmp(StartTagToken token) {
1669 if (tree.elementInScope("p", variant: "button")) {
1670 endTagP(new EndTagToken("p"));
1671 }
1672 tree.reconstructActiveFormattingElements();
1673 parser.framesetOK = false;
1674 parser.parseRCDataRawtext(token, "RAWTEXT");
1675 }
1676
1677 void startTagTable(StartTagToken token) {
1678 if (parser.compatMode != "quirks") {
1679 if (tree.elementInScope("p", variant: "button")) {
1680 processEndTag(new EndTagToken("p"));
1681 }
1682 }
1683 tree.insertElement(token);
1684 parser.framesetOK = false;
1685 parser.phase = parser._inTablePhase;
1686 }
1687
1688 void startTagVoidFormatting(StartTagToken token) {
1689 tree.reconstructActiveFormattingElements();
1690 tree.insertElement(token);
1691 tree.openElements.removeLast();
1692 token.selfClosingAcknowledged = true;
1693 parser.framesetOK = false;
1694 }
1695
1696 void startTagInput(StartTagToken token) {
1697 var savedFramesetOK = parser.framesetOK;
1698 startTagVoidFormatting(token);
1699 if (asciiUpper2Lower(token.data["type"]) == "hidden") {
1700 //input type=hidden doesn't change framesetOK
1701 parser.framesetOK = savedFramesetOK;
1702 }
1703 }
1704
1705 void startTagParamSource(StartTagToken token) {
1706 tree.insertElement(token);
1707 tree.openElements.removeLast();
1708 token.selfClosingAcknowledged = true;
1709 }
1710
1711 void startTagHr(StartTagToken token) {
1712 if (tree.elementInScope("p", variant: "button")) {
1713 endTagP(new EndTagToken("p"));
1714 }
1715 tree.insertElement(token);
1716 tree.openElements.removeLast();
1717 token.selfClosingAcknowledged = true;
1718 parser.framesetOK = false;
1719 }
1720
1721 void startTagImage(StartTagToken token) {
1722 // No really...
1723 parser.parseError(token.span, "unexpected-start-tag-treated-as", {
1724 "originalName": "image",
1725 "newName": "img"
1726 });
1727 processStartTag(new StartTagToken("img",
1728 data: token.data, selfClosing: token.selfClosing));
1729 }
1730
1731 void startTagIsIndex(StartTagToken token) {
1732 parser.parseError(token.span, "deprecated-tag", {"name": "isindex"});
1733 if (tree.formPointer != null) {
1734 return;
1735 }
1736 var formAttrs = {};
1737 var dataAction = token.data["action"];
1738 if (dataAction != null) {
1739 formAttrs["action"] = dataAction;
1740 }
1741 processStartTag(new StartTagToken("form", data: formAttrs));
1742 processStartTag(new StartTagToken("hr", data: {}));
1743 processStartTag(new StartTagToken("label", data: {}));
1744 // XXX Localization ...
1745 var prompt = token.data["prompt"];
1746 if (prompt == null) {
1747 prompt = "This is a searchable index. Enter search keywords: ";
1748 }
1749 processCharacters(new CharactersToken(prompt));
1750 var attributes = new LinkedHashMap.from(token.data);
1751 attributes.remove('action');
1752 attributes.remove('prompt');
1753 attributes["name"] = "isindex";
1754 processStartTag(new StartTagToken(
1755 "input", data: attributes, selfClosing: token.selfClosing));
1756 processEndTag(new EndTagToken("label"));
1757 processStartTag(new StartTagToken("hr", data: {}));
1758 processEndTag(new EndTagToken("form"));
1759 }
1760
1761 void startTagTextarea(StartTagToken token) {
1762 tree.insertElement(token);
1763 parser.tokenizer.state = parser.tokenizer.rcdataState;
1764 dropNewline = true;
1765 parser.framesetOK = false;
1766 }
1767
1768 void startTagIFrame(StartTagToken token) {
1769 parser.framesetOK = false;
1770 startTagRawtext(token);
1771 }
1772
1773 /// iframe, noembed noframes, noscript(if scripting enabled).
1774 void startTagRawtext(StartTagToken token) {
1775 parser.parseRCDataRawtext(token, "RAWTEXT");
1776 }
1777
1778 void startTagOpt(StartTagToken token) {
1779 if (tree.openElements.last.localName == "option") {
1780 parser.phase.processEndTag(new EndTagToken("option"));
1781 }
1782 tree.reconstructActiveFormattingElements();
1783 parser.tree.insertElement(token);
1784 }
1785
1786 void startTagSelect(StartTagToken token) {
1787 tree.reconstructActiveFormattingElements();
1788 tree.insertElement(token);
1789 parser.framesetOK = false;
1790
1791 if (parser._inTablePhase == parser.phase ||
1792 parser._inCaptionPhase == parser.phase ||
1793 parser._inColumnGroupPhase == parser.phase ||
1794 parser._inTableBodyPhase == parser.phase ||
1795 parser._inRowPhase == parser.phase ||
1796 parser._inCellPhase == parser.phase) {
1797 parser.phase = parser._inSelectInTablePhase;
1798 } else {
1799 parser.phase = parser._inSelectPhase;
1800 }
1801 }
1802
1803 void startTagRpRt(StartTagToken token) {
1804 if (tree.elementInScope("ruby")) {
1805 tree.generateImpliedEndTags();
1806 var last = tree.openElements.last;
1807 if (last.localName != "ruby") {
1808 parser.parseError(last.sourceSpan, 'undefined-error');
1809 }
1810 }
1811 tree.insertElement(token);
1812 }
1813
1814 void startTagMath(StartTagToken token) {
1815 tree.reconstructActiveFormattingElements();
1816 parser.adjustMathMLAttributes(token);
1817 parser.adjustForeignAttributes(token);
1818 token.namespace = Namespaces.mathml;
1819 tree.insertElement(token);
1820 //Need to get the parse error right for the case where the token
1821 //has a namespace not equal to the xmlns attribute
1822 if (token.selfClosing) {
1823 tree.openElements.removeLast();
1824 token.selfClosingAcknowledged = true;
1825 }
1826 }
1827
1828 void startTagSvg(StartTagToken token) {
1829 tree.reconstructActiveFormattingElements();
1830 parser.adjustSVGAttributes(token);
1831 parser.adjustForeignAttributes(token);
1832 token.namespace = Namespaces.svg;
1833 tree.insertElement(token);
1834 //Need to get the parse error right for the case where the token
1835 //has a namespace not equal to the xmlns attribute
1836 if (token.selfClosing) {
1837 tree.openElements.removeLast();
1838 token.selfClosingAcknowledged = true;
1839 }
1840 }
1841
1842 /// Elements that should be children of other elements that have a
1843 /// different insertion mode; here they are ignored
1844 /// "caption", "col", "colgroup", "frame", "frameset", "head",
1845 /// "option", "optgroup", "tbody", "td", "tfoot", "th", "thead",
1846 /// "tr", "noscript"
1847 void startTagMisplaced(StartTagToken token) {
1848 parser.parseError(
1849 token.span, "unexpected-start-tag-ignored", {"name": token.name});
1850 }
1851
1852 Token startTagOther(StartTagToken token) {
1853 tree.reconstructActiveFormattingElements();
1854 tree.insertElement(token);
1855 return null;
1856 }
1857
1858 void endTagP(EndTagToken token) {
1859 if (!tree.elementInScope("p", variant: "button")) {
1860 startTagCloseP(new StartTagToken("p", data: {}));
1861 parser.parseError(token.span, "unexpected-end-tag", {"name": "p"});
1862 endTagP(new EndTagToken("p"));
1863 } else {
1864 tree.generateImpliedEndTags("p");
1865 if (tree.openElements.last.localName != "p") {
1866 parser.parseError(token.span, "unexpected-end-tag", {"name": "p"});
1867 }
1868 popOpenElementsUntil(token);
1869 }
1870 }
1871
1872 void endTagBody(EndTagToken token) {
1873 if (!tree.elementInScope("body")) {
1874 parser.parseError(token.span, 'undefined-error');
1875 return;
1876 } else if (tree.openElements.last.localName == "body") {
1877 tree.openElements.last.endSourceSpan = token.span;
1878 } else {
1879 for (Element node in slice(tree.openElements, 2)) {
1880 switch (node.localName) {
1881 case "dd":
1882 case "dt":
1883 case "li":
1884 case "optgroup":
1885 case "option":
1886 case "p":
1887 case "rp":
1888 case "rt":
1889 case "tbody":
1890 case "td":
1891 case "tfoot":
1892 case "th":
1893 case "thead":
1894 case "tr":
1895 case "body":
1896 case "html":
1897 continue;
1898 }
1899 // Not sure this is the correct name for the parse error
1900 parser.parseError(token.span, "expected-one-end-tag-but-got-another", {
1901 "gotName": "body",
1902 "expectedName": node.localName
1903 });
1904 break;
1905 }
1906 }
1907 parser.phase = parser._afterBodyPhase;
1908 }
1909
1910 Token endTagHtml(EndTagToken token) {
1911 //We repeat the test for the body end tag token being ignored here
1912 if (tree.elementInScope("body")) {
1913 endTagBody(new EndTagToken("body"));
1914 return token;
1915 }
1916 return null;
1917 }
1918
1919 void endTagBlock(EndTagToken token) {
1920 //Put us back in the right whitespace handling mode
1921 if (token.name == "pre") {
1922 dropNewline = false;
1923 }
1924 var inScope = tree.elementInScope(token.name);
1925 if (inScope) {
1926 tree.generateImpliedEndTags();
1927 }
1928 if (tree.openElements.last.localName != token.name) {
1929 parser.parseError(token.span, "end-tag-too-early", {"name": token.name});
1930 }
1931 if (inScope) {
1932 popOpenElementsUntil(token);
1933 }
1934 }
1935
1936 void endTagForm(EndTagToken token) {
1937 var node = tree.formPointer;
1938 tree.formPointer = null;
1939 if (node == null || !tree.elementInScope(node)) {
1940 parser.parseError(token.span, "unexpected-end-tag", {"name": "form"});
1941 } else {
1942 tree.generateImpliedEndTags();
1943 if (tree.openElements.last != node) {
1944 parser.parseError(
1945 token.span, "end-tag-too-early-ignored", {"name": "form"});
1946 }
1947 tree.openElements.remove(node);
1948 node.endSourceSpan = token.span;
1949 }
1950 }
1951
1952 void endTagListItem(EndTagToken token) {
1953 var variant;
1954 if (token.name == "li") {
1955 variant = "list";
1956 } else {
1957 variant = null;
1958 }
1959 if (!tree.elementInScope(token.name, variant: variant)) {
1960 parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
1961 } else {
1962 tree.generateImpliedEndTags(token.name);
1963 if (tree.openElements.last.localName != token.name) {
1964 parser.parseError(
1965 token.span, "end-tag-too-early", {"name": token.name});
1966 }
1967 popOpenElementsUntil(token);
1968 }
1969 }
1970
1971 void endTagHeading(EndTagToken token) {
1972 for (var item in headingElements) {
1973 if (tree.elementInScope(item)) {
1974 tree.generateImpliedEndTags();
1975 break;
1976 }
1977 }
1978 if (tree.openElements.last.localName != token.name) {
1979 parser.parseError(token.span, "end-tag-too-early", {"name": token.name});
1980 }
1981
1982 for (var item in headingElements) {
1983 if (tree.elementInScope(item)) {
1984 Element node = tree.openElements.removeLast();
1985 while (!headingElements.contains(node.localName)) {
1986 node = tree.openElements.removeLast();
1987 }
1988 if (node != null) {
1989 node.endSourceSpan = token.span;
1990 }
1991 break;
1992 }
1993 }
1994 }
1995
1996 /// The much-feared adoption agency algorithm.
1997 endTagFormatting(EndTagToken token) {
1998 // http://www.whatwg.org/specs/web-apps/current-work/multipage/tree-construc tion.html#adoptionAgency
1999 // TODO(jmesserly): the comments here don't match the numbered steps in the
2000 // updated spec. This needs a pass over it to verify that it still matches.
2001 // In particular the html5lib Python code skiped "step 4", I'm not sure why.
2002 // XXX Better parseError messages appreciated.
2003 int outerLoopCounter = 0;
2004 while (outerLoopCounter < 8) {
2005 outerLoopCounter += 1;
2006
2007 // Step 1 paragraph 1
2008 var formattingElement =
2009 tree.elementInActiveFormattingElements(token.name);
2010 if (formattingElement == null ||
2011 (tree.openElements.contains(formattingElement) &&
2012 !tree.elementInScope(formattingElement.localName))) {
2013 parser.parseError(
2014 token.span, "adoption-agency-1.1", {"name": token.name});
2015 return;
2016 // Step 1 paragraph 2
2017 } else if (!tree.openElements.contains(formattingElement)) {
2018 parser.parseError(
2019 token.span, "adoption-agency-1.2", {"name": token.name});
2020 tree.activeFormattingElements.remove(formattingElement);
2021 return;
2022 }
2023
2024 // Step 1 paragraph 3
2025 if (formattingElement != tree.openElements.last) {
2026 parser.parseError(
2027 token.span, "adoption-agency-1.3", {"name": token.name});
2028 }
2029
2030 // Step 2
2031 // Start of the adoption agency algorithm proper
2032 var afeIndex = tree.openElements.indexOf(formattingElement);
2033 Node furthestBlock = null;
2034 for (Node element in slice(tree.openElements, afeIndex)) {
2035 if (specialElements.contains(getElementNameTuple(element))) {
2036 furthestBlock = element;
2037 break;
2038 }
2039 }
2040 // Step 3
2041 if (furthestBlock == null) {
2042 Element element = tree.openElements.removeLast();
2043 while (element != formattingElement) {
2044 element = tree.openElements.removeLast();
2045 }
2046 if (element != null) {
2047 element.endSourceSpan = token.span;
2048 }
2049 tree.activeFormattingElements.remove(element);
2050 return;
2051 }
2052
2053 var commonAncestor = tree.openElements[afeIndex - 1];
2054
2055 // Step 5
2056 // The bookmark is supposed to help us identify where to reinsert
2057 // nodes in step 12. We have to ensure that we reinsert nodes after
2058 // the node before the active formatting element. Note the bookmark
2059 // can move in step 7.4
2060 var bookmark = tree.activeFormattingElements.indexOf(formattingElement);
2061
2062 // Step 6
2063 Node lastNode = furthestBlock;
2064 var node = furthestBlock;
2065 int innerLoopCounter = 0;
2066
2067 var index = tree.openElements.indexOf(node);
2068 while (innerLoopCounter < 3) {
2069 innerLoopCounter += 1;
2070
2071 // Node is element before node in open elements
2072 index -= 1;
2073 node = tree.openElements[index];
2074 if (!tree.activeFormattingElements.contains(node)) {
2075 tree.openElements.remove(node);
2076 continue;
2077 }
2078 // Step 6.3
2079 if (node == formattingElement) {
2080 break;
2081 }
2082 // Step 6.4
2083 if (lastNode == furthestBlock) {
2084 bookmark = (tree.activeFormattingElements.indexOf(node) + 1);
2085 }
2086 // Step 6.5
2087 //cite = node.parent
2088 var clone = node.clone(false);
2089 // Replace node with clone
2090 tree.activeFormattingElements[
2091 tree.activeFormattingElements.indexOf(node)] = clone;
2092 tree.openElements[tree.openElements.indexOf(node)] = clone;
2093 node = clone;
2094
2095 // Step 6.6
2096 // Remove lastNode from its parents, if any
2097 if (lastNode.parentNode != null) {
2098 lastNode.parentNode.nodes.remove(lastNode);
2099 }
2100 node.nodes.add(lastNode);
2101 // Step 7.7
2102 lastNode = node;
2103 // End of inner loop
2104 }
2105
2106 // Step 7
2107 // Foster parent lastNode if commonAncestor is a
2108 // table, tbody, tfoot, thead, or tr we need to foster parent the
2109 // lastNode
2110 if (lastNode.parentNode != null) {
2111 lastNode.parentNode.nodes.remove(lastNode);
2112 }
2113
2114 if (const [
2115 "table",
2116 "tbody",
2117 "tfoot",
2118 "thead",
2119 "tr"
2120 ].contains(commonAncestor.localName)) {
2121 var nodePos = tree.getTableMisnestedNodePosition();
2122 nodePos[0].insertBefore(lastNode, nodePos[1]);
2123 } else {
2124 commonAncestor.nodes.add(lastNode);
2125 }
2126
2127 // Step 8
2128 var clone = formattingElement.clone(false);
2129
2130 // Step 9
2131 furthestBlock.reparentChildren(clone);
2132
2133 // Step 10
2134 furthestBlock.nodes.add(clone);
2135
2136 // Step 11
2137 tree.activeFormattingElements.remove(formattingElement);
2138 tree.activeFormattingElements.insert(
2139 min(bookmark, tree.activeFormattingElements.length), clone);
2140
2141 // Step 12
2142 tree.openElements.remove(formattingElement);
2143 tree.openElements.insert(
2144 tree.openElements.indexOf(furthestBlock) + 1, clone);
2145 }
2146 }
2147
2148 void endTagAppletMarqueeObject(EndTagToken token) {
2149 if (tree.elementInScope(token.name)) {
2150 tree.generateImpliedEndTags();
2151 }
2152 if (tree.openElements.last.localName != token.name) {
2153 parser.parseError(token.span, "end-tag-too-early", {"name": token.name});
2154 }
2155 if (tree.elementInScope(token.name)) {
2156 popOpenElementsUntil(token);
2157 tree.clearActiveFormattingElements();
2158 }
2159 }
2160
2161 void endTagBr(EndTagToken token) {
2162 parser.parseError(token.span, "unexpected-end-tag-treated-as", {
2163 "originalName": "br",
2164 "newName": "br element"
2165 });
2166 tree.reconstructActiveFormattingElements();
2167 tree.insertElement(new StartTagToken("br", data: {}));
2168 tree.openElements.removeLast();
2169 }
2170
2171 void endTagOther(EndTagToken token) {
2172 for (var node in tree.openElements.reversed) {
2173 if (node.localName == token.name) {
2174 tree.generateImpliedEndTags(token.name);
2175 if (tree.openElements.last.localName != token.name) {
2176 parser.parseError(
2177 token.span, "unexpected-end-tag", {"name": token.name});
2178 }
2179 while (tree.openElements.removeLast() != node);
2180 node.endSourceSpan = token.span;
2181 break;
2182 } else {
2183 if (specialElements.contains(getElementNameTuple(node))) {
2184 parser.parseError(
2185 token.span, "unexpected-end-tag", {"name": token.name});
2186 break;
2187 }
2188 }
2189 }
2190 }
2191 }
2192
2193 class TextPhase extends Phase {
2194 TextPhase(parser) : super(parser);
2195
2196 // "Tried to process start tag %s in RCDATA/RAWTEXT mode"%token.name
2197 processStartTag(StartTagToken token) {
2198 assert(false);
2199 }
2200
2201 processEndTag(EndTagToken token) {
2202 if (token.name == 'script') return endTagScript(token);
2203 return endTagOther(token);
2204 }
2205
2206 Token processCharacters(CharactersToken token) {
2207 tree.insertText(token.data, token.span);
2208 return null;
2209 }
2210
2211 bool processEOF() {
2212 var last = tree.openElements.last;
2213 parser.parseError(last.sourceSpan, "expected-named-closing-tag-but-got-eof",
2214 {'name': last.localName});
2215 tree.openElements.removeLast();
2216 parser.phase = parser.originalPhase;
2217 return true;
2218 }
2219
2220 void endTagScript(EndTagToken token) {
2221 var node = tree.openElements.removeLast();
2222 assert(node.localName == "script");
2223 parser.phase = parser.originalPhase;
2224 //The rest of this method is all stuff that only happens if
2225 //document.write works
2226 }
2227
2228 void endTagOther(EndTagToken token) {
2229 tree.openElements.removeLast();
2230 parser.phase = parser.originalPhase;
2231 }
2232 }
2233
2234 class InTablePhase extends Phase {
2235 // http://www.whatwg.org/specs/web-apps/current-work///in-table
2236 InTablePhase(parser) : super(parser);
2237
2238 processStartTag(StartTagToken token) {
2239 switch (token.name) {
2240 case "html":
2241 return startTagHtml(token);
2242 case "caption":
2243 return startTagCaption(token);
2244 case "colgroup":
2245 return startTagColgroup(token);
2246 case "col":
2247 return startTagCol(token);
2248 case "tbody":
2249 case "tfoot":
2250 case "thead":
2251 return startTagRowGroup(token);
2252 case "td":
2253 case "th":
2254 case "tr":
2255 return startTagImplyTbody(token);
2256 case "table":
2257 return startTagTable(token);
2258 case "style":
2259 case "script":
2260 return startTagStyleScript(token);
2261 case "input":
2262 return startTagInput(token);
2263 case "form":
2264 return startTagForm(token);
2265 default:
2266 return startTagOther(token);
2267 }
2268 }
2269
2270 processEndTag(EndTagToken token) {
2271 switch (token.name) {
2272 case "table":
2273 return endTagTable(token);
2274 case "body":
2275 case "caption":
2276 case "col":
2277 case "colgroup":
2278 case "html":
2279 case "tbody":
2280 case "td":
2281 case "tfoot":
2282 case "th":
2283 case "thead":
2284 case "tr":
2285 return endTagIgnore(token);
2286 default:
2287 return endTagOther(token);
2288 }
2289 }
2290
2291 // helper methods
2292 void clearStackToTableContext() {
2293 // "clear the stack back to a table context"
2294 while (tree.openElements.last.localName != "table" &&
2295 tree.openElements.last.localName != "html") {
2296 //parser.parseError(token.span, "unexpected-implied-end-tag-in-table",
2297 // {"name": tree.openElements.last.name})
2298 tree.openElements.removeLast();
2299 }
2300 // When the current node is <html> it's an innerHTML case
2301 }
2302
2303 // processing methods
2304 bool processEOF() {
2305 var last = tree.openElements.last;
2306 if (last.localName != "html") {
2307 parser.parseError(last.sourceSpan, "eof-in-table");
2308 } else {
2309 assert(parser.innerHTMLMode);
2310 }
2311 //Stop parsing
2312 return false;
2313 }
2314
2315 Token processSpaceCharacters(SpaceCharactersToken token) {
2316 var originalPhase = parser.phase;
2317 parser.phase = parser._inTableTextPhase;
2318 parser._inTableTextPhase.originalPhase = originalPhase;
2319 parser.phase.processSpaceCharacters(token);
2320 return null;
2321 }
2322
2323 Token processCharacters(CharactersToken token) {
2324 var originalPhase = parser.phase;
2325 parser.phase = parser._inTableTextPhase;
2326 parser._inTableTextPhase.originalPhase = originalPhase;
2327 parser.phase.processCharacters(token);
2328 return null;
2329 }
2330
2331 void insertText(CharactersToken token) {
2332 // If we get here there must be at least one non-whitespace character
2333 // Do the table magic!
2334 tree.insertFromTable = true;
2335 parser._inBodyPhase.processCharacters(token);
2336 tree.insertFromTable = false;
2337 }
2338
2339 void startTagCaption(StartTagToken token) {
2340 clearStackToTableContext();
2341 tree.activeFormattingElements.add(Marker);
2342 tree.insertElement(token);
2343 parser.phase = parser._inCaptionPhase;
2344 }
2345
2346 void startTagColgroup(StartTagToken token) {
2347 clearStackToTableContext();
2348 tree.insertElement(token);
2349 parser.phase = parser._inColumnGroupPhase;
2350 }
2351
2352 Token startTagCol(StartTagToken token) {
2353 startTagColgroup(new StartTagToken("colgroup", data: {}));
2354 return token;
2355 }
2356
2357 void startTagRowGroup(StartTagToken token) {
2358 clearStackToTableContext();
2359 tree.insertElement(token);
2360 parser.phase = parser._inTableBodyPhase;
2361 }
2362
2363 Token startTagImplyTbody(StartTagToken token) {
2364 startTagRowGroup(new StartTagToken("tbody", data: {}));
2365 return token;
2366 }
2367
2368 Token startTagTable(StartTagToken token) {
2369 parser.parseError(token.span, "unexpected-start-tag-implies-end-tag", {
2370 "startName": "table",
2371 "endName": "table"
2372 });
2373 parser.phase.processEndTag(new EndTagToken("table"));
2374 if (!parser.innerHTMLMode) {
2375 return token;
2376 }
2377 return null;
2378 }
2379
2380 Token startTagStyleScript(StartTagToken token) {
2381 return parser._inHeadPhase.processStartTag(token);
2382 }
2383
2384 void startTagInput(StartTagToken token) {
2385 if (asciiUpper2Lower(token.data["type"]) == "hidden") {
2386 parser.parseError(token.span, "unexpected-hidden-input-in-table");
2387 tree.insertElement(token);
2388 // XXX associate with form
2389 tree.openElements.removeLast();
2390 } else {
2391 startTagOther(token);
2392 }
2393 }
2394
2395 void startTagForm(StartTagToken token) {
2396 parser.parseError(token.span, "unexpected-form-in-table");
2397 if (tree.formPointer == null) {
2398 tree.insertElement(token);
2399 tree.formPointer = tree.openElements.last;
2400 tree.openElements.removeLast();
2401 }
2402 }
2403
2404 void startTagOther(StartTagToken token) {
2405 parser.parseError(token.span, "unexpected-start-tag-implies-table-voodoo", {
2406 "name": token.name
2407 });
2408 // Do the table magic!
2409 tree.insertFromTable = true;
2410 parser._inBodyPhase.processStartTag(token);
2411 tree.insertFromTable = false;
2412 }
2413
2414 void endTagTable(EndTagToken token) {
2415 if (tree.elementInScope("table", variant: "table")) {
2416 tree.generateImpliedEndTags();
2417 var last = tree.openElements.last;
2418 if (last.localName != "table") {
2419 parser.parseError(token.span, "end-tag-too-early-named", {
2420 "gotName": "table",
2421 "expectedName": last.localName
2422 });
2423 }
2424 while (tree.openElements.last.localName != "table") {
2425 tree.openElements.removeLast();
2426 }
2427 var node = tree.openElements.removeLast();
2428 node.endSourceSpan = token.span;
2429 parser.resetInsertionMode();
2430 } else {
2431 // innerHTML case
2432 assert(parser.innerHTMLMode);
2433 parser.parseError(token.span, "undefined-error");
2434 }
2435 }
2436
2437 void endTagIgnore(EndTagToken token) {
2438 parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
2439 }
2440
2441 void endTagOther(EndTagToken token) {
2442 parser.parseError(token.span, "unexpected-end-tag-implies-table-voodoo", {
2443 "name": token.name
2444 });
2445 // Do the table magic!
2446 tree.insertFromTable = true;
2447 parser._inBodyPhase.processEndTag(token);
2448 tree.insertFromTable = false;
2449 }
2450 }
2451
2452 class InTableTextPhase extends Phase {
2453 Phase originalPhase;
2454 List<StringToken> characterTokens;
2455
2456 InTableTextPhase(parser)
2457 : characterTokens = <StringToken>[],
2458 super(parser);
2459
2460 void flushCharacters() {
2461 if (characterTokens.length == 0) return;
2462
2463 // TODO(sigmund,jmesserly): remove '' (dartbug.com/8480)
2464 var data = characterTokens.map((t) => t.data).join('');
2465 var span = null;
2466
2467 if (parser.generateSpans) {
2468 span = characterTokens[0].span.expand(characterTokens.last.span);
2469 }
2470
2471 if (!allWhitespace(data)) {
2472 parser._inTablePhase.insertText(new CharactersToken(data)..span = span);
2473 } else if (data.length > 0) {
2474 tree.insertText(data, span);
2475 }
2476 characterTokens = <StringToken>[];
2477 }
2478
2479 Token processComment(CommentToken token) {
2480 flushCharacters();
2481 parser.phase = originalPhase;
2482 return token;
2483 }
2484
2485 bool processEOF() {
2486 flushCharacters();
2487 parser.phase = originalPhase;
2488 return true;
2489 }
2490
2491 Token processCharacters(CharactersToken token) {
2492 if (token.data == "\u0000") {
2493 return null;
2494 }
2495 characterTokens.add(token);
2496 return null;
2497 }
2498
2499 Token processSpaceCharacters(SpaceCharactersToken token) {
2500 //pretty sure we should never reach here
2501 characterTokens.add(token);
2502 // XXX assert(false);
2503 return null;
2504 }
2505
2506 Token processStartTag(StartTagToken token) {
2507 flushCharacters();
2508 parser.phase = originalPhase;
2509 return token;
2510 }
2511
2512 Token processEndTag(EndTagToken token) {
2513 flushCharacters();
2514 parser.phase = originalPhase;
2515 return token;
2516 }
2517 }
2518
2519 class InCaptionPhase extends Phase {
2520 // http://www.whatwg.org/specs/web-apps/current-work///in-caption
2521 InCaptionPhase(parser) : super(parser);
2522
2523 processStartTag(StartTagToken token) {
2524 switch (token.name) {
2525 case "html":
2526 return startTagHtml(token);
2527 case "caption":
2528 case "col":
2529 case "colgroup":
2530 case "tbody":
2531 case "td":
2532 case "tfoot":
2533 case "th":
2534 case "thead":
2535 case "tr":
2536 return startTagTableElement(token);
2537 default:
2538 return startTagOther(token);
2539 }
2540 }
2541
2542 processEndTag(EndTagToken token) {
2543 switch (token.name) {
2544 case "caption":
2545 return endTagCaption(token);
2546 case "table":
2547 return endTagTable(token);
2548 case "body":
2549 case "col":
2550 case "colgroup":
2551 case "html":
2552 case "tbody":
2553 case "td":
2554 case "tfoot":
2555 case "th":
2556 case "thead":
2557 case "tr":
2558 return endTagIgnore(token);
2559 default:
2560 return endTagOther(token);
2561 }
2562 }
2563
2564 bool ignoreEndTagCaption() {
2565 return !tree.elementInScope("caption", variant: "table");
2566 }
2567
2568 bool processEOF() {
2569 parser._inBodyPhase.processEOF();
2570 return false;
2571 }
2572
2573 Token processCharacters(CharactersToken token) {
2574 return parser._inBodyPhase.processCharacters(token);
2575 }
2576
2577 Token startTagTableElement(StartTagToken token) {
2578 parser.parseError(token.span, "undefined-error");
2579 //XXX Have to duplicate logic here to find out if the tag is ignored
2580 var ignoreEndTag = ignoreEndTagCaption();
2581 parser.phase.processEndTag(new EndTagToken("caption"));
2582 if (!ignoreEndTag) {
2583 return token;
2584 }
2585 return null;
2586 }
2587
2588 Token startTagOther(StartTagToken token) {
2589 return parser._inBodyPhase.processStartTag(token);
2590 }
2591
2592 void endTagCaption(EndTagToken token) {
2593 if (!ignoreEndTagCaption()) {
2594 // AT this code is quite similar to endTagTable in "InTable"
2595 tree.generateImpliedEndTags();
2596 if (tree.openElements.last.localName != "caption") {
2597 parser.parseError(token.span, "expected-one-end-tag-but-got-another", {
2598 "gotName": "caption",
2599 "expectedName": tree.openElements.last.localName
2600 });
2601 }
2602 while (tree.openElements.last.localName != "caption") {
2603 tree.openElements.removeLast();
2604 }
2605 var node = tree.openElements.removeLast();
2606 node.endSourceSpan = token.span;
2607 tree.clearActiveFormattingElements();
2608 parser.phase = parser._inTablePhase;
2609 } else {
2610 // innerHTML case
2611 assert(parser.innerHTMLMode);
2612 parser.parseError(token.span, "undefined-error");
2613 }
2614 }
2615
2616 Token endTagTable(EndTagToken token) {
2617 parser.parseError(token.span, "undefined-error");
2618 var ignoreEndTag = ignoreEndTagCaption();
2619 parser.phase.processEndTag(new EndTagToken("caption"));
2620 if (!ignoreEndTag) {
2621 return token;
2622 }
2623 return null;
2624 }
2625
2626 void endTagIgnore(EndTagToken token) {
2627 parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
2628 }
2629
2630 Token endTagOther(EndTagToken token) {
2631 return parser._inBodyPhase.processEndTag(token);
2632 }
2633 }
2634
2635 class InColumnGroupPhase extends Phase {
2636 // http://www.whatwg.org/specs/web-apps/current-work///in-column
2637 InColumnGroupPhase(parser) : super(parser);
2638
2639 processStartTag(StartTagToken token) {
2640 switch (token.name) {
2641 case "html":
2642 return startTagHtml(token);
2643 case "col":
2644 return startTagCol(token);
2645 default:
2646 return startTagOther(token);
2647 }
2648 }
2649
2650 processEndTag(EndTagToken token) {
2651 switch (token.name) {
2652 case "colgroup":
2653 return endTagColgroup(token);
2654 case "col":
2655 return endTagCol(token);
2656 default:
2657 return endTagOther(token);
2658 }
2659 }
2660
2661 bool ignoreEndTagColgroup() {
2662 return tree.openElements.last.localName == "html";
2663 }
2664
2665 bool processEOF() {
2666 var ignoreEndTag = ignoreEndTagColgroup();
2667 if (ignoreEndTag) {
2668 assert(parser.innerHTMLMode);
2669 return false;
2670 } else {
2671 endTagColgroup(new EndTagToken("colgroup"));
2672 return true;
2673 }
2674 }
2675
2676 Token processCharacters(CharactersToken token) {
2677 var ignoreEndTag = ignoreEndTagColgroup();
2678 endTagColgroup(new EndTagToken("colgroup"));
2679 return ignoreEndTag ? null : token;
2680 }
2681
2682 void startTagCol(StartTagToken token) {
2683 tree.insertElement(token);
2684 tree.openElements.removeLast();
2685 }
2686
2687 Token startTagOther(StartTagToken token) {
2688 var ignoreEndTag = ignoreEndTagColgroup();
2689 endTagColgroup(new EndTagToken("colgroup"));
2690 return ignoreEndTag ? null : token;
2691 }
2692
2693 void endTagColgroup(EndTagToken token) {
2694 if (ignoreEndTagColgroup()) {
2695 // innerHTML case
2696 assert(parser.innerHTMLMode);
2697 parser.parseError(token.span, "undefined-error");
2698 } else {
2699 var node = tree.openElements.removeLast();
2700 node.endSourceSpan = token.span;
2701 parser.phase = parser._inTablePhase;
2702 }
2703 }
2704
2705 void endTagCol(EndTagToken token) {
2706 parser.parseError(token.span, "no-end-tag", {"name": "col"});
2707 }
2708
2709 Token endTagOther(EndTagToken token) {
2710 var ignoreEndTag = ignoreEndTagColgroup();
2711 endTagColgroup(new EndTagToken("colgroup"));
2712 return ignoreEndTag ? null : token;
2713 }
2714 }
2715
2716 class InTableBodyPhase extends Phase {
2717 // http://www.whatwg.org/specs/web-apps/current-work///in-table0
2718 InTableBodyPhase(parser) : super(parser);
2719
2720 processStartTag(StartTagToken token) {
2721 switch (token.name) {
2722 case "html":
2723 return startTagHtml(token);
2724 case "tr":
2725 return startTagTr(token);
2726 case "td":
2727 case "th":
2728 return startTagTableCell(token);
2729 case "caption":
2730 case "col":
2731 case "colgroup":
2732 case "tbody":
2733 case "tfoot":
2734 case "thead":
2735 return startTagTableOther(token);
2736 default:
2737 return startTagOther(token);
2738 }
2739 }
2740
2741 processEndTag(EndTagToken token) {
2742 switch (token.name) {
2743 case "tbody":
2744 case "tfoot":
2745 case "thead":
2746 return endTagTableRowGroup(token);
2747 case "table":
2748 return endTagTable(token);
2749 case "body":
2750 case "caption":
2751 case "col":
2752 case "colgroup":
2753 case "html":
2754 case "td":
2755 case "th":
2756 case "tr":
2757 return endTagIgnore(token);
2758 default:
2759 return endTagOther(token);
2760 }
2761 }
2762
2763 // helper methods
2764 void clearStackToTableBodyContext() {
2765 var tableTags = const ["tbody", "tfoot", "thead", "html"];
2766 while (!tableTags.contains(tree.openElements.last.localName)) {
2767 //XXX parser.parseError(token.span, "unexpected-implied-end-tag-in-table",
2768 // {"name": tree.openElements.last.name})
2769 tree.openElements.removeLast();
2770 }
2771 if (tree.openElements.last.localName == "html") {
2772 assert(parser.innerHTMLMode);
2773 }
2774 }
2775
2776 // the rest
2777 bool processEOF() {
2778 parser._inTablePhase.processEOF();
2779 return false;
2780 }
2781
2782 Token processSpaceCharacters(SpaceCharactersToken token) {
2783 return parser._inTablePhase.processSpaceCharacters(token);
2784 }
2785
2786 Token processCharacters(CharactersToken token) {
2787 return parser._inTablePhase.processCharacters(token);
2788 }
2789
2790 void startTagTr(StartTagToken token) {
2791 clearStackToTableBodyContext();
2792 tree.insertElement(token);
2793 parser.phase = parser._inRowPhase;
2794 }
2795
2796 Token startTagTableCell(StartTagToken token) {
2797 parser.parseError(
2798 token.span, "unexpected-cell-in-table-body", {"name": token.name});
2799 startTagTr(new StartTagToken("tr", data: {}));
2800 return token;
2801 }
2802
2803 Token startTagTableOther(token) => endTagTable(token);
2804
2805 Token startTagOther(StartTagToken token) {
2806 return parser._inTablePhase.processStartTag(token);
2807 }
2808
2809 void endTagTableRowGroup(EndTagToken token) {
2810 if (tree.elementInScope(token.name, variant: "table")) {
2811 clearStackToTableBodyContext();
2812 var node = tree.openElements.removeLast();
2813 node.endSourceSpan = token.span;
2814 parser.phase = parser._inTablePhase;
2815 } else {
2816 parser.parseError(
2817 token.span, "unexpected-end-tag-in-table-body", {"name": token.name});
2818 }
2819 }
2820
2821 Token endTagTable(TagToken token) {
2822 // XXX AT Any ideas on how to share this with endTagTable?
2823 if (tree.elementInScope("tbody", variant: "table") ||
2824 tree.elementInScope("thead", variant: "table") ||
2825 tree.elementInScope("tfoot", variant: "table")) {
2826 clearStackToTableBodyContext();
2827 endTagTableRowGroup(new EndTagToken(tree.openElements.last.localName));
2828 return token;
2829 } else {
2830 // innerHTML case
2831 assert(parser.innerHTMLMode);
2832 parser.parseError(token.span, "undefined-error");
2833 }
2834 return null;
2835 }
2836
2837 void endTagIgnore(EndTagToken token) {
2838 parser.parseError(
2839 token.span, "unexpected-end-tag-in-table-body", {"name": token.name});
2840 }
2841
2842 Token endTagOther(EndTagToken token) {
2843 return parser._inTablePhase.processEndTag(token);
2844 }
2845 }
2846
2847 class InRowPhase extends Phase {
2848 // http://www.whatwg.org/specs/web-apps/current-work///in-row
2849 InRowPhase(parser) : super(parser);
2850
2851 processStartTag(StartTagToken token) {
2852 switch (token.name) {
2853 case "html":
2854 return startTagHtml(token);
2855 case "td":
2856 case "th":
2857 return startTagTableCell(token);
2858 case "caption":
2859 case "col":
2860 case "colgroup":
2861 case "tbody":
2862 case "tfoot":
2863 case "thead":
2864 case "tr":
2865 return startTagTableOther(token);
2866 default:
2867 return startTagOther(token);
2868 }
2869 }
2870
2871 processEndTag(EndTagToken token) {
2872 switch (token.name) {
2873 case "tr":
2874 return endTagTr(token);
2875 case "table":
2876 return endTagTable(token);
2877 case "tbody":
2878 case "tfoot":
2879 case "thead":
2880 return endTagTableRowGroup(token);
2881 case "body":
2882 case "caption":
2883 case "col":
2884 case "colgroup":
2885 case "html":
2886 case "td":
2887 case "th":
2888 return endTagIgnore(token);
2889 default:
2890 return endTagOther(token);
2891 }
2892 }
2893
2894 // helper methods (XXX unify this with other table helper methods)
2895 void clearStackToTableRowContext() {
2896 while (true) {
2897 var last = tree.openElements.last;
2898 if (last.localName == "tr" || last.localName == "html") break;
2899
2900 parser.parseError(last.sourceSpan,
2901 "unexpected-implied-end-tag-in-table-row", {
2902 "name": tree.openElements.last.localName
2903 });
2904 tree.openElements.removeLast();
2905 }
2906 }
2907
2908 bool ignoreEndTagTr() {
2909 return !tree.elementInScope("tr", variant: "table");
2910 }
2911
2912 // the rest
2913 bool processEOF() {
2914 parser._inTablePhase.processEOF();
2915 return false;
2916 }
2917
2918 Token processSpaceCharacters(SpaceCharactersToken token) {
2919 return parser._inTablePhase.processSpaceCharacters(token);
2920 }
2921
2922 Token processCharacters(CharactersToken token) {
2923 return parser._inTablePhase.processCharacters(token);
2924 }
2925
2926 void startTagTableCell(StartTagToken token) {
2927 clearStackToTableRowContext();
2928 tree.insertElement(token);
2929 parser.phase = parser._inCellPhase;
2930 tree.activeFormattingElements.add(Marker);
2931 }
2932
2933 Token startTagTableOther(StartTagToken token) {
2934 bool ignoreEndTag = ignoreEndTagTr();
2935 endTagTr(new EndTagToken("tr"));
2936 // XXX how are we sure it's always ignored in the innerHTML case?
2937 return ignoreEndTag ? null : token;
2938 }
2939
2940 Token startTagOther(StartTagToken token) {
2941 return parser._inTablePhase.processStartTag(token);
2942 }
2943
2944 void endTagTr(EndTagToken token) {
2945 if (!ignoreEndTagTr()) {
2946 clearStackToTableRowContext();
2947 var node = tree.openElements.removeLast();
2948 node.endSourceSpan = token.span;
2949 parser.phase = parser._inTableBodyPhase;
2950 } else {
2951 // innerHTML case
2952 assert(parser.innerHTMLMode);
2953 parser.parseError(token.span, "undefined-error");
2954 }
2955 }
2956
2957 Token endTagTable(EndTagToken token) {
2958 var ignoreEndTag = ignoreEndTagTr();
2959 endTagTr(new EndTagToken("tr"));
2960 // Reprocess the current tag if the tr end tag was not ignored
2961 // XXX how are we sure it's always ignored in the innerHTML case?
2962 return ignoreEndTag ? null : token;
2963 }
2964
2965 Token endTagTableRowGroup(EndTagToken token) {
2966 if (tree.elementInScope(token.name, variant: "table")) {
2967 endTagTr(new EndTagToken("tr"));
2968 return token;
2969 } else {
2970 parser.parseError(token.span, "undefined-error");
2971 return null;
2972 }
2973 }
2974
2975 void endTagIgnore(EndTagToken token) {
2976 parser.parseError(
2977 token.span, "unexpected-end-tag-in-table-row", {"name": token.name});
2978 }
2979
2980 Token endTagOther(EndTagToken token) {
2981 return parser._inTablePhase.processEndTag(token);
2982 }
2983 }
2984
2985 class InCellPhase extends Phase {
2986 // http://www.whatwg.org/specs/web-apps/current-work///in-cell
2987 InCellPhase(parser) : super(parser);
2988
2989 processStartTag(StartTagToken token) {
2990 switch (token.name) {
2991 case "html":
2992 return startTagHtml(token);
2993 case "caption":
2994 case "col":
2995 case "colgroup":
2996 case "tbody":
2997 case "td":
2998 case "tfoot":
2999 case "th":
3000 case "thead":
3001 case "tr":
3002 return startTagTableOther(token);
3003 default:
3004 return startTagOther(token);
3005 }
3006 }
3007
3008 processEndTag(EndTagToken token) {
3009 switch (token.name) {
3010 case "td":
3011 case "th":
3012 return endTagTableCell(token);
3013 case "body":
3014 case "caption":
3015 case "col":
3016 case "colgroup":
3017 case "html":
3018 return endTagIgnore(token);
3019 case "table":
3020 case "tbody":
3021 case "tfoot":
3022 case "thead":
3023 case "tr":
3024 return endTagImply(token);
3025 default:
3026 return endTagOther(token);
3027 }
3028 }
3029
3030 // helper
3031 void closeCell() {
3032 if (tree.elementInScope("td", variant: "table")) {
3033 endTagTableCell(new EndTagToken("td"));
3034 } else if (tree.elementInScope("th", variant: "table")) {
3035 endTagTableCell(new EndTagToken("th"));
3036 }
3037 }
3038
3039 // the rest
3040 bool processEOF() {
3041 parser._inBodyPhase.processEOF();
3042 return false;
3043 }
3044
3045 Token processCharacters(CharactersToken token) {
3046 return parser._inBodyPhase.processCharacters(token);
3047 }
3048
3049 Token startTagTableOther(StartTagToken token) {
3050 if (tree.elementInScope("td", variant: "table") ||
3051 tree.elementInScope("th", variant: "table")) {
3052 closeCell();
3053 return token;
3054 } else {
3055 // innerHTML case
3056 assert(parser.innerHTMLMode);
3057 parser.parseError(token.span, "undefined-error");
3058 return null;
3059 }
3060 }
3061
3062 Token startTagOther(StartTagToken token) {
3063 return parser._inBodyPhase.processStartTag(token);
3064 }
3065
3066 void endTagTableCell(EndTagToken token) {
3067 if (tree.elementInScope(token.name, variant: "table")) {
3068 tree.generateImpliedEndTags(token.name);
3069 if (tree.openElements.last.localName != token.name) {
3070 parser.parseError(
3071 token.span, "unexpected-cell-end-tag", {"name": token.name});
3072 popOpenElementsUntil(token);
3073 } else {
3074 var node = tree.openElements.removeLast();
3075 node.endSourceSpan = token.span;
3076 }
3077 tree.clearActiveFormattingElements();
3078 parser.phase = parser._inRowPhase;
3079 } else {
3080 parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
3081 }
3082 }
3083
3084 void endTagIgnore(EndTagToken token) {
3085 parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
3086 }
3087
3088 Token endTagImply(EndTagToken token) {
3089 if (tree.elementInScope(token.name, variant: "table")) {
3090 closeCell();
3091 return token;
3092 } else {
3093 // sometimes innerHTML case
3094 parser.parseError(token.span, "undefined-error");
3095 }
3096 return null;
3097 }
3098
3099 Token endTagOther(EndTagToken token) {
3100 return parser._inBodyPhase.processEndTag(token);
3101 }
3102 }
3103
3104 class InSelectPhase extends Phase {
3105 InSelectPhase(parser) : super(parser);
3106
3107 processStartTag(StartTagToken token) {
3108 switch (token.name) {
3109 case "html":
3110 return startTagHtml(token);
3111 case "option":
3112 return startTagOption(token);
3113 case "optgroup":
3114 return startTagOptgroup(token);
3115 case "select":
3116 return startTagSelect(token);
3117 case "input":
3118 case "keygen":
3119 case "textarea":
3120 return startTagInput(token);
3121 case "script":
3122 return startTagScript(token);
3123 default:
3124 return startTagOther(token);
3125 }
3126 }
3127
3128 processEndTag(EndTagToken token) {
3129 switch (token.name) {
3130 case "option":
3131 return endTagOption(token);
3132 case "optgroup":
3133 return endTagOptgroup(token);
3134 case "select":
3135 return endTagSelect(token);
3136 default:
3137 return endTagOther(token);
3138 }
3139 }
3140
3141 // http://www.whatwg.org/specs/web-apps/current-work///in-select
3142 bool processEOF() {
3143 var last = tree.openElements.last;
3144 if (last.localName != "html") {
3145 parser.parseError(last.sourceSpan, "eof-in-select");
3146 } else {
3147 assert(parser.innerHTMLMode);
3148 }
3149 return false;
3150 }
3151
3152 Token processCharacters(CharactersToken token) {
3153 if (token.data == "\u0000") {
3154 return null;
3155 }
3156 tree.insertText(token.data, token.span);
3157 return null;
3158 }
3159
3160 void startTagOption(StartTagToken token) {
3161 // We need to imply </option> if <option> is the current node.
3162 if (tree.openElements.last.localName == "option") {
3163 tree.openElements.removeLast();
3164 }
3165 tree.insertElement(token);
3166 }
3167
3168 void startTagOptgroup(StartTagToken token) {
3169 if (tree.openElements.last.localName == "option") {
3170 tree.openElements.removeLast();
3171 }
3172 if (tree.openElements.last.localName == "optgroup") {
3173 tree.openElements.removeLast();
3174 }
3175 tree.insertElement(token);
3176 }
3177
3178 void startTagSelect(StartTagToken token) {
3179 parser.parseError(token.span, "unexpected-select-in-select");
3180 endTagSelect(new EndTagToken("select"));
3181 }
3182
3183 Token startTagInput(StartTagToken token) {
3184 parser.parseError(token.span, "unexpected-input-in-select");
3185 if (tree.elementInScope("select", variant: "select")) {
3186 endTagSelect(new EndTagToken("select"));
3187 return token;
3188 } else {
3189 assert(parser.innerHTMLMode);
3190 }
3191 return null;
3192 }
3193
3194 Token startTagScript(StartTagToken token) {
3195 return parser._inHeadPhase.processStartTag(token);
3196 }
3197
3198 Token startTagOther(StartTagToken token) {
3199 parser.parseError(
3200 token.span, "unexpected-start-tag-in-select", {"name": token.name});
3201 return null;
3202 }
3203
3204 void endTagOption(EndTagToken token) {
3205 if (tree.openElements.last.localName == "option") {
3206 var node = tree.openElements.removeLast();
3207 node.endSourceSpan = token.span;
3208 } else {
3209 parser.parseError(
3210 token.span, "unexpected-end-tag-in-select", {"name": "option"});
3211 }
3212 }
3213
3214 void endTagOptgroup(EndTagToken token) {
3215 // </optgroup> implicitly closes <option>
3216 if (tree.openElements.last.localName == "option" &&
3217 tree.openElements[tree.openElements.length - 2].localName ==
3218 "optgroup") {
3219 tree.openElements.removeLast();
3220 }
3221 // It also closes </optgroup>
3222 if (tree.openElements.last.localName == "optgroup") {
3223 var node = tree.openElements.removeLast();
3224 node.endSourceSpan = token.span;
3225 // But nothing else
3226 } else {
3227 parser.parseError(
3228 token.span, "unexpected-end-tag-in-select", {"name": "optgroup"});
3229 }
3230 }
3231
3232 void endTagSelect(EndTagToken token) {
3233 if (tree.elementInScope("select", variant: "select")) {
3234 popOpenElementsUntil(token);
3235 parser.resetInsertionMode();
3236 } else {
3237 // innerHTML case
3238 assert(parser.innerHTMLMode);
3239 parser.parseError(token.span, "undefined-error");
3240 }
3241 }
3242
3243 void endTagOther(EndTagToken token) {
3244 parser.parseError(
3245 token.span, "unexpected-end-tag-in-select", {"name": token.name});
3246 }
3247 }
3248
3249 class InSelectInTablePhase extends Phase {
3250 InSelectInTablePhase(parser) : super(parser);
3251
3252 processStartTag(StartTagToken token) {
3253 switch (token.name) {
3254 case "caption":
3255 case "table":
3256 case "tbody":
3257 case "tfoot":
3258 case "thead":
3259 case "tr":
3260 case "td":
3261 case "th":
3262 return startTagTable(token);
3263 default:
3264 return startTagOther(token);
3265 }
3266 }
3267
3268 processEndTag(EndTagToken token) {
3269 switch (token.name) {
3270 case "caption":
3271 case "table":
3272 case "tbody":
3273 case "tfoot":
3274 case "thead":
3275 case "tr":
3276 case "td":
3277 case "th":
3278 return endTagTable(token);
3279 default:
3280 return endTagOther(token);
3281 }
3282 }
3283
3284 bool processEOF() {
3285 parser._inSelectPhase.processEOF();
3286 return false;
3287 }
3288
3289 Token processCharacters(CharactersToken token) {
3290 return parser._inSelectPhase.processCharacters(token);
3291 }
3292
3293 Token startTagTable(StartTagToken token) {
3294 parser.parseError(token.span,
3295 "unexpected-table-element-start-tag-in-select-in-table", {
3296 "name": token.name
3297 });
3298 endTagOther(new EndTagToken("select"));
3299 return token;
3300 }
3301
3302 Token startTagOther(StartTagToken token) {
3303 return parser._inSelectPhase.processStartTag(token);
3304 }
3305
3306 Token endTagTable(EndTagToken token) {
3307 parser.parseError(token.span,
3308 "unexpected-table-element-end-tag-in-select-in-table", {
3309 "name": token.name
3310 });
3311 if (tree.elementInScope(token.name, variant: "table")) {
3312 endTagOther(new EndTagToken("select"));
3313 return token;
3314 }
3315 return null;
3316 }
3317
3318 Token endTagOther(EndTagToken token) {
3319 return parser._inSelectPhase.processEndTag(token);
3320 }
3321 }
3322
3323 class InForeignContentPhase extends Phase {
3324 // TODO(jmesserly): this is sorted so we could binary search.
3325 static const breakoutElements = const [
3326 'b',
3327 'big',
3328 'blockquote',
3329 'body',
3330 'br',
3331 'center',
3332 'code',
3333 'dd',
3334 'div',
3335 'dl',
3336 'dt',
3337 'em',
3338 'embed',
3339 'h1',
3340 'h2',
3341 'h3',
3342 'h4',
3343 'h5',
3344 'h6',
3345 'head',
3346 'hr',
3347 'i',
3348 'img',
3349 'li',
3350 'listing',
3351 'menu',
3352 'meta',
3353 'nobr',
3354 'ol',
3355 'p',
3356 'pre',
3357 'ruby',
3358 's',
3359 'small',
3360 'span',
3361 'strike',
3362 'strong',
3363 'sub',
3364 'sup',
3365 'table',
3366 'tt',
3367 'u',
3368 'ul',
3369 'var'
3370 ];
3371
3372 InForeignContentPhase(parser) : super(parser);
3373
3374 void adjustSVGTagNames(token) {
3375 final replacements = const {
3376 "altglyph": "altGlyph",
3377 "altglyphdef": "altGlyphDef",
3378 "altglyphitem": "altGlyphItem",
3379 "animatecolor": "animateColor",
3380 "animatemotion": "animateMotion",
3381 "animatetransform": "animateTransform",
3382 "clippath": "clipPath",
3383 "feblend": "feBlend",
3384 "fecolormatrix": "feColorMatrix",
3385 "fecomponenttransfer": "feComponentTransfer",
3386 "fecomposite": "feComposite",
3387 "feconvolvematrix": "feConvolveMatrix",
3388 "fediffuselighting": "feDiffuseLighting",
3389 "fedisplacementmap": "feDisplacementMap",
3390 "fedistantlight": "feDistantLight",
3391 "feflood": "feFlood",
3392 "fefunca": "feFuncA",
3393 "fefuncb": "feFuncB",
3394 "fefuncg": "feFuncG",
3395 "fefuncr": "feFuncR",
3396 "fegaussianblur": "feGaussianBlur",
3397 "feimage": "feImage",
3398 "femerge": "feMerge",
3399 "femergenode": "feMergeNode",
3400 "femorphology": "feMorphology",
3401 "feoffset": "feOffset",
3402 "fepointlight": "fePointLight",
3403 "fespecularlighting": "feSpecularLighting",
3404 "fespotlight": "feSpotLight",
3405 "fetile": "feTile",
3406 "feturbulence": "feTurbulence",
3407 "foreignobject": "foreignObject",
3408 "glyphref": "glyphRef",
3409 "lineargradient": "linearGradient",
3410 "radialgradient": "radialGradient",
3411 "textpath": "textPath"
3412 };
3413
3414 var replace = replacements[token.name];
3415 if (replace != null) {
3416 token.name = replace;
3417 }
3418 }
3419
3420 Token processCharacters(CharactersToken token) {
3421 if (token.data == "\u0000") {
3422 token.replaceData("\uFFFD");
3423 } else if (parser.framesetOK && !allWhitespace(token.data)) {
3424 parser.framesetOK = false;
3425 }
3426 return super.processCharacters(token);
3427 }
3428
3429 Token processStartTag(StartTagToken token) {
3430 var currentNode = tree.openElements.last;
3431 if (breakoutElements.contains(token.name) ||
3432 (token.name == "font" &&
3433 (token.data.containsKey("color") ||
3434 token.data.containsKey("face") ||
3435 token.data.containsKey("size")))) {
3436 parser.parseError(token.span,
3437 "unexpected-html-element-in-foreign-content", {'name': token.name});
3438 while (tree.openElements.last.namespaceUri != tree.defaultNamespace &&
3439 !parser.isHTMLIntegrationPoint(tree.openElements.last) &&
3440 !parser.isMathMLTextIntegrationPoint(tree.openElements.last)) {
3441 tree.openElements.removeLast();
3442 }
3443 return token;
3444 } else {
3445 if (currentNode.namespaceUri == Namespaces.mathml) {
3446 parser.adjustMathMLAttributes(token);
3447 } else if (currentNode.namespaceUri == Namespaces.svg) {
3448 adjustSVGTagNames(token);
3449 parser.adjustSVGAttributes(token);
3450 }
3451 parser.adjustForeignAttributes(token);
3452 token.namespace = currentNode.namespaceUri;
3453 tree.insertElement(token);
3454 if (token.selfClosing) {
3455 tree.openElements.removeLast();
3456 token.selfClosingAcknowledged = true;
3457 }
3458 return null;
3459 }
3460 }
3461
3462 Token processEndTag(EndTagToken token) {
3463 var nodeIndex = tree.openElements.length - 1;
3464 var node = tree.openElements.last;
3465 if (asciiUpper2Lower(node.localName) != token.name) {
3466 parser.parseError(token.span, "unexpected-end-tag", {"name": token.name});
3467 }
3468
3469 var newToken = null;
3470 while (true) {
3471 if (asciiUpper2Lower(node.localName) == token.name) {
3472 //XXX this isn't in the spec but it seems necessary
3473 if (parser.phase == parser._inTableTextPhase) {
3474 InTableTextPhase inTableText = parser.phase;
3475 inTableText.flushCharacters();
3476 parser.phase = inTableText.originalPhase;
3477 }
3478 while (tree.openElements.removeLast() != node) {
3479 assert(tree.openElements.length > 0);
3480 }
3481 newToken = null;
3482 break;
3483 }
3484 nodeIndex -= 1;
3485
3486 node = tree.openElements[nodeIndex];
3487 if (node.namespaceUri != tree.defaultNamespace) {
3488 continue;
3489 } else {
3490 newToken = parser.phase.processEndTag(token);
3491 break;
3492 }
3493 }
3494 return newToken;
3495 }
3496 }
3497
3498 class AfterBodyPhase extends Phase {
3499 AfterBodyPhase(parser) : super(parser);
3500
3501 processStartTag(StartTagToken token) {
3502 if (token.name == "html") return startTagHtml(token);
3503 return startTagOther(token);
3504 }
3505
3506 processEndTag(EndTagToken token) {
3507 if (token.name == "html") return endTagHtml(token);
3508 return endTagOther(token);
3509 }
3510
3511 //Stop parsing
3512 bool processEOF() => false;
3513
3514 Token processComment(CommentToken token) {
3515 // This is needed because data is to be appended to the <html> element
3516 // here and not to whatever is currently open.
3517 tree.insertComment(token, tree.openElements[0]);
3518 return null;
3519 }
3520
3521 Token processCharacters(CharactersToken token) {
3522 parser.parseError(token.span, "unexpected-char-after-body");
3523 parser.phase = parser._inBodyPhase;
3524 return token;
3525 }
3526
3527 Token startTagHtml(StartTagToken token) {
3528 return parser._inBodyPhase.processStartTag(token);
3529 }
3530
3531 Token startTagOther(StartTagToken token) {
3532 parser.parseError(
3533 token.span, "unexpected-start-tag-after-body", {"name": token.name});
3534 parser.phase = parser._inBodyPhase;
3535 return token;
3536 }
3537
3538 void endTagHtml(Token token) {
3539 for (var node in tree.openElements.reversed) {
3540 if (node.localName == 'html') {
3541 node.endSourceSpan = token.span;
3542 break;
3543 }
3544 }
3545 if (parser.innerHTMLMode) {
3546 parser.parseError(token.span, "unexpected-end-tag-after-body-innerhtml");
3547 } else {
3548 parser.phase = parser._afterAfterBodyPhase;
3549 }
3550 }
3551
3552 Token endTagOther(EndTagToken token) {
3553 parser.parseError(
3554 token.span, "unexpected-end-tag-after-body", {"name": token.name});
3555 parser.phase = parser._inBodyPhase;
3556 return token;
3557 }
3558 }
3559
3560 class InFramesetPhase extends Phase {
3561 // http://www.whatwg.org/specs/web-apps/current-work///in-frameset
3562 InFramesetPhase(parser) : super(parser);
3563
3564 processStartTag(StartTagToken token) {
3565 switch (token.name) {
3566 case "html":
3567 return startTagHtml(token);
3568 case "frameset":
3569 return startTagFrameset(token);
3570 case "frame":
3571 return startTagFrame(token);
3572 case "noframes":
3573 return startTagNoframes(token);
3574 default:
3575 return startTagOther(token);
3576 }
3577 }
3578
3579 processEndTag(EndTagToken token) {
3580 switch (token.name) {
3581 case "frameset":
3582 return endTagFrameset(token);
3583 default:
3584 return endTagOther(token);
3585 }
3586 }
3587
3588 bool processEOF() {
3589 var last = tree.openElements.last;
3590 if (last.localName != "html") {
3591 parser.parseError(last.sourceSpan, "eof-in-frameset");
3592 } else {
3593 assert(parser.innerHTMLMode);
3594 }
3595 return false;
3596 }
3597
3598 Token processCharacters(CharactersToken token) {
3599 parser.parseError(token.span, "unexpected-char-in-frameset");
3600 return null;
3601 }
3602
3603 void startTagFrameset(StartTagToken token) {
3604 tree.insertElement(token);
3605 }
3606
3607 void startTagFrame(StartTagToken token) {
3608 tree.insertElement(token);
3609 tree.openElements.removeLast();
3610 }
3611
3612 Token startTagNoframes(StartTagToken token) {
3613 return parser._inBodyPhase.processStartTag(token);
3614 }
3615
3616 Token startTagOther(StartTagToken token) {
3617 parser.parseError(
3618 token.span, "unexpected-start-tag-in-frameset", {"name": token.name});
3619 return null;
3620 }
3621
3622 void endTagFrameset(EndTagToken token) {
3623 if (tree.openElements.last.localName == "html") {
3624 // innerHTML case
3625 parser.parseError(
3626 token.span, "unexpected-frameset-in-frameset-innerhtml");
3627 } else {
3628 var node = tree.openElements.removeLast();
3629 node.endSourceSpan = token.span;
3630 }
3631 if (!parser.innerHTMLMode &&
3632 tree.openElements.last.localName != "frameset") {
3633 // If we're not in innerHTML mode and the the current node is not a
3634 // "frameset" element (anymore) then switch.
3635 parser.phase = parser._afterFramesetPhase;
3636 }
3637 }
3638
3639 void endTagOther(EndTagToken token) {
3640 parser.parseError(
3641 token.span, "unexpected-end-tag-in-frameset", {"name": token.name});
3642 }
3643 }
3644
3645 class AfterFramesetPhase extends Phase {
3646 // http://www.whatwg.org/specs/web-apps/current-work///after3
3647 AfterFramesetPhase(parser) : super(parser);
3648
3649 processStartTag(StartTagToken token) {
3650 switch (token.name) {
3651 case "html":
3652 return startTagHtml(token);
3653 case "noframes":
3654 return startTagNoframes(token);
3655 default:
3656 return startTagOther(token);
3657 }
3658 }
3659
3660 processEndTag(EndTagToken token) {
3661 switch (token.name) {
3662 case "html":
3663 return endTagHtml(token);
3664 default:
3665 return endTagOther(token);
3666 }
3667 }
3668
3669 // Stop parsing
3670 bool processEOF() => false;
3671
3672 Token processCharacters(CharactersToken token) {
3673 parser.parseError(token.span, "unexpected-char-after-frameset");
3674 return null;
3675 }
3676
3677 Token startTagNoframes(StartTagToken token) {
3678 return parser._inHeadPhase.processStartTag(token);
3679 }
3680
3681 void startTagOther(StartTagToken token) {
3682 parser.parseError(token.span, "unexpected-start-tag-after-frameset", {
3683 "name": token.name
3684 });
3685 }
3686
3687 void endTagHtml(EndTagToken token) {
3688 parser.phase = parser._afterAfterFramesetPhase;
3689 }
3690
3691 void endTagOther(EndTagToken token) {
3692 parser.parseError(
3693 token.span, "unexpected-end-tag-after-frameset", {"name": token.name});
3694 }
3695 }
3696
3697 class AfterAfterBodyPhase extends Phase {
3698 AfterAfterBodyPhase(parser) : super(parser);
3699
3700 processStartTag(StartTagToken token) {
3701 if (token.name == 'html') return startTagHtml(token);
3702 return startTagOther(token);
3703 }
3704
3705 bool processEOF() => false;
3706
3707 Token processComment(CommentToken token) {
3708 tree.insertComment(token, tree.document);
3709 return null;
3710 }
3711
3712 Token processSpaceCharacters(SpaceCharactersToken token) {
3713 return parser._inBodyPhase.processSpaceCharacters(token);
3714 }
3715
3716 Token processCharacters(CharactersToken token) {
3717 parser.parseError(token.span, "expected-eof-but-got-char");
3718 parser.phase = parser._inBodyPhase;
3719 return token;
3720 }
3721
3722 Token startTagHtml(StartTagToken token) {
3723 return parser._inBodyPhase.processStartTag(token);
3724 }
3725
3726 Token startTagOther(StartTagToken token) {
3727 parser.parseError(
3728 token.span, "expected-eof-but-got-start-tag", {"name": token.name});
3729 parser.phase = parser._inBodyPhase;
3730 return token;
3731 }
3732
3733 Token processEndTag(EndTagToken token) {
3734 parser.parseError(
3735 token.span, "expected-eof-but-got-end-tag", {"name": token.name});
3736 parser.phase = parser._inBodyPhase;
3737 return token;
3738 }
3739 }
3740
3741 class AfterAfterFramesetPhase extends Phase {
3742 AfterAfterFramesetPhase(parser) : super(parser);
3743
3744 processStartTag(StartTagToken token) {
3745 switch (token.name) {
3746 case "html":
3747 return startTagHtml(token);
3748 case "noframes":
3749 return startTagNoFrames(token);
3750 default:
3751 return startTagOther(token);
3752 }
3753 }
3754
3755 bool processEOF() => false;
3756
3757 Token processComment(CommentToken token) {
3758 tree.insertComment(token, tree.document);
3759 return null;
3760 }
3761
3762 Token processSpaceCharacters(SpaceCharactersToken token) {
3763 return parser._inBodyPhase.processSpaceCharacters(token);
3764 }
3765
3766 Token processCharacters(CharactersToken token) {
3767 parser.parseError(token.span, "expected-eof-but-got-char");
3768 return null;
3769 }
3770
3771 Token startTagHtml(StartTagToken token) {
3772 return parser._inBodyPhase.processStartTag(token);
3773 }
3774
3775 Token startTagNoFrames(StartTagToken token) {
3776 return parser._inHeadPhase.processStartTag(token);
3777 }
3778
3779 void startTagOther(StartTagToken token) {
3780 parser.parseError(
3781 token.span, "expected-eof-but-got-start-tag", {"name": token.name});
3782 }
3783
3784 Token processEndTag(EndTagToken token) {
3785 parser.parseError(
3786 token.span, "expected-eof-but-got-end-tag", {"name": token.name});
3787 return null;
3788 }
3789 }
3790
3791 /// Error in parsed document.
3792 class ParseError implements SourceSpanException {
3793 final String errorCode;
3794 final SourceSpan span;
3795 final Map data;
3796
3797 ParseError(this.errorCode, this.span, this.data);
3798
3799 int get line => span.start.line;
3800
3801 int get column => span.start.column;
3802
3803 /// Gets the human readable error message for this error. Use
3804 /// [span.getLocationMessage] or [toString] to get a message including span
3805 /// information. If there is a file associated with the span, both
3806 /// [span.getLocationMessage] and [toString] are equivalent. Otherwise,
3807 /// [span.getLocationMessage] will not show any source url information, but
3808 /// [toString] will include 'ParserError:' as a prefix.
3809 String get message => formatStr(errorMessages[errorCode], data);
3810
3811 String toString({color}) {
3812 var res = span.message(message, color: color);
3813 return span.sourceUrl == null ? 'ParserError on $res' : 'On $res';
3814 }
3815 }
3816
3817 /// Convenience function to get the pair of namespace and localName.
3818 Pair<String, String> getElementNameTuple(Element e) {
3819 var ns = e.namespaceUri;
3820 if (ns == null) ns = Namespaces.html;
3821 return new Pair(ns, e.localName);
3822 }
OLDNEW
« no previous file with comments | « html/lib/dom_parsing.dart ('k') | html/lib/parser_console.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698