1 // Copyright 2010 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
12 // A parser implements the HTML5 parsing algorithm:
13 // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#tree-construction
15 // tokenizer provides the tokens for the parser.
17 // tok is the most recently read token.
19 // Self-closing tags like <hr/> are re-interpreted as a two-token sequence:
20 // <hr> followed by </hr>. hasSelfClosingToken is true if we have just read
21 // the synthetic start tag and the next one due is the matching end tag.
22 hasSelfClosingToken bool
23 // doc is the document root element.
25 // The stack of open elements (section 11.2.3.2) and active formatting
26 // elements (section 11.2.3.3).
28 // Element pointers (section 11.2.3.4).
30 // Other parsing state flags (section 11.2.3.5).
31 scripting, framesetOK bool
32 // im is the current insertion mode.
34 // originalIM is the insertion mode to go back to after completing a text
35 // or inTableText insertion mode.
36 originalIM insertionMode
37 // fosterParenting is whether new elements should be inserted according to
38 // the foster parenting rules (section 11.2.5.3).
42 func (p *parser) top() *Node {
43 if n := p.oe.top(); n != nil {
49 // stopTags for use in popUntil. These come from section 11.2.3.2.
51 defaultScopeStopTags = []string{"applet", "caption", "html", "table", "td", "th", "marquee", "object"}
52 listItemScopeStopTags = []string{"applet", "caption", "html", "table", "td", "th", "marquee", "object", "ol", "ul"}
53 buttonScopeStopTags = []string{"applet", "caption", "html", "table", "td", "th", "marquee", "object", "button"}
54 tableScopeStopTags = []string{"html", "table"}
57 // stopTags for use in clearStackToContext.
59 tableRowContextStopTags = []string{"tr", "html"}
62 // popUntil pops the stack of open elements at the highest element whose tag
63 // is in matchTags, provided there is no higher element in stopTags. It returns
64 // whether or not there was such an element. If there was not, popUntil leaves
65 // the stack unchanged.
67 // For example, if the stack was:
68 // ["html", "body", "font", "table", "b", "i", "u"]
69 // then popUntil([]string{"html, "table"}, "font") would return false, but
70 // popUntil([]string{"html, "table"}, "i") would return true and the resultant
72 // ["html", "body", "font", "table", "b"]
74 // If an element's tag is in both stopTags and matchTags, then the stack will
75 // be popped and the function returns true (provided, of course, there was no
76 // higher element in the stack that was also in stopTags). For example,
77 // popUntil([]string{"html, "table"}, "table") would return true and leave:
78 // ["html", "body", "font"]
79 func (p *parser) popUntil(stopTags []string, matchTags ...string) bool {
80 if i := p.indexOfElementInScope(stopTags, matchTags...); i != -1 {
87 // indexOfElementInScope returns the index in p.oe of the highest element
88 // whose tag is in matchTags that is in scope according to stopTags.
89 // If no matching element is in scope, it returns -1.
90 func (p *parser) indexOfElementInScope(stopTags []string, matchTags ...string) int {
91 for i := len(p.oe) - 1; i >= 0; i-- {
93 for _, t := range matchTags {
98 for _, t := range stopTags {
107 // elementInScope is like popUntil, except that it doesn't modify the stack of
109 func (p *parser) elementInScope(stopTags []string, matchTags ...string) bool {
110 return p.indexOfElementInScope(stopTags, matchTags...) != -1
113 // addChild adds a child node n to the top element, and pushes n onto the stack
114 // of open elements if it is an element node.
115 func (p *parser) addChild(n *Node) {
116 if p.fosterParenting {
122 if n.Type == ElementNode {
123 p.oe = append(p.oe, n)
127 // fosterParent adds a child node according to the foster parenting rules.
128 // Section 11.2.5.3, "foster parenting".
129 func (p *parser) fosterParent(n *Node) {
130 p.fosterParenting = false
131 var table, parent *Node
133 for i = len(p.oe) - 1; i >= 0; i-- {
134 if p.oe[i].Data == "table" {
141 // The foster parent is the html element.
144 parent = table.Parent
151 for i, child = range parent.Child {
157 if i > 0 && parent.Child[i-1].Type == TextNode && n.Type == TextNode {
158 parent.Child[i-1].Data += n.Data
162 if i == len(parent.Child) {
165 // Insert n into parent.Child at index i.
166 parent.Child = append(parent.Child[:i+1], parent.Child[i:]...)
172 // addText adds text to the preceding node if it is a text node, or else it
173 // calls addChild with a new text node.
174 func (p *parser) addText(text string) {
175 // TODO: distinguish whitespace text from others.
177 if i := len(t.Child); i > 0 && t.Child[i-1].Type == TextNode {
178 t.Child[i-1].Data += text
187 // addElement calls addChild with an element node.
188 func (p *parser) addElement(tag string, attr []Attribute) {
197 func (p *parser) addFormattingElement(tag string, attr []Attribute) {
198 p.addElement(tag, attr)
199 p.afe = append(p.afe, p.top())
204 func (p *parser) clearActiveFormattingElements() {
207 if len(p.afe) == 0 || n.Type == scopeMarkerNode {
214 func (p *parser) reconstructActiveFormattingElements() {
219 if n.Type == scopeMarkerNode || p.oe.index(n) != -1 {
223 for n.Type != scopeMarkerNode && p.oe.index(n) == -1 {
233 clone := p.afe[i].clone()
236 if i == len(p.afe)-1 {
242 // read reads the next token. This is usually from the tokenizer, but it may
243 // be the synthesized end tag implied by a self-closing tag.
244 func (p *parser) read() error {
245 if p.hasSelfClosingToken {
246 p.hasSelfClosingToken = false
247 p.tok.Type = EndTagToken
252 p.tok = p.tokenizer.Token()
255 return p.tokenizer.Err()
256 case SelfClosingTagToken:
257 p.hasSelfClosingToken = true
258 p.tok.Type = StartTagToken
264 func (p *parser) acknowledgeSelfClosingTag() {
265 p.hasSelfClosingToken = false
268 // An insertion mode (section 11.2.3.1) is the state transition function from
269 // a particular state in the HTML5 parser's state machine. It updates the
270 // parser's fields depending on parser.tok (where ErrorToken means EOF).
271 // It returns whether the token was consumed.
272 type insertionMode func(*parser) bool
274 // setOriginalIM sets the insertion mode to return to after completing a text or
275 // inTableText insertion mode.
276 // Section 11.2.3.1, "using the rules for".
277 func (p *parser) setOriginalIM() {
278 if p.originalIM != nil {
279 panic("html: bad parser state: originalIM was set twice")
284 // Section 11.2.3.1, "reset the insertion mode".
285 func (p *parser) resetInsertionMode() {
286 for i := len(p.oe) - 1; i >= 0; i-- {
289 // TODO: set n to the context element, for HTML fragment parsing.
298 case "tbody", "thead", "tfoot":
303 p.im = inColumnGroupIM
322 // Section 11.2.5.4.1.
323 func initialIM(p *parser) bool {
339 // TODO: set "quirks mode"? It's defined in the DOM spec instead of HTML5 proper,
340 // and so switching on "quirks mode" might belong in a different package.
345 // Section 11.2.5.4.2.
346 func beforeHTMLIM(p *parser) bool {
349 if p.tok.Data == "html" {
350 p.addElement(p.tok.Data, p.tok.Attr)
356 case "head", "body", "html", "br":
357 // Drop down to creating an implied <html> tag.
369 // Create an implied <html> tag.
370 p.addElement("html", nil)
375 // Section 11.2.5.4.3.
376 func beforeHeadIM(p *parser) bool {
386 // TODO: distinguish whitespace text from others.
400 case "head", "body", "html", "br":
413 p.addElement("head", attr)
420 const whitespace = " \t\r\n\f"
422 // Section 11.2.5.4.4.
423 func inHeadIM(p *parser) bool {
432 s := strings.TrimLeft(p.tok.Data, whitespace)
433 if len(s) < len(p.tok.Data) {
434 // Add the initial whitespace to the current node.
435 p.addText(p.tok.Data[:len(p.tok.Data)-len(s)])
444 case "base", "basefont", "bgsound", "command", "link", "meta":
445 p.addElement(p.tok.Data, p.tok.Attr)
447 p.acknowledgeSelfClosingTag()
448 case "script", "title", "noscript", "noframes", "style":
449 p.addElement(p.tok.Data, p.tok.Attr)
460 case "body", "html", "br":
475 if n.Data != "head" {
476 panic("html: bad parser state: <head> element not found, in the in-head insertion mode")
484 // Section 11.2.5.4.6.
485 func afterHeadIM(p *parser) bool {
493 case ErrorToken, TextToken:
505 p.addElement(p.tok.Data, p.tok.Attr)
508 case "base", "basefont", "bgsound", "link", "meta", "noframes", "script", "style", "title":
509 p.oe = append(p.oe, p.head)
520 case "body", "html", "br":
535 p.addElement("body", attr)
536 p.framesetOK = framesetOK
542 // copyAttributes copies attributes of src not found on dst to dst.
543 func copyAttributes(dst *Node, src Token) {
544 if len(src.Attr) == 0 {
547 attr := map[string]string{}
548 for _, a := range dst.Attr {
551 for _, a := range src.Attr {
552 if _, ok := attr[a.Key]; !ok {
553 dst.Attr = append(dst.Attr, a)
559 // Section 11.2.5.4.7.
560 func inBodyIM(p *parser) bool {
563 p.reconstructActiveFormattingElements()
564 p.addText(p.tok.Data)
568 case "address", "article", "aside", "blockquote", "center", "details", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "menu", "nav", "ol", "p", "section", "summary", "ul":
569 p.popUntil(buttonScopeStopTags, "p")
570 p.addElement(p.tok.Data, p.tok.Attr)
571 case "h1", "h2", "h3", "h4", "h5", "h6":
572 p.popUntil(buttonScopeStopTags, "p")
573 switch n := p.top(); n.Data {
574 case "h1", "h2", "h3", "h4", "h5", "h6":
577 p.addElement(p.tok.Data, p.tok.Attr)
579 for i := len(p.afe) - 1; i >= 0 && p.afe[i].Type != scopeMarkerNode; i-- {
580 if n := p.afe[i]; n.Type == ElementNode && n.Data == "a" {
581 p.inBodyEndTagFormatting("a")
587 p.reconstructActiveFormattingElements()
588 p.addFormattingElement(p.tok.Data, p.tok.Attr)
589 case "b", "big", "code", "em", "font", "i", "s", "small", "strike", "strong", "tt", "u":
590 p.reconstructActiveFormattingElements()
591 p.addFormattingElement(p.tok.Data, p.tok.Attr)
592 case "applet", "marquee", "object":
593 p.reconstructActiveFormattingElements()
594 p.addElement(p.tok.Data, p.tok.Attr)
595 p.afe = append(p.afe, &scopeMarker)
597 case "area", "br", "embed", "img", "input", "keygen", "wbr":
598 p.reconstructActiveFormattingElements()
599 p.addElement(p.tok.Data, p.tok.Attr)
601 p.acknowledgeSelfClosingTag()
604 p.popUntil(buttonScopeStopTags, "p") // TODO: skip this step in quirks mode.
605 p.addElement(p.tok.Data, p.tok.Attr)
610 p.popUntil(buttonScopeStopTags, "p")
611 p.addElement(p.tok.Data, p.tok.Attr)
613 p.acknowledgeSelfClosingTag()
616 p.reconstructActiveFormattingElements()
617 p.addElement(p.tok.Data, p.tok.Attr)
619 // TODO: detect <select> inside a table.
624 p.popUntil(buttonScopeStopTags, "p")
625 p.addElement(p.tok.Data, p.tok.Attr)
630 for i := len(p.oe) - 1; i >= 0; i-- {
634 p.popUntil(listItemScopeStopTags, "li")
635 case "address", "div", "p":
638 if !isSpecialElement[node.Data] {
644 p.popUntil(buttonScopeStopTags, "p")
645 p.addElement(p.tok.Data, p.tok.Attr)
648 for i := len(p.oe) - 1; i >= 0; i-- {
653 case "address", "div", "p":
656 if !isSpecialElement[node.Data] {
662 p.popUntil(buttonScopeStopTags, "p")
663 p.addElement(p.tok.Data, p.tok.Attr)
665 p.popUntil(buttonScopeStopTags, "p")
666 p.addElement(p.tok.Data, p.tok.Attr)
667 case "optgroup", "option":
668 if p.top().Data == "option" {
671 p.reconstructActiveFormattingElements()
672 p.addElement(p.tok.Data, p.tok.Attr)
676 if body.Type == ElementNode && body.Data == "body" {
678 copyAttributes(body, p.tok)
681 case "base", "basefont", "bgsound", "command", "link", "meta", "noframes", "script", "style", "title":
692 prompt := "This is a searchable index. Enter search keywords: "
693 attr := []Attribute{{Key: "name", Val: "isindex"}}
694 for _, a := range p.tok.Attr {
699 // Ignore the attribute.
703 attr = append(attr, a)
706 p.acknowledgeSelfClosingTag()
707 p.popUntil(buttonScopeStopTags, "p")
708 p.addElement("form", nil)
711 p.form.Attr = []Attribute{{Key: "action", Val: action}}
713 p.addElement("hr", nil)
715 p.addElement("label", nil)
717 p.addElement("input", attr)
720 p.addElement("hr", nil)
724 case "caption", "col", "colgroup", "frame", "head", "tbody", "td", "tfoot", "th", "thead", "tr":
728 p.addElement(p.tok.Data, p.tok.Attr)
733 // TODO: autoclose the stack of open elements.
737 if !p.elementInScope(buttonScopeStopTags, "p") {
738 p.addElement("p", nil)
740 p.popUntil(buttonScopeStopTags, "p")
741 case "a", "b", "big", "code", "em", "font", "i", "nobr", "s", "small", "strike", "strong", "tt", "u":
742 p.inBodyEndTagFormatting(p.tok.Data)
743 case "address", "article", "aside", "blockquote", "button", "center", "details", "dir", "div", "dl", "fieldset", "figcaption", "figure", "footer", "header", "hgroup", "listing", "menu", "nav", "ol", "pre", "section", "summary", "ul":
744 p.popUntil(defaultScopeStopTags, p.tok.Data)
745 case "applet", "marquee", "object":
746 if p.popUntil(defaultScopeStopTags, p.tok.Data) {
747 p.clearActiveFormattingElements()
750 p.tok.Type = StartTagToken
753 p.inBodyEndTagOther(p.tok.Data)
765 func (p *parser) inBodyEndTagFormatting(tag string) {
766 // This is the "adoption agency" algorithm, described at
767 // http://www.whatwg.org/specs/web-apps/current-work/multipage/tokenization.html#adoptionAgency
769 // TODO: this is a fairly literal line-by-line translation of that algorithm.
770 // Once the code successfully parses the comprehensive test suite, we should
771 // refactor this code to be more idiomatic.
773 // Steps 1-3. The outer loop.
774 for i := 0; i < 8; i++ {
775 // Step 4. Find the formatting element.
776 var formattingElement *Node
777 for j := len(p.afe) - 1; j >= 0; j-- {
778 if p.afe[j].Type == scopeMarkerNode {
781 if p.afe[j].Data == tag {
782 formattingElement = p.afe[j]
786 if formattingElement == nil {
787 p.inBodyEndTagOther(tag)
790 feIndex := p.oe.index(formattingElement)
792 p.afe.remove(formattingElement)
795 if !p.elementInScope(defaultScopeStopTags, tag) {
800 // Steps 5-6. Find the furthest block.
801 var furthestBlock *Node
802 for _, e := range p.oe[feIndex:] {
803 if isSpecialElement[e.Data] {
808 if furthestBlock == nil {
810 for e != formattingElement {
817 // Steps 7-8. Find the common ancestor and bookmark node.
818 commonAncestor := p.oe[feIndex-1]
819 bookmark := p.afe.index(formattingElement)
821 // Step 9. The inner loop. Find the lastNode to reparent.
822 lastNode := furthestBlock
823 node := furthestBlock
824 x := p.oe.index(node)
826 for j := 0; j < 3; j++ {
831 if p.afe.index(node) == -1 {
836 if node == formattingElement {
840 clone := node.clone()
841 p.afe[p.afe.index(node)] = clone
842 p.oe[p.oe.index(node)] = clone
845 if lastNode == furthestBlock {
846 bookmark = p.afe.index(node) + 1
849 if lastNode.Parent != nil {
850 lastNode.Parent.Remove(lastNode)
857 // Step 10. Reparent lastNode to the common ancestor,
858 // or for misnested table nodes, to the foster parent.
859 if lastNode.Parent != nil {
860 lastNode.Parent.Remove(lastNode)
862 switch commonAncestor.Data {
863 case "table", "tbody", "tfoot", "thead", "tr":
864 p.fosterParent(lastNode)
866 commonAncestor.Add(lastNode)
869 // Steps 11-13. Reparent nodes from the furthest block's children
870 // to a clone of the formatting element.
871 clone := formattingElement.clone()
872 reparentChildren(clone, furthestBlock)
873 furthestBlock.Add(clone)
875 // Step 14. Fix up the list of active formatting elements.
876 if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
877 // Move the bookmark with the rest of the list.
880 p.afe.remove(formattingElement)
881 p.afe.insert(bookmark, clone)
883 // Step 15. Fix up the stack of open elements.
884 p.oe.remove(formattingElement)
885 p.oe.insert(p.oe.index(furthestBlock)+1, clone)
889 // inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM.
890 func (p *parser) inBodyEndTagOther(tag string) {
891 for i := len(p.oe) - 1; i >= 0; i-- {
892 if p.oe[i].Data == tag {
896 if isSpecialElement[p.oe[i].Data] {
902 // Section 11.2.5.4.8.
903 func textIM(p *parser) bool {
908 p.addText(p.tok.Data)
915 return p.tok.Type == EndTagToken
918 // Section 11.2.5.4.9.
919 func inTableIM(p *parser) bool {
929 p.clearStackToContext(tableScopeStopTags)
930 p.afe = append(p.afe, &scopeMarker)
931 p.addElement(p.tok.Data, p.tok.Attr)
934 case "tbody", "tfoot", "thead":
935 p.clearStackToContext(tableScopeStopTags)
936 p.addElement(p.tok.Data, p.tok.Attr)
939 case "td", "th", "tr":
940 p.clearStackToContext(tableScopeStopTags)
941 p.addElement("tbody", nil)
945 if p.popUntil(tableScopeStopTags, "table") {
946 p.resetInsertionMode()
952 p.clearStackToContext(tableScopeStopTags)
953 p.addElement(p.tok.Data, p.tok.Attr)
954 p.im = inColumnGroupIM
957 p.clearStackToContext(tableScopeStopTags)
958 p.addElement("colgroup", p.tok.Attr)
959 p.im = inColumnGroupIM
967 if p.popUntil(tableScopeStopTags, "table") {
968 p.resetInsertionMode()
973 case "body", "caption", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr":
985 switch p.top().Data {
986 case "table", "tbody", "tfoot", "thead", "tr":
987 p.fosterParenting = true
988 defer func() { p.fosterParenting = false }()
994 // clearStackToContext pops elements off the stack of open elements
995 // until an element listed in stopTags is found.
996 func (p *parser) clearStackToContext(stopTags []string) {
997 for i := len(p.oe) - 1; i >= 0; i-- {
998 for _, tag := range stopTags {
999 if p.oe[i].Data == tag {
1007 // Section 11.2.5.4.11.
1008 func inCaptionIM(p *parser) bool {
1012 case "caption", "col", "colgroup", "tbody", "td", "tfoot", "thead", "tr":
1013 if p.popUntil(tableScopeStopTags, "caption") {
1014 p.clearActiveFormattingElements()
1018 // Ignore the token.
1025 if p.popUntil(tableScopeStopTags, "caption") {
1026 p.clearActiveFormattingElements()
1031 if p.popUntil(tableScopeStopTags, "caption") {
1032 p.clearActiveFormattingElements()
1036 // Ignore the token.
1039 case "body", "col", "colgroup", "html", "tbody", "td", "tfoot", "th", "thead", "tr":
1040 // Ignore the token.
1047 // Section 11.2.5.4.12.
1048 func inColumnGroupIM(p *parser) bool {
1057 // Ignore the token.
1064 p.addElement(p.tok.Data, p.tok.Attr)
1066 p.acknowledgeSelfClosingTag()
1072 if p.oe.top().Data != "html" {
1078 // Ignore the token.
1082 if p.oe.top().Data != "html" {
1089 // Section 11.2.5.4.13.
1090 func inTableBodyIM(p *parser) bool {
1119 if p.popUntil(tableScopeStopTags, "tbody", "thead", "tfoot") {
1123 // Ignore the token.
1125 case "body", "caption", "col", "colgroup", "html", "td", "th", "tr":
1126 // Ignore the token.
1137 // TODO: clear the stack back to a table body context.
1138 p.addElement(data, attr)
1145 // Section 11.2.5.4.14.
1146 func inRowIM(p *parser) bool {
1155 p.clearStackToContext(tableRowContextStopTags)
1156 p.addElement(p.tok.Data, p.tok.Attr)
1157 p.afe = append(p.afe, &scopeMarker)
1160 case "caption", "col", "colgroup", "tbody", "tfoot", "thead", "tr":
1161 if p.popUntil(tableScopeStopTags, "tr") {
1162 p.im = inTableBodyIM
1165 // Ignore the token.
1173 if p.popUntil(tableScopeStopTags, "tr") {
1174 p.im = inTableBodyIM
1177 // Ignore the token.
1180 if p.popUntil(tableScopeStopTags, "tr") {
1181 p.im = inTableBodyIM
1184 // Ignore the token.
1186 case "tbody", "tfoot", "thead":
1188 case "body", "caption", "col", "colgroup", "html", "td", "th":
1189 // Ignore the token.
1204 // Section 11.2.5.4.15.
1205 func inCellIM(p *parser) bool {
1207 closeTheCellAndReprocess bool
1212 case "caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr":
1213 // TODO: check for "td" or "th" in table scope.
1214 closeTheCellAndReprocess = true
1219 if !p.popUntil(tableScopeStopTags, p.tok.Data) {
1220 // Ignore the token.
1223 p.clearActiveFormattingElements()
1226 case "body", "caption", "col", "colgroup", "html":
1228 case "table", "tbody", "tfoot", "thead", "tr":
1229 // TODO: check for matching element in table scope.
1230 closeTheCellAndReprocess = true
1239 if closeTheCellAndReprocess {
1240 if p.popUntil(tableScopeStopTags, "td") || p.popUntil(tableScopeStopTags, "th") {
1241 p.clearActiveFormattingElements()
1249 // Section 11.2.5.4.16.
1250 func inSelectIM(p *parser) bool {
1256 p.addText(p.tok.Data)
1262 if p.top().Data == "option" {
1265 p.addElement(p.tok.Data, p.tok.Attr)
1267 if p.top().Data == "option" {
1270 if p.top().Data == "optgroup" {
1273 p.addElement(p.tok.Data, p.tok.Attr)
1276 case "input", "keygen", "textarea":
1281 // Ignore the token.
1286 if p.top().Data == "option" {
1291 if p.oe[i].Data == "option" {
1294 if p.oe[i].Data == "optgroup" {
1300 // Ignore the token.
1309 for i := len(p.oe) - 1; i >= 0; i-- {
1310 switch p.oe[i].Data {
1313 p.resetInsertionMode()
1315 case "option", "optgroup":
1318 // Ignore the token.
1326 // Section 11.2.5.4.18.
1327 func afterBodyIM(p *parser) bool {
1333 if p.tok.Data == "html" {
1337 if p.tok.Data == "html" {
1338 p.im = afterAfterBodyIM
1342 // The comment is attached to the <html> element.
1343 if len(p.oe) < 1 || p.oe[0].Data != "html" {
1344 panic("html: bad parser state: <html> element not found, in the after-body insertion mode")
1356 // Section 11.2.5.4.19.
1357 func inFramesetIM(p *parser) bool {
1369 p.addElement(p.tok.Data, p.tok.Attr)
1371 p.addElement(p.tok.Data, p.tok.Attr)
1373 p.acknowledgeSelfClosingTag()
1380 if p.oe.top().Data != "html" {
1382 if p.oe.top().Data != "frameset" {
1383 p.im = afterFramesetIM
1389 // Ignore the token.
1394 // Section 11.2.5.4.20.
1395 func afterFramesetIM(p *parser) bool {
1412 p.im = afterAfterFramesetIM
1416 // Ignore the token.
1421 // Section 11.2.5.4.21.
1422 func afterAfterBodyIM(p *parser) bool {
1430 if p.tok.Data == "html" {
1444 // Section 11.2.5.4.22.
1445 func afterAfterFramesetIM(p *parser) bool {
1460 // Ignore the token.
1465 // Parse returns the parse tree for the HTML from the given Reader.
1466 // The input is assumed to be UTF-8 encoded.
1467 func Parse(r io.Reader) (*Node, error) {
1469 tokenizer: NewTokenizer(r),
1477 // Iterate until EOF. Any other error will cause an early return.
1481 if err := p.read(); err != nil {
1490 // Loop until the final token (the ErrorToken signifying EOF) is consumed.
1492 if consumed = p.im(p); consumed {