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.
13 a "golang.org/x/net/html/atom"
16 // A parser implements the HTML5 parsing algorithm:
17 // https://html.spec.whatwg.org/multipage/syntax.html#tree-construction
19 // tokenizer provides the tokens for the parser.
21 // tok is the most recently read token.
23 // Self-closing tags like <hr/> are treated as start tags, except that
24 // hasSelfClosingToken is set while they are being processed.
25 hasSelfClosingToken bool
26 // doc is the document root element.
28 // The stack of open elements (section 12.2.3.2) and active formatting
29 // elements (section 12.2.3.3).
31 // Element pointers (section 12.2.3.4).
33 // Other parsing state flags (section 12.2.3.5).
34 scripting, framesetOK bool
35 // im is the current insertion mode.
37 // originalIM is the insertion mode to go back to after completing a text
38 // or inTableText insertion mode.
39 originalIM insertionMode
40 // fosterParenting is whether new elements should be inserted according to
41 // the foster parenting rules (section 12.2.5.3).
43 // quirks is whether the parser is operating in "quirks mode."
45 // fragment is whether the parser is parsing an HTML fragment.
47 // context is the context element when parsing an HTML fragment
52 func (p *parser) top() *Node {
53 if n := p.oe.top(); n != nil {
59 // Stop tags for use in popUntil. These come from section 12.2.3.2.
61 defaultScopeStopTags = map[string][]a.Atom{
62 "": {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template},
63 "math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext},
64 "svg": {a.Desc, a.ForeignObject, a.Title},
71 defaultScope scope = iota
80 // popUntil pops the stack of open elements at the highest element whose tag
81 // is in matchTags, provided there is no higher element in the scope's stop
82 // tags (as defined in section 12.2.3.2). It returns whether or not there was
83 // such an element. If there was not, popUntil leaves the stack unchanged.
85 // For example, the set of stop tags for table scope is: "html", "table". If
87 // ["html", "body", "font", "table", "b", "i", "u"]
88 // then popUntil(tableScope, "font") would return false, but
89 // popUntil(tableScope, "i") would return true and the stack would become:
90 // ["html", "body", "font", "table", "b"]
92 // If an element's tag is in both the stop tags and matchTags, then the stack
93 // will be popped and the function returns true (provided, of course, there was
94 // no higher element in the stack that was also in the stop tags). For example,
95 // popUntil(tableScope, "table") returns true and leaves:
96 // ["html", "body", "font"]
97 func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool {
98 if i := p.indexOfElementInScope(s, matchTags...); i != -1 {
105 // indexOfElementInScope returns the index in p.oe of the highest element whose
106 // tag is in matchTags that is in scope. If no matching element is in scope, it
108 func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int {
109 for i := len(p.oe) - 1; i >= 0; i-- {
110 tagAtom := p.oe[i].DataAtom
111 if p.oe[i].Namespace == "" {
112 for _, t := range matchTags {
121 if tagAtom == a.Ol || tagAtom == a.Ul {
125 if tagAtom == a.Button {
129 if tagAtom == a.Html || tagAtom == a.Table {
133 if tagAtom != a.Optgroup && tagAtom != a.Option {
141 case defaultScope, listItemScope, buttonScope:
142 for _, t := range defaultScopeStopTags[p.oe[i].Namespace] {
152 // elementInScope is like popUntil, except that it doesn't modify the stack of
154 func (p *parser) elementInScope(s scope, matchTags ...a.Atom) bool {
155 return p.indexOfElementInScope(s, matchTags...) != -1
158 // clearStackToContext pops elements off the stack of open elements until a
159 // scope-defined element is found.
160 func (p *parser) clearStackToContext(s scope) {
161 for i := len(p.oe) - 1; i >= 0; i-- {
162 tagAtom := p.oe[i].DataAtom
165 if tagAtom == a.Html || tagAtom == a.Table {
170 if tagAtom == a.Html || tagAtom == a.Tr {
175 if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead {
185 // generateImpliedEndTags pops nodes off the stack of open elements as long as
186 // the top node has a tag name of dd, dt, li, option, optgroup, p, rp, or rt.
187 // If exceptions are specified, nodes with that name will not be popped off.
188 func (p *parser) generateImpliedEndTags(exceptions ...string) {
191 for i = len(p.oe) - 1; i >= 0; i-- {
193 if n.Type == ElementNode {
195 case a.Dd, a.Dt, a.Li, a.Option, a.Optgroup, a.P, a.Rp, a.Rt:
196 for _, except := range exceptions {
197 if n.Data == except {
210 // addChild adds a child node n to the top element, and pushes n onto the stack
211 // of open elements if it is an element node.
212 func (p *parser) addChild(n *Node) {
213 if p.shouldFosterParent() {
216 p.top().AppendChild(n)
219 if n.Type == ElementNode {
220 p.oe = append(p.oe, n)
224 // shouldFosterParent returns whether the next node to be added should be
226 func (p *parser) shouldFosterParent() bool {
227 if p.fosterParenting {
228 switch p.top().DataAtom {
229 case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
236 // fosterParent adds a child node according to the foster parenting rules.
237 // Section 12.2.5.3, "foster parenting".
238 func (p *parser) fosterParent(n *Node) {
239 var table, parent, prev *Node
241 for i = len(p.oe) - 1; i >= 0; i-- {
242 if p.oe[i].DataAtom == a.Table {
249 // The foster parent is the html element.
252 parent = table.Parent
259 prev = table.PrevSibling
261 prev = parent.LastChild
263 if prev != nil && prev.Type == TextNode && n.Type == TextNode {
268 parent.InsertBefore(n, table)
271 // addText adds text to the preceding node if it is a text node, or else it
272 // calls addChild with a new text node.
273 func (p *parser) addText(text string) {
278 if p.shouldFosterParent() {
279 p.fosterParent(&Node{
287 if n := t.LastChild; n != nil && n.Type == TextNode {
297 // addElement adds a child element based on the current token.
298 func (p *parser) addElement() {
301 DataAtom: p.tok.DataAtom,
308 func (p *parser) addFormattingElement() {
309 tagAtom, attr := p.tok.DataAtom, p.tok.Attr
312 // Implement the Noah's Ark clause, but with three per family instead of two.
313 identicalElements := 0
314 findIdenticalElements:
315 for i := len(p.afe) - 1; i >= 0; i-- {
317 if n.Type == scopeMarkerNode {
320 if n.Type != ElementNode {
323 if n.Namespace != "" {
326 if n.DataAtom != tagAtom {
329 if len(n.Attr) != len(attr) {
333 for _, t0 := range n.Attr {
334 for _, t1 := range attr {
335 if t0.Key == t1.Key && t0.Namespace == t1.Namespace && t0.Val == t1.Val {
336 // Found a match for this attribute, continue with the next attribute.
337 continue compareAttributes
340 // If we get here, there is no attribute that matches a.
341 // Therefore the element is not identical to the new one.
342 continue findIdenticalElements
346 if identicalElements >= 3 {
351 p.afe = append(p.afe, p.top())
355 func (p *parser) clearActiveFormattingElements() {
358 if len(p.afe) == 0 || n.Type == scopeMarkerNode {
365 func (p *parser) reconstructActiveFormattingElements() {
370 if n.Type == scopeMarkerNode || p.oe.index(n) != -1 {
374 for n.Type != scopeMarkerNode && p.oe.index(n) == -1 {
384 clone := p.afe[i].clone()
387 if i == len(p.afe)-1 {
394 func (p *parser) acknowledgeSelfClosingTag() {
395 p.hasSelfClosingToken = false
398 // An insertion mode (section 12.2.3.1) is the state transition function from
399 // a particular state in the HTML5 parser's state machine. It updates the
400 // parser's fields depending on parser.tok (where ErrorToken means EOF).
401 // It returns whether the token was consumed.
402 type insertionMode func(*parser) bool
404 // setOriginalIM sets the insertion mode to return to after completing a text or
405 // inTableText insertion mode.
406 // Section 12.2.3.1, "using the rules for".
407 func (p *parser) setOriginalIM() {
408 if p.originalIM != nil {
409 panic("html: bad parser state: originalIM was set twice")
414 // Section 12.2.3.1, "reset the insertion mode".
415 func (p *parser) resetInsertionMode() {
416 for i := len(p.oe) - 1; i >= 0; i-- {
418 if i == 0 && p.context != nil {
429 case a.Tbody, a.Thead, a.Tfoot:
434 p.im = inColumnGroupIM
453 const whitespace = " \t\r\n\f"
455 // Section 12.2.5.4.1.
456 func initialIM(p *parser) bool {
459 p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace)
460 if len(p.tok.Data) == 0 {
461 // It was all whitespace, so ignore it.
465 p.doc.AppendChild(&Node{
471 n, quirks := parseDoctype(p.tok.Data)
482 // Section 12.2.5.4.2.
483 func beforeHTMLIM(p *parser) bool {
489 p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace)
490 if len(p.tok.Data) == 0 {
491 // It was all whitespace, so ignore it.
495 if p.tok.DataAtom == a.Html {
501 switch p.tok.DataAtom {
502 case a.Head, a.Body, a.Html, a.Br:
503 p.parseImpliedToken(StartTagToken, a.Html, a.Html.String())
510 p.doc.AppendChild(&Node{
516 p.parseImpliedToken(StartTagToken, a.Html, a.Html.String())
520 // Section 12.2.5.4.3.
521 func beforeHeadIM(p *parser) bool {
524 p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace)
525 if len(p.tok.Data) == 0 {
526 // It was all whitespace, so ignore it.
530 switch p.tok.DataAtom {
540 switch p.tok.DataAtom {
541 case a.Head, a.Body, a.Html, a.Br:
542 p.parseImpliedToken(StartTagToken, a.Head, a.Head.String())
559 p.parseImpliedToken(StartTagToken, a.Head, a.Head.String())
563 // Section 12.2.5.4.4.
564 func inHeadIM(p *parser) bool {
567 s := strings.TrimLeft(p.tok.Data, whitespace)
568 if len(s) < len(p.tok.Data) {
569 // Add the initial whitespace to the current node.
570 p.addText(p.tok.Data[:len(p.tok.Data)-len(s)])
577 switch p.tok.DataAtom {
580 case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta:
583 p.acknowledgeSelfClosingTag()
585 case a.Script, a.Title, a.Noscript, a.Noframes, a.Style:
595 switch p.tok.DataAtom {
598 if n.DataAtom != a.Head {
599 panic("html: bad parser state: <head> element not found, in the in-head insertion mode")
603 case a.Body, a.Html, a.Br:
604 p.parseImpliedToken(EndTagToken, a.Head, a.Head.String())
621 p.parseImpliedToken(EndTagToken, a.Head, a.Head.String())
625 // Section 12.2.5.4.6.
626 func afterHeadIM(p *parser) bool {
629 s := strings.TrimLeft(p.tok.Data, whitespace)
630 if len(s) < len(p.tok.Data) {
631 // Add the initial whitespace to the current node.
632 p.addText(p.tok.Data[:len(p.tok.Data)-len(s)])
639 switch p.tok.DataAtom {
651 case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
652 p.oe = append(p.oe, p.head)
653 defer p.oe.remove(p.head)
660 switch p.tok.DataAtom {
661 case a.Body, a.Html, a.Br:
662 // Drop down to creating an implied <body> tag.
678 p.parseImpliedToken(StartTagToken, a.Body, a.Body.String())
683 // copyAttributes copies attributes of src not found on dst to dst.
684 func copyAttributes(dst *Node, src Token) {
685 if len(src.Attr) == 0 {
688 attr := map[string]string{}
689 for _, t := range dst.Attr {
692 for _, t := range src.Attr {
693 if _, ok := attr[t.Key]; !ok {
694 dst.Attr = append(dst.Attr, t)
700 // Section 12.2.5.4.7.
701 func inBodyIM(p *parser) bool {
705 switch n := p.oe.top(); n.DataAtom {
706 case a.Pre, a.Listing:
707 if n.FirstChild == nil {
708 // Ignore a newline at the start of a <pre> block.
709 if d != "" && d[0] == '\r' {
712 if d != "" && d[0] == '\n' {
717 d = strings.Replace(d, "\x00", "", -1)
721 p.reconstructActiveFormattingElements()
723 if p.framesetOK && strings.TrimLeft(d, whitespace) != "" {
724 // There were non-whitespace characters inserted.
728 switch p.tok.DataAtom {
730 copyAttributes(p.oe[0], p.tok)
731 case a.Base, a.Basefont, a.Bgsound, a.Command, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Title:
736 if body.Type == ElementNode && body.DataAtom == a.Body {
738 copyAttributes(body, p.tok)
742 if !p.framesetOK || len(p.oe) < 2 || p.oe[1].DataAtom != a.Body {
747 if body.Parent != nil {
748 body.Parent.RemoveChild(body)
754 case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Menu, a.Nav, a.Ol, a.P, a.Section, a.Summary, a.Ul:
755 p.popUntil(buttonScope, a.P)
757 case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
758 p.popUntil(buttonScope, a.P)
759 switch n := p.top(); n.DataAtom {
760 case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
764 case a.Pre, a.Listing:
765 p.popUntil(buttonScope, a.P)
767 // The newline, if any, will be dealt with by the TextToken case.
771 p.popUntil(buttonScope, a.P)
777 for i := len(p.oe) - 1; i >= 0; i-- {
779 switch node.DataAtom {
782 case a.Address, a.Div, a.P:
785 if !isSpecialElement(node) {
791 p.popUntil(buttonScope, a.P)
795 for i := len(p.oe) - 1; i >= 0; i-- {
797 switch node.DataAtom {
800 case a.Address, a.Div, a.P:
803 if !isSpecialElement(node) {
809 p.popUntil(buttonScope, a.P)
812 p.popUntil(buttonScope, a.P)
815 p.popUntil(defaultScope, a.Button)
816 p.reconstructActiveFormattingElements()
820 for i := len(p.afe) - 1; i >= 0 && p.afe[i].Type != scopeMarkerNode; i-- {
821 if n := p.afe[i]; n.Type == ElementNode && n.DataAtom == a.A {
822 p.inBodyEndTagFormatting(a.A)
828 p.reconstructActiveFormattingElements()
829 p.addFormattingElement()
830 case a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
831 p.reconstructActiveFormattingElements()
832 p.addFormattingElement()
834 p.reconstructActiveFormattingElements()
835 if p.elementInScope(defaultScope, a.Nobr) {
836 p.inBodyEndTagFormatting(a.Nobr)
837 p.reconstructActiveFormattingElements()
839 p.addFormattingElement()
840 case a.Applet, a.Marquee, a.Object:
841 p.reconstructActiveFormattingElements()
843 p.afe = append(p.afe, &scopeMarker)
847 p.popUntil(buttonScope, a.P)
853 case a.Area, a.Br, a.Embed, a.Img, a.Input, a.Keygen, a.Wbr:
854 p.reconstructActiveFormattingElements()
857 p.acknowledgeSelfClosingTag()
858 if p.tok.DataAtom == a.Input {
859 for _, t := range p.tok.Attr {
861 if strings.ToLower(t.Val) == "hidden" {
862 // Skip setting framesetOK = false
869 case a.Param, a.Source, a.Track:
872 p.acknowledgeSelfClosingTag()
874 p.popUntil(buttonScope, a.P)
877 p.acknowledgeSelfClosingTag()
880 p.tok.DataAtom = a.Img
881 p.tok.Data = a.Img.String()
889 prompt := "This is a searchable index. Enter search keywords: "
890 attr := []Attribute{{Key: "name", Val: "isindex"}}
891 for _, t := range p.tok.Attr {
896 // Ignore the attribute.
900 attr = append(attr, t)
903 p.acknowledgeSelfClosingTag()
904 p.popUntil(buttonScope, a.P)
905 p.parseImpliedToken(StartTagToken, a.Form, a.Form.String())
907 p.form.Attr = []Attribute{{Key: "action", Val: action}}
909 p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
910 p.parseImpliedToken(StartTagToken, a.Label, a.Label.String())
915 Data: a.Input.String(),
919 p.parseImpliedToken(EndTagToken, a.Label, a.Label.String())
920 p.parseImpliedToken(StartTagToken, a.Hr, a.Hr.String())
921 p.parseImpliedToken(EndTagToken, a.Form, a.Form.String())
928 p.popUntil(buttonScope, a.P)
929 p.reconstructActiveFormattingElements()
939 case a.Noembed, a.Noscript:
944 p.reconstructActiveFormattingElements()
949 case a.Optgroup, a.Option:
950 if p.top().DataAtom == a.Option {
953 p.reconstructActiveFormattingElements()
956 if p.elementInScope(defaultScope, a.Ruby) {
957 p.generateImpliedEndTags()
961 p.reconstructActiveFormattingElements()
962 if p.tok.DataAtom == a.Math {
963 adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments)
965 adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments)
967 adjustForeignAttributes(p.tok.Attr)
969 p.top().Namespace = p.tok.Data
970 if p.hasSelfClosingToken {
972 p.acknowledgeSelfClosingTag()
975 case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
978 p.reconstructActiveFormattingElements()
982 switch p.tok.DataAtom {
984 if p.elementInScope(defaultScope, a.Body) {
988 if p.elementInScope(defaultScope, a.Body) {
989 p.parseImpliedToken(EndTagToken, a.Body, a.Body.String())
993 case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul:
994 p.popUntil(defaultScope, p.tok.DataAtom)
998 i := p.indexOfElementInScope(defaultScope, a.Form)
999 if node == nil || i == -1 || p.oe[i] != node {
1000 // Ignore the token.
1003 p.generateImpliedEndTags()
1006 if !p.elementInScope(buttonScope, a.P) {
1007 p.parseImpliedToken(StartTagToken, a.P, a.P.String())
1009 p.popUntil(buttonScope, a.P)
1011 p.popUntil(listItemScope, a.Li)
1013 p.popUntil(defaultScope, p.tok.DataAtom)
1014 case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6:
1015 p.popUntil(defaultScope, a.H1, a.H2, a.H3, a.H4, a.H5, a.H6)
1016 case a.A, a.B, a.Big, a.Code, a.Em, a.Font, a.I, a.Nobr, a.S, a.Small, a.Strike, a.Strong, a.Tt, a.U:
1017 p.inBodyEndTagFormatting(p.tok.DataAtom)
1018 case a.Applet, a.Marquee, a.Object:
1019 if p.popUntil(defaultScope, p.tok.DataAtom) {
1020 p.clearActiveFormattingElements()
1023 p.tok.Type = StartTagToken
1026 p.inBodyEndTagOther(p.tok.DataAtom)
1038 func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom) {
1039 // This is the "adoption agency" algorithm, described at
1040 // https://html.spec.whatwg.org/multipage/syntax.html#adoptionAgency
1042 // TODO: this is a fairly literal line-by-line translation of that algorithm.
1043 // Once the code successfully parses the comprehensive test suite, we should
1044 // refactor this code to be more idiomatic.
1046 // Steps 1-4. The outer loop.
1047 for i := 0; i < 8; i++ {
1048 // Step 5. Find the formatting element.
1049 var formattingElement *Node
1050 for j := len(p.afe) - 1; j >= 0; j-- {
1051 if p.afe[j].Type == scopeMarkerNode {
1054 if p.afe[j].DataAtom == tagAtom {
1055 formattingElement = p.afe[j]
1059 if formattingElement == nil {
1060 p.inBodyEndTagOther(tagAtom)
1063 feIndex := p.oe.index(formattingElement)
1065 p.afe.remove(formattingElement)
1068 if !p.elementInScope(defaultScope, tagAtom) {
1073 // Steps 9-10. Find the furthest block.
1074 var furthestBlock *Node
1075 for _, e := range p.oe[feIndex:] {
1076 if isSpecialElement(e) {
1081 if furthestBlock == nil {
1083 for e != formattingElement {
1090 // Steps 11-12. Find the common ancestor and bookmark node.
1091 commonAncestor := p.oe[feIndex-1]
1092 bookmark := p.afe.index(formattingElement)
1094 // Step 13. The inner loop. Find the lastNode to reparent.
1095 lastNode := furthestBlock
1096 node := furthestBlock
1097 x := p.oe.index(node)
1099 for j := 0; j < 3; j++ {
1103 // Step 13.4 - 13.5.
1104 if p.afe.index(node) == -1 {
1109 if node == formattingElement {
1113 clone := node.clone()
1114 p.afe[p.afe.index(node)] = clone
1115 p.oe[p.oe.index(node)] = clone
1118 if lastNode == furthestBlock {
1119 bookmark = p.afe.index(node) + 1
1122 if lastNode.Parent != nil {
1123 lastNode.Parent.RemoveChild(lastNode)
1125 node.AppendChild(lastNode)
1130 // Step 14. Reparent lastNode to the common ancestor,
1131 // or for misnested table nodes, to the foster parent.
1132 if lastNode.Parent != nil {
1133 lastNode.Parent.RemoveChild(lastNode)
1135 switch commonAncestor.DataAtom {
1136 case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
1137 p.fosterParent(lastNode)
1139 commonAncestor.AppendChild(lastNode)
1142 // Steps 15-17. Reparent nodes from the furthest block's children
1143 // to a clone of the formatting element.
1144 clone := formattingElement.clone()
1145 reparentChildren(clone, furthestBlock)
1146 furthestBlock.AppendChild(clone)
1148 // Step 18. Fix up the list of active formatting elements.
1149 if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark {
1150 // Move the bookmark with the rest of the list.
1153 p.afe.remove(formattingElement)
1154 p.afe.insert(bookmark, clone)
1156 // Step 19. Fix up the stack of open elements.
1157 p.oe.remove(formattingElement)
1158 p.oe.insert(p.oe.index(furthestBlock)+1, clone)
1162 // inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM.
1163 // "Any other end tag" handling from 12.2.5.5 The rules for parsing tokens in foreign content
1164 // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign
1165 func (p *parser) inBodyEndTagOther(tagAtom a.Atom) {
1166 for i := len(p.oe) - 1; i >= 0; i-- {
1167 if p.oe[i].DataAtom == tagAtom {
1171 if isSpecialElement(p.oe[i]) {
1177 // Section 12.2.5.4.8.
1178 func textIM(p *parser) bool {
1184 if n := p.oe.top(); n.DataAtom == a.Textarea && n.FirstChild == nil {
1185 // Ignore a newline at the start of a <textarea> block.
1186 if d != "" && d[0] == '\r' {
1189 if d != "" && d[0] == '\n' {
1203 return p.tok.Type == EndTagToken
1206 // Section 12.2.5.4.9.
1207 func inTableIM(p *parser) bool {
1213 p.tok.Data = strings.Replace(p.tok.Data, "\x00", "", -1)
1214 switch p.oe.top().DataAtom {
1215 case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
1216 if strings.Trim(p.tok.Data, whitespace) == "" {
1217 p.addText(p.tok.Data)
1222 switch p.tok.DataAtom {
1224 p.clearStackToContext(tableScope)
1225 p.afe = append(p.afe, &scopeMarker)
1230 p.clearStackToContext(tableScope)
1232 p.im = inColumnGroupIM
1235 p.parseImpliedToken(StartTagToken, a.Colgroup, a.Colgroup.String())
1237 case a.Tbody, a.Tfoot, a.Thead:
1238 p.clearStackToContext(tableScope)
1240 p.im = inTableBodyIM
1242 case a.Td, a.Th, a.Tr:
1243 p.parseImpliedToken(StartTagToken, a.Tbody, a.Tbody.String())
1246 if p.popUntil(tableScope, a.Table) {
1247 p.resetInsertionMode()
1250 // Ignore the token.
1252 case a.Style, a.Script:
1255 for _, t := range p.tok.Attr {
1256 if t.Key == "type" && strings.ToLower(t.Val) == "hidden" {
1262 // Otherwise drop down to the default action.
1265 // Ignore the token.
1271 p.reconstructActiveFormattingElements()
1272 switch p.top().DataAtom {
1273 case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
1274 p.fosterParenting = true
1277 p.fosterParenting = false
1278 p.framesetOK = false
1279 p.im = inSelectInTableIM
1283 switch p.tok.DataAtom {
1285 if p.popUntil(tableScope, a.Table) {
1286 p.resetInsertionMode()
1289 // Ignore the token.
1291 case a.Body, a.Caption, a.Col, a.Colgroup, a.Html, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
1292 // Ignore the token.
1302 // Ignore the token.
1306 p.fosterParenting = true
1307 defer func() { p.fosterParenting = false }()
1312 // Section 12.2.5.4.11.
1313 func inCaptionIM(p *parser) bool {
1316 switch p.tok.DataAtom {
1317 case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Td, a.Tfoot, a.Thead, a.Tr:
1318 if p.popUntil(tableScope, a.Caption) {
1319 p.clearActiveFormattingElements()
1323 // Ignore the token.
1327 p.reconstructActiveFormattingElements()
1329 p.framesetOK = false
1330 p.im = inSelectInTableIM
1334 switch p.tok.DataAtom {
1336 if p.popUntil(tableScope, a.Caption) {
1337 p.clearActiveFormattingElements()
1342 if p.popUntil(tableScope, a.Caption) {
1343 p.clearActiveFormattingElements()
1347 // Ignore the token.
1350 case a.Body, a.Col, a.Colgroup, a.Html, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
1351 // Ignore the token.
1358 // Section 12.2.5.4.12.
1359 func inColumnGroupIM(p *parser) bool {
1362 s := strings.TrimLeft(p.tok.Data, whitespace)
1363 if len(s) < len(p.tok.Data) {
1364 // Add the initial whitespace to the current node.
1365 p.addText(p.tok.Data[:len(p.tok.Data)-len(s)])
1378 // Ignore the token.
1381 switch p.tok.DataAtom {
1387 p.acknowledgeSelfClosingTag()
1391 switch p.tok.DataAtom {
1393 if p.oe.top().DataAtom != a.Html {
1399 // Ignore the token.
1403 if p.oe.top().DataAtom != a.Html {
1411 // Section 12.2.5.4.13.
1412 func inTableBodyIM(p *parser) bool {
1415 switch p.tok.DataAtom {
1417 p.clearStackToContext(tableBodyScope)
1422 p.parseImpliedToken(StartTagToken, a.Tr, a.Tr.String())
1424 case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Tfoot, a.Thead:
1425 if p.popUntil(tableScope, a.Tbody, a.Thead, a.Tfoot) {
1429 // Ignore the token.
1433 switch p.tok.DataAtom {
1434 case a.Tbody, a.Tfoot, a.Thead:
1435 if p.elementInScope(tableScope, p.tok.DataAtom) {
1436 p.clearStackToContext(tableBodyScope)
1442 if p.popUntil(tableScope, a.Tbody, a.Thead, a.Tfoot) {
1446 // Ignore the token.
1448 case a.Body, a.Caption, a.Col, a.Colgroup, a.Html, a.Td, a.Th, a.Tr:
1449 // Ignore the token.
1463 // Section 12.2.5.4.14.
1464 func inRowIM(p *parser) bool {
1467 switch p.tok.DataAtom {
1469 p.clearStackToContext(tableRowScope)
1471 p.afe = append(p.afe, &scopeMarker)
1474 case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Tfoot, a.Thead, a.Tr:
1475 if p.popUntil(tableScope, a.Tr) {
1476 p.im = inTableBodyIM
1479 // Ignore the token.
1483 switch p.tok.DataAtom {
1485 if p.popUntil(tableScope, a.Tr) {
1486 p.im = inTableBodyIM
1489 // Ignore the token.
1492 if p.popUntil(tableScope, a.Tr) {
1493 p.im = inTableBodyIM
1496 // Ignore the token.
1498 case a.Tbody, a.Tfoot, a.Thead:
1499 if p.elementInScope(tableScope, p.tok.DataAtom) {
1500 p.parseImpliedToken(EndTagToken, a.Tr, a.Tr.String())
1503 // Ignore the token.
1505 case a.Body, a.Caption, a.Col, a.Colgroup, a.Html, a.Td, a.Th:
1506 // Ignore the token.
1514 // Section 12.2.5.4.15.
1515 func inCellIM(p *parser) bool {
1518 switch p.tok.DataAtom {
1519 case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr:
1520 if p.popUntil(tableScope, a.Td, a.Th) {
1521 // Close the cell and reprocess.
1522 p.clearActiveFormattingElements()
1526 // Ignore the token.
1529 p.reconstructActiveFormattingElements()
1531 p.framesetOK = false
1532 p.im = inSelectInTableIM
1536 switch p.tok.DataAtom {
1538 if !p.popUntil(tableScope, p.tok.DataAtom) {
1539 // Ignore the token.
1542 p.clearActiveFormattingElements()
1545 case a.Body, a.Caption, a.Col, a.Colgroup, a.Html:
1546 // Ignore the token.
1548 case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr:
1549 if !p.elementInScope(tableScope, p.tok.DataAtom) {
1550 // Ignore the token.
1553 // Close the cell and reprocess.
1554 p.popUntil(tableScope, a.Td, a.Th)
1555 p.clearActiveFormattingElements()
1563 // Section 12.2.5.4.16.
1564 func inSelectIM(p *parser) bool {
1570 p.addText(strings.Replace(p.tok.Data, "\x00", "", -1))
1572 switch p.tok.DataAtom {
1576 if p.top().DataAtom == a.Option {
1581 if p.top().DataAtom == a.Option {
1584 if p.top().DataAtom == a.Optgroup {
1589 p.tok.Type = EndTagToken
1591 case a.Input, a.Keygen, a.Textarea:
1592 if p.elementInScope(selectScope, a.Select) {
1593 p.parseImpliedToken(EndTagToken, a.Select, a.Select.String())
1596 // In order to properly ignore <textarea>, we need to change the tokenizer mode.
1597 p.tokenizer.NextIsNotRawText()
1598 // Ignore the token.
1604 switch p.tok.DataAtom {
1606 if p.top().DataAtom == a.Option {
1611 if p.oe[i].DataAtom == a.Option {
1614 if p.oe[i].DataAtom == a.Optgroup {
1618 if p.popUntil(selectScope, a.Select) {
1619 p.resetInsertionMode()
1628 // Ignore the token.
1635 // Section 12.2.5.4.17.
1636 func inSelectInTableIM(p *parser) bool {
1638 case StartTagToken, EndTagToken:
1639 switch p.tok.DataAtom {
1640 case a.Caption, a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr, a.Td, a.Th:
1641 if p.tok.Type == StartTagToken || p.elementInScope(tableScope, p.tok.DataAtom) {
1642 p.parseImpliedToken(EndTagToken, a.Select, a.Select.String())
1645 // Ignore the token.
1650 return inSelectIM(p)
1653 // Section 12.2.5.4.18.
1654 func afterBodyIM(p *parser) bool {
1660 s := strings.TrimLeft(p.tok.Data, whitespace)
1662 // It was all whitespace.
1666 if p.tok.DataAtom == a.Html {
1670 if p.tok.DataAtom == a.Html {
1672 p.im = afterAfterBodyIM
1677 // The comment is attached to the <html> element.
1678 if len(p.oe) < 1 || p.oe[0].DataAtom != a.Html {
1679 panic("html: bad parser state: <html> element not found, in the after-body insertion mode")
1681 p.oe[0].AppendChild(&Node{
1691 // Section 12.2.5.4.19.
1692 func inFramesetIM(p *parser) bool {
1700 // Ignore all text but whitespace.
1701 s := strings.Map(func(c rune) rune {
1703 case ' ', '\t', '\n', '\f', '\r':
1712 switch p.tok.DataAtom {
1720 p.acknowledgeSelfClosingTag()
1725 switch p.tok.DataAtom {
1727 if p.oe.top().DataAtom != a.Html {
1729 if p.oe.top().DataAtom != a.Frameset {
1730 p.im = afterFramesetIM
1736 // Ignore the token.
1741 // Section 12.2.5.4.20.
1742 func afterFramesetIM(p *parser) bool {
1750 // Ignore all text but whitespace.
1751 s := strings.Map(func(c rune) rune {
1753 case ' ', '\t', '\n', '\f', '\r':
1762 switch p.tok.DataAtom {
1769 switch p.tok.DataAtom {
1771 p.im = afterAfterFramesetIM
1775 // Ignore the token.
1780 // Section 12.2.5.4.21.
1781 func afterAfterBodyIM(p *parser) bool {
1787 s := strings.TrimLeft(p.tok.Data, whitespace)
1789 // It was all whitespace.
1793 if p.tok.DataAtom == a.Html {
1797 p.doc.AppendChild(&Node{
1809 // Section 12.2.5.4.22.
1810 func afterAfterFramesetIM(p *parser) bool {
1813 p.doc.AppendChild(&Node{
1818 // Ignore all text but whitespace.
1819 s := strings.Map(func(c rune) rune {
1821 case ' ', '\t', '\n', '\f', '\r':
1831 switch p.tok.DataAtom {
1840 // Ignore the token.
1845 const whitespaceOrNUL = whitespace + "\x00"
1847 // Section 12.2.5.5.
1848 func parseForeignContent(p *parser) bool {
1852 p.framesetOK = strings.TrimLeft(p.tok.Data, whitespaceOrNUL) == ""
1854 p.tok.Data = strings.Replace(p.tok.Data, "\x00", "\ufffd", -1)
1855 p.addText(p.tok.Data)
1862 b := breakout[p.tok.Data]
1863 if p.tok.DataAtom == a.Font {
1865 for _, attr := range p.tok.Attr {
1867 case "color", "face", "size":
1874 for i := len(p.oe) - 1; i >= 0; i-- {
1876 if n.Namespace == "" || htmlIntegrationPoint(n) || mathMLTextIntegrationPoint(n) {
1883 switch p.top().Namespace {
1885 adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments)
1887 // Adjust SVG tag names. The tokenizer lower-cases tag names, but
1888 // SVG wants e.g. "foreignObject" with a capital second "O".
1889 if x := svgTagNameAdjustments[p.tok.Data]; x != "" {
1890 p.tok.DataAtom = a.Lookup([]byte(x))
1893 adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments)
1895 panic("html: bad parser state: unexpected namespace")
1897 adjustForeignAttributes(p.tok.Attr)
1898 namespace := p.top().Namespace
1900 p.top().Namespace = namespace
1901 if namespace != "" {
1902 // Don't let the tokenizer go into raw text mode in foreign content
1903 // (e.g. in an SVG <title> tag).
1904 p.tokenizer.NextIsNotRawText()
1906 if p.hasSelfClosingToken {
1908 p.acknowledgeSelfClosingTag()
1911 for i := len(p.oe) - 1; i >= 0; i-- {
1912 if p.oe[i].Namespace == "" {
1915 if strings.EqualFold(p.oe[i].Data, p.tok.Data) {
1922 // Ignore the token.
1928 func (p *parser) inForeignContent() bool {
1932 n := p.oe[len(p.oe)-1]
1933 if n.Namespace == "" {
1936 if mathMLTextIntegrationPoint(n) {
1937 if p.tok.Type == StartTagToken && p.tok.DataAtom != a.Mglyph && p.tok.DataAtom != a.Malignmark {
1940 if p.tok.Type == TextToken {
1944 if n.Namespace == "math" && n.DataAtom == a.AnnotationXml && p.tok.Type == StartTagToken && p.tok.DataAtom == a.Svg {
1947 if htmlIntegrationPoint(n) && (p.tok.Type == StartTagToken || p.tok.Type == TextToken) {
1950 if p.tok.Type == ErrorToken {
1956 // parseImpliedToken parses a token as though it had appeared in the parser's
1958 func (p *parser) parseImpliedToken(t TokenType, dataAtom a.Atom, data string) {
1959 realToken, selfClosing := p.tok, p.hasSelfClosingToken
1965 p.hasSelfClosingToken = false
1966 p.parseCurrentToken()
1967 p.tok, p.hasSelfClosingToken = realToken, selfClosing
1970 // parseCurrentToken runs the current token through the parsing routines
1971 // until it is consumed.
1972 func (p *parser) parseCurrentToken() {
1973 if p.tok.Type == SelfClosingTagToken {
1974 p.hasSelfClosingToken = true
1975 p.tok.Type = StartTagToken
1980 if p.inForeignContent() {
1981 consumed = parseForeignContent(p)
1987 if p.hasSelfClosingToken {
1988 // This is a parse error, but ignore it.
1989 p.hasSelfClosingToken = false
1993 func (p *parser) parse() error {
1994 // Iterate until EOF. Any other error will cause an early return.
1997 // CDATA sections are allowed only in foreign content.
1999 p.tokenizer.AllowCDATA(n != nil && n.Namespace != "")
2000 // Read and parse the next token.
2002 p.tok = p.tokenizer.Token()
2003 if p.tok.Type == ErrorToken {
2004 err = p.tokenizer.Err()
2005 if err != nil && err != io.EOF {
2009 p.parseCurrentToken()
2014 // Parse returns the parse tree for the HTML from the given Reader.
2015 // The input is assumed to be UTF-8 encoded.
2016 func Parse(r io.Reader) (*Node, error) {
2018 tokenizer: NewTokenizer(r),
2033 // ParseFragment parses a fragment of HTML and returns the nodes that were
2034 // found. If the fragment is the InnerHTML for an existing element, pass that
2035 // element in context.
2036 func ParseFragment(r io.Reader, context *Node) ([]*Node, error) {
2039 if context.Type != ElementNode {
2040 return nil, errors.New("html: ParseFragment of non-element Node")
2042 // The next check isn't just context.DataAtom.String() == context.Data because
2043 // it is valid to pass an element whose tag isn't a known atom. For example,
2044 // DataAtom == 0 and Data = "tagfromthefuture" is perfectly consistent.
2045 if context.DataAtom != a.Lookup([]byte(context.Data)) {
2046 return nil, fmt.Errorf("html: inconsistent Node: DataAtom=%q, Data=%q", context.DataAtom, context.Data)
2048 contextTag = context.DataAtom.String()
2051 tokenizer: NewTokenizerFragment(r, contextTag),
2063 Data: a.Html.String(),
2065 p.doc.AppendChild(root)
2066 p.oe = nodeStack{root}
2067 p.resetInsertionMode()
2069 for n := context; n != nil; n = n.Parent {
2070 if n.Type == ElementNode && n.DataAtom == a.Form {
2087 for c := parent.FirstChild; c != nil; {
2088 next := c.NextSibling
2089 parent.RemoveChild(c)
2090 result = append(result, c)