| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | // Copyright 2010 The Go Authors. All rights reserved. | 
					
						
							|  |  |  | // Use of this source code is governed by a BSD-style | 
					
						
							|  |  |  | // license that can be found in the LICENSE file. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | package html | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	a "golang.org/x/net/html/atom" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // A parser implements the HTML5 parsing algorithm: | 
					
						
							|  |  |  | // https://html.spec.whatwg.org/multipage/syntax.html#tree-construction | 
					
						
							|  |  |  | type parser struct { | 
					
						
							|  |  |  | 	// tokenizer provides the tokens for the parser. | 
					
						
							|  |  |  | 	tokenizer *Tokenizer | 
					
						
							|  |  |  | 	// tok is the most recently read token. | 
					
						
							|  |  |  | 	tok Token | 
					
						
							|  |  |  | 	// Self-closing tags like <hr/> are treated as start tags, except that | 
					
						
							|  |  |  | 	// hasSelfClosingToken is set while they are being processed. | 
					
						
							|  |  |  | 	hasSelfClosingToken bool | 
					
						
							|  |  |  | 	// doc is the document root element. | 
					
						
							|  |  |  | 	doc *Node | 
					
						
							|  |  |  | 	// The stack of open elements (section 12.2.4.2) and active formatting | 
					
						
							|  |  |  | 	// elements (section 12.2.4.3). | 
					
						
							|  |  |  | 	oe, afe nodeStack | 
					
						
							|  |  |  | 	// Element pointers (section 12.2.4.4). | 
					
						
							|  |  |  | 	head, form *Node | 
					
						
							|  |  |  | 	// Other parsing state flags (section 12.2.4.5). | 
					
						
							|  |  |  | 	scripting, framesetOK bool | 
					
						
							|  |  |  | 	// The stack of template insertion modes | 
					
						
							|  |  |  | 	templateStack insertionModeStack | 
					
						
							|  |  |  | 	// im is the current insertion mode. | 
					
						
							|  |  |  | 	im insertionMode | 
					
						
							|  |  |  | 	// originalIM is the insertion mode to go back to after completing a text | 
					
						
							|  |  |  | 	// or inTableText insertion mode. | 
					
						
							|  |  |  | 	originalIM insertionMode | 
					
						
							|  |  |  | 	// fosterParenting is whether new elements should be inserted according to | 
					
						
							|  |  |  | 	// the foster parenting rules (section 12.2.6.1). | 
					
						
							|  |  |  | 	fosterParenting bool | 
					
						
							|  |  |  | 	// quirks is whether the parser is operating in "quirks mode." | 
					
						
							|  |  |  | 	quirks bool | 
					
						
							|  |  |  | 	// fragment is whether the parser is parsing an HTML fragment. | 
					
						
							|  |  |  | 	fragment bool | 
					
						
							|  |  |  | 	// context is the context element when parsing an HTML fragment | 
					
						
							|  |  |  | 	// (section 12.4). | 
					
						
							|  |  |  | 	context *Node | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *parser) top() *Node { | 
					
						
							|  |  |  | 	if n := p.oe.top(); n != nil { | 
					
						
							|  |  |  | 		return n | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return p.doc | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Stop tags for use in popUntil. These come from section 12.2.4.2. | 
					
						
							|  |  |  | var ( | 
					
						
							|  |  |  | 	defaultScopeStopTags = map[string][]a.Atom{ | 
					
						
							|  |  |  | 		"":     {a.Applet, a.Caption, a.Html, a.Table, a.Td, a.Th, a.Marquee, a.Object, a.Template}, | 
					
						
							|  |  |  | 		"math": {a.AnnotationXml, a.Mi, a.Mn, a.Mo, a.Ms, a.Mtext}, | 
					
						
							|  |  |  | 		"svg":  {a.Desc, a.ForeignObject, a.Title}, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type scope int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const ( | 
					
						
							|  |  |  | 	defaultScope scope = iota | 
					
						
							|  |  |  | 	listItemScope | 
					
						
							|  |  |  | 	buttonScope | 
					
						
							|  |  |  | 	tableScope | 
					
						
							|  |  |  | 	tableRowScope | 
					
						
							|  |  |  | 	tableBodyScope | 
					
						
							|  |  |  | 	selectScope | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // popUntil pops the stack of open elements at the highest element whose tag | 
					
						
							|  |  |  | // is in matchTags, provided there is no higher element in the scope's stop | 
					
						
							|  |  |  | // tags (as defined in section 12.2.4.2). It returns whether or not there was | 
					
						
							|  |  |  | // such an element. If there was not, popUntil leaves the stack unchanged. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // For example, the set of stop tags for table scope is: "html", "table". If | 
					
						
							|  |  |  | // the stack was: | 
					
						
							|  |  |  | // ["html", "body", "font", "table", "b", "i", "u"] | 
					
						
							|  |  |  | // then popUntil(tableScope, "font") would return false, but | 
					
						
							|  |  |  | // popUntil(tableScope, "i") would return true and the stack would become: | 
					
						
							|  |  |  | // ["html", "body", "font", "table", "b"] | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // If an element's tag is in both the stop tags and matchTags, then the stack | 
					
						
							|  |  |  | // will be popped and the function returns true (provided, of course, there was | 
					
						
							|  |  |  | // no higher element in the stack that was also in the stop tags). For example, | 
					
						
							|  |  |  | // popUntil(tableScope, "table") returns true and leaves: | 
					
						
							|  |  |  | // ["html", "body", "font"] | 
					
						
							|  |  |  | func (p *parser) popUntil(s scope, matchTags ...a.Atom) bool { | 
					
						
							|  |  |  | 	if i := p.indexOfElementInScope(s, matchTags...); i != -1 { | 
					
						
							|  |  |  | 		p.oe = p.oe[:i] | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // indexOfElementInScope returns the index in p.oe of the highest element whose | 
					
						
							|  |  |  | // tag is in matchTags that is in scope. If no matching element is in scope, it | 
					
						
							|  |  |  | // returns -1. | 
					
						
							|  |  |  | func (p *parser) indexOfElementInScope(s scope, matchTags ...a.Atom) int { | 
					
						
							|  |  |  | 	for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		tagAtom := p.oe[i].DataAtom | 
					
						
							|  |  |  | 		if p.oe[i].Namespace == "" { | 
					
						
							|  |  |  | 			for _, t := range matchTags { | 
					
						
							|  |  |  | 				if t == tagAtom { | 
					
						
							|  |  |  | 					return i | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			switch s { | 
					
						
							|  |  |  | 			case defaultScope: | 
					
						
							|  |  |  | 				// No-op. | 
					
						
							|  |  |  | 			case listItemScope: | 
					
						
							|  |  |  | 				if tagAtom == a.Ol || tagAtom == a.Ul { | 
					
						
							|  |  |  | 					return -1 | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			case buttonScope: | 
					
						
							|  |  |  | 				if tagAtom == a.Button { | 
					
						
							|  |  |  | 					return -1 | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			case tableScope: | 
					
						
							|  |  |  | 				if tagAtom == a.Html || tagAtom == a.Table || tagAtom == a.Template { | 
					
						
							|  |  |  | 					return -1 | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			case selectScope: | 
					
						
							|  |  |  | 				if tagAtom != a.Optgroup && tagAtom != a.Option { | 
					
						
							|  |  |  | 					return -1 | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				panic("unreachable") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		switch s { | 
					
						
							|  |  |  | 		case defaultScope, listItemScope, buttonScope: | 
					
						
							|  |  |  | 			for _, t := range defaultScopeStopTags[p.oe[i].Namespace] { | 
					
						
							|  |  |  | 				if t == tagAtom { | 
					
						
							|  |  |  | 					return -1 | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return -1 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // elementInScope is like popUntil, except that it doesn't modify the stack of | 
					
						
							|  |  |  | // open elements. | 
					
						
							|  |  |  | func (p *parser) elementInScope(s scope, matchTags ...a.Atom) bool { | 
					
						
							|  |  |  | 	return p.indexOfElementInScope(s, matchTags...) != -1 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // clearStackToContext pops elements off the stack of open elements until a | 
					
						
							|  |  |  | // scope-defined element is found. | 
					
						
							|  |  |  | func (p *parser) clearStackToContext(s scope) { | 
					
						
							|  |  |  | 	for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		tagAtom := p.oe[i].DataAtom | 
					
						
							|  |  |  | 		switch s { | 
					
						
							|  |  |  | 		case tableScope: | 
					
						
							|  |  |  | 			if tagAtom == a.Html || tagAtom == a.Table || tagAtom == a.Template { | 
					
						
							|  |  |  | 				p.oe = p.oe[:i+1] | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case tableRowScope: | 
					
						
							|  |  |  | 			if tagAtom == a.Html || tagAtom == a.Tr || tagAtom == a.Template { | 
					
						
							|  |  |  | 				p.oe = p.oe[:i+1] | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case tableBodyScope: | 
					
						
							|  |  |  | 			if tagAtom == a.Html || tagAtom == a.Tbody || tagAtom == a.Tfoot || tagAtom == a.Thead || tagAtom == a.Template { | 
					
						
							|  |  |  | 				p.oe = p.oe[:i+1] | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			panic("unreachable") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-18 16:52:33 +01:00
										 |  |  | // parseGenericRawTextElement implements the generic raw text element parsing | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | // algorithm defined in 12.2.6.2. | 
					
						
							|  |  |  | // https://html.spec.whatwg.org/multipage/parsing.html#parsing-elements-that-contain-only-text | 
					
						
							|  |  |  | // TODO: Since both RAWTEXT and RCDATA states are treated as tokenizer's part | 
					
						
							|  |  |  | // officially, need to make tokenizer consider both states. | 
					
						
							|  |  |  | func (p *parser) parseGenericRawTextElement() { | 
					
						
							|  |  |  | 	p.addElement() | 
					
						
							|  |  |  | 	p.originalIM = p.im | 
					
						
							|  |  |  | 	p.im = textIM | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // generateImpliedEndTags pops nodes off the stack of open elements as long as | 
					
						
							|  |  |  | // the top node has a tag name of dd, dt, li, optgroup, option, p, rb, rp, rt or rtc. | 
					
						
							|  |  |  | // If exceptions are specified, nodes with that name will not be popped off. | 
					
						
							|  |  |  | func (p *parser) generateImpliedEndTags(exceptions ...string) { | 
					
						
							|  |  |  | 	var i int | 
					
						
							|  |  |  | loop: | 
					
						
							|  |  |  | 	for i = len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		n := p.oe[i] | 
					
						
							|  |  |  | 		if n.Type != ElementNode { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		switch n.DataAtom { | 
					
						
							|  |  |  | 		case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc: | 
					
						
							|  |  |  | 			for _, except := range exceptions { | 
					
						
							|  |  |  | 				if n.Data == except { | 
					
						
							|  |  |  | 					break loop | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		break | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	p.oe = p.oe[:i+1] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // addChild adds a child node n to the top element, and pushes n onto the stack | 
					
						
							|  |  |  | // of open elements if it is an element node. | 
					
						
							|  |  |  | func (p *parser) addChild(n *Node) { | 
					
						
							|  |  |  | 	if p.shouldFosterParent() { | 
					
						
							|  |  |  | 		p.fosterParent(n) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		p.top().AppendChild(n) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if n.Type == ElementNode { | 
					
						
							|  |  |  | 		p.oe = append(p.oe, n) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // shouldFosterParent returns whether the next node to be added should be | 
					
						
							|  |  |  | // foster parented. | 
					
						
							|  |  |  | func (p *parser) shouldFosterParent() bool { | 
					
						
							|  |  |  | 	if p.fosterParenting { | 
					
						
							|  |  |  | 		switch p.top().DataAtom { | 
					
						
							|  |  |  | 		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // fosterParent adds a child node according to the foster parenting rules. | 
					
						
							|  |  |  | // Section 12.2.6.1, "foster parenting". | 
					
						
							|  |  |  | func (p *parser) fosterParent(n *Node) { | 
					
						
							|  |  |  | 	var table, parent, prev, template *Node | 
					
						
							|  |  |  | 	var i int | 
					
						
							|  |  |  | 	for i = len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		if p.oe[i].DataAtom == a.Table { | 
					
						
							|  |  |  | 			table = p.oe[i] | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var j int | 
					
						
							|  |  |  | 	for j = len(p.oe) - 1; j >= 0; j-- { | 
					
						
							|  |  |  | 		if p.oe[j].DataAtom == a.Template { | 
					
						
							|  |  |  | 			template = p.oe[j] | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if template != nil && (table == nil || j > i) { | 
					
						
							|  |  |  | 		template.AppendChild(n) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if table == nil { | 
					
						
							|  |  |  | 		// The foster parent is the html element. | 
					
						
							|  |  |  | 		parent = p.oe[0] | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		parent = table.Parent | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if parent == nil { | 
					
						
							|  |  |  | 		parent = p.oe[i-1] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if table != nil { | 
					
						
							|  |  |  | 		prev = table.PrevSibling | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		prev = parent.LastChild | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if prev != nil && prev.Type == TextNode && n.Type == TextNode { | 
					
						
							|  |  |  | 		prev.Data += n.Data | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	parent.InsertBefore(n, table) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // addText adds text to the preceding node if it is a text node, or else it | 
					
						
							|  |  |  | // calls addChild with a new text node. | 
					
						
							|  |  |  | func (p *parser) addText(text string) { | 
					
						
							|  |  |  | 	if text == "" { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if p.shouldFosterParent() { | 
					
						
							|  |  |  | 		p.fosterParent(&Node{ | 
					
						
							|  |  |  | 			Type: TextNode, | 
					
						
							|  |  |  | 			Data: text, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t := p.top() | 
					
						
							|  |  |  | 	if n := t.LastChild; n != nil && n.Type == TextNode { | 
					
						
							|  |  |  | 		n.Data += text | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.addChild(&Node{ | 
					
						
							|  |  |  | 		Type: TextNode, | 
					
						
							|  |  |  | 		Data: text, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // addElement adds a child element based on the current token. | 
					
						
							|  |  |  | func (p *parser) addElement() { | 
					
						
							|  |  |  | 	p.addChild(&Node{ | 
					
						
							|  |  |  | 		Type:     ElementNode, | 
					
						
							|  |  |  | 		DataAtom: p.tok.DataAtom, | 
					
						
							|  |  |  | 		Data:     p.tok.Data, | 
					
						
							|  |  |  | 		Attr:     p.tok.Attr, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.4.3. | 
					
						
							|  |  |  | func (p *parser) addFormattingElement() { | 
					
						
							|  |  |  | 	tagAtom, attr := p.tok.DataAtom, p.tok.Attr | 
					
						
							|  |  |  | 	p.addElement() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Implement the Noah's Ark clause, but with three per family instead of two. | 
					
						
							|  |  |  | 	identicalElements := 0 | 
					
						
							|  |  |  | findIdenticalElements: | 
					
						
							|  |  |  | 	for i := len(p.afe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		n := p.afe[i] | 
					
						
							|  |  |  | 		if n.Type == scopeMarkerNode { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if n.Type != ElementNode { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if n.Namespace != "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if n.DataAtom != tagAtom { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if len(n.Attr) != len(attr) { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	compareAttributes: | 
					
						
							|  |  |  | 		for _, t0 := range n.Attr { | 
					
						
							|  |  |  | 			for _, t1 := range attr { | 
					
						
							|  |  |  | 				if t0.Key == t1.Key && t0.Namespace == t1.Namespace && t0.Val == t1.Val { | 
					
						
							|  |  |  | 					// Found a match for this attribute, continue with the next attribute. | 
					
						
							|  |  |  | 					continue compareAttributes | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// If we get here, there is no attribute that matches a. | 
					
						
							|  |  |  | 			// Therefore the element is not identical to the new one. | 
					
						
							|  |  |  | 			continue findIdenticalElements | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		identicalElements++ | 
					
						
							|  |  |  | 		if identicalElements >= 3 { | 
					
						
							|  |  |  | 			p.afe.remove(n) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	p.afe = append(p.afe, p.top()) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.4.3. | 
					
						
							|  |  |  | func (p *parser) clearActiveFormattingElements() { | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		if n := p.afe.pop(); len(p.afe) == 0 || n.Type == scopeMarkerNode { | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.4.3. | 
					
						
							|  |  |  | func (p *parser) reconstructActiveFormattingElements() { | 
					
						
							|  |  |  | 	n := p.afe.top() | 
					
						
							|  |  |  | 	if n == nil { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if n.Type == scopeMarkerNode || p.oe.index(n) != -1 { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	i := len(p.afe) - 1 | 
					
						
							|  |  |  | 	for n.Type != scopeMarkerNode && p.oe.index(n) == -1 { | 
					
						
							|  |  |  | 		if i == 0 { | 
					
						
							|  |  |  | 			i = -1 | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		i-- | 
					
						
							|  |  |  | 		n = p.afe[i] | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		i++ | 
					
						
							|  |  |  | 		clone := p.afe[i].clone() | 
					
						
							|  |  |  | 		p.addChild(clone) | 
					
						
							|  |  |  | 		p.afe[i] = clone | 
					
						
							|  |  |  | 		if i == len(p.afe)-1 { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.5. | 
					
						
							|  |  |  | func (p *parser) acknowledgeSelfClosingTag() { | 
					
						
							|  |  |  | 	p.hasSelfClosingToken = false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // An insertion mode (section 12.2.4.1) is the state transition function from | 
					
						
							|  |  |  | // a particular state in the HTML5 parser's state machine. It updates the | 
					
						
							|  |  |  | // parser's fields depending on parser.tok (where ErrorToken means EOF). | 
					
						
							|  |  |  | // It returns whether the token was consumed. | 
					
						
							|  |  |  | type insertionMode func(*parser) bool | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // setOriginalIM sets the insertion mode to return to after completing a text or | 
					
						
							|  |  |  | // inTableText insertion mode. | 
					
						
							|  |  |  | // Section 12.2.4.1, "using the rules for". | 
					
						
							|  |  |  | func (p *parser) setOriginalIM() { | 
					
						
							|  |  |  | 	if p.originalIM != nil { | 
					
						
							|  |  |  | 		panic("html: bad parser state: originalIM was set twice") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.originalIM = p.im | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.4.1, "reset the insertion mode". | 
					
						
							|  |  |  | func (p *parser) resetInsertionMode() { | 
					
						
							|  |  |  | 	for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		n := p.oe[i] | 
					
						
							|  |  |  | 		last := i == 0 | 
					
						
							|  |  |  | 		if last && p.context != nil { | 
					
						
							|  |  |  | 			n = p.context | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		switch n.DataAtom { | 
					
						
							|  |  |  | 		case a.Select: | 
					
						
							|  |  |  | 			if !last { | 
					
						
							|  |  |  | 				for ancestor, first := n, p.oe[0]; ancestor != first; { | 
					
						
							|  |  |  | 					ancestor = p.oe[p.oe.index(ancestor)-1] | 
					
						
							|  |  |  | 					switch ancestor.DataAtom { | 
					
						
							|  |  |  | 					case a.Template: | 
					
						
							|  |  |  | 						p.im = inSelectIM | 
					
						
							|  |  |  | 						return | 
					
						
							|  |  |  | 					case a.Table: | 
					
						
							|  |  |  | 						p.im = inSelectInTableIM | 
					
						
							|  |  |  | 						return | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.im = inSelectIM | 
					
						
							|  |  |  | 		case a.Td, a.Th: | 
					
						
							|  |  |  | 			// TODO: remove this divergence from the HTML5 spec. | 
					
						
							|  |  |  | 			// | 
					
						
							|  |  |  | 			// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668 | 
					
						
							|  |  |  | 			p.im = inCellIM | 
					
						
							|  |  |  | 		case a.Tr: | 
					
						
							|  |  |  | 			p.im = inRowIM | 
					
						
							|  |  |  | 		case a.Tbody, a.Thead, a.Tfoot: | 
					
						
							|  |  |  | 			p.im = inTableBodyIM | 
					
						
							|  |  |  | 		case a.Caption: | 
					
						
							|  |  |  | 			p.im = inCaptionIM | 
					
						
							|  |  |  | 		case a.Colgroup: | 
					
						
							|  |  |  | 			p.im = inColumnGroupIM | 
					
						
							|  |  |  | 		case a.Table: | 
					
						
							|  |  |  | 			p.im = inTableIM | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			// TODO: remove this divergence from the HTML5 spec. | 
					
						
							|  |  |  | 			if n.Namespace != "" { | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.im = p.templateStack.top() | 
					
						
							|  |  |  | 		case a.Head: | 
					
						
							|  |  |  | 			// TODO: remove this divergence from the HTML5 spec. | 
					
						
							|  |  |  | 			// | 
					
						
							|  |  |  | 			// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668 | 
					
						
							|  |  |  | 			p.im = inHeadIM | 
					
						
							|  |  |  | 		case a.Body: | 
					
						
							|  |  |  | 			p.im = inBodyIM | 
					
						
							|  |  |  | 		case a.Frameset: | 
					
						
							|  |  |  | 			p.im = inFramesetIM | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			if p.head == nil { | 
					
						
							|  |  |  | 				p.im = beforeHeadIM | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				p.im = afterHeadIM | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			if last { | 
					
						
							|  |  |  | 				p.im = inBodyIM | 
					
						
							|  |  |  | 				return | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const whitespace = " \t\r\n\f" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.1. | 
					
						
							|  |  |  | func initialIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(p.tok.Data) == 0 { | 
					
						
							|  |  |  | 			// It was all whitespace, so ignore it. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.doc.AppendChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		n, quirks := parseDoctype(p.tok.Data) | 
					
						
							|  |  |  | 		p.doc.AppendChild(n) | 
					
						
							|  |  |  | 		p.quirks = quirks | 
					
						
							|  |  |  | 		p.im = beforeHTMLIM | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.quirks = true | 
					
						
							|  |  |  | 	p.im = beforeHTMLIM | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.2. | 
					
						
							|  |  |  | func beforeHTMLIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(p.tok.Data) == 0 { | 
					
						
							|  |  |  | 			// It was all whitespace, so ignore it. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		if p.tok.DataAtom == a.Html { | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.im = beforeHeadIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Head, a.Body, a.Html, a.Br: | 
					
						
							|  |  |  | 			p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.doc.AppendChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.parseImpliedToken(StartTagToken, a.Html, a.Html.String()) | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.3. | 
					
						
							|  |  |  | func beforeHeadIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		p.tok.Data = strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(p.tok.Data) == 0 { | 
					
						
							|  |  |  | 			// It was all whitespace, so ignore it. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Head: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.head = p.top() | 
					
						
							|  |  |  | 			p.im = inHeadIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Head, a.Body, a.Html, a.Br: | 
					
						
							|  |  |  | 			p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	p.parseImpliedToken(StartTagToken, a.Head, a.Head.String()) | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.4. | 
					
						
							|  |  |  | func inHeadIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		s := strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(s) < len(p.tok.Data) { | 
					
						
							|  |  |  | 			// Add the initial whitespace to the current node. | 
					
						
							|  |  |  | 			p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) | 
					
						
							|  |  |  | 			if s == "" { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.tok.Data = s | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.oe.pop() | 
					
						
							|  |  |  | 			p.acknowledgeSelfClosingTag() | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Noscript: | 
					
						
							|  |  |  | 			if p.scripting { | 
					
						
							|  |  |  | 				p.parseGenericRawTextElement() | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.im = inHeadNoscriptIM | 
					
						
							|  |  |  | 			// Don't let the tokenizer go into raw text mode when scripting is disabled. | 
					
						
							|  |  |  | 			p.tokenizer.NextIsNotRawText() | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Script, a.Title: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.setOriginalIM() | 
					
						
							|  |  |  | 			p.im = textIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Noframes, a.Style: | 
					
						
							|  |  |  | 			p.parseGenericRawTextElement() | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Head: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			// TODO: remove this divergence from the HTML5 spec. | 
					
						
							|  |  |  | 			// | 
					
						
							|  |  |  | 			// We don't handle all of the corner cases when mixing foreign | 
					
						
							|  |  |  | 			// content (i.e. <math> or <svg>) with <template>. Without this | 
					
						
							|  |  |  | 			// early return, we can get into an infinite loop, possibly because | 
					
						
							|  |  |  | 			// of the "TODO... further divergence" a little below. | 
					
						
							|  |  |  | 			// | 
					
						
							|  |  |  | 			// As a workaround, if we are mixing foreign content and templates, | 
					
						
							|  |  |  | 			// just ignore the rest of the HTML. Foreign content is rare and a | 
					
						
							|  |  |  | 			// relatively old HTML feature. Templates are also rare and a | 
					
						
							|  |  |  | 			// relatively new HTML feature. Their combination is very rare. | 
					
						
							|  |  |  | 			for _, e := range p.oe { | 
					
						
							|  |  |  | 				if e.Namespace != "" { | 
					
						
							|  |  |  | 					p.im = ignoreTheRemainingTokens | 
					
						
							|  |  |  | 					return true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.afe = append(p.afe, &scopeMarker) | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.im = inTemplateIM | 
					
						
							|  |  |  | 			p.templateStack = append(p.templateStack, inTemplateIM) | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Head: | 
					
						
							|  |  |  | 			p.oe.pop() | 
					
						
							|  |  |  | 			p.im = afterHeadIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Body, a.Html, a.Br: | 
					
						
							|  |  |  | 			p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			if !p.oe.contains(a.Template) { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// TODO: remove this further divergence from the HTML5 spec. | 
					
						
							|  |  |  | 			// | 
					
						
							|  |  |  | 			// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668 | 
					
						
							|  |  |  | 			p.generateImpliedEndTags() | 
					
						
							|  |  |  | 			for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 				if n := p.oe[i]; n.Namespace == "" && n.DataAtom == a.Template { | 
					
						
							|  |  |  | 					p.oe = p.oe[:i] | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 			p.templateStack.pop() | 
					
						
							|  |  |  | 			p.resetInsertionMode() | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	p.parseImpliedToken(EndTagToken, a.Head, a.Head.String()) | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 11:30:29 +01:00
										 |  |  | // Section 12.2.6.4.5. | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | func inHeadNoscriptIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		case a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Style: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		case a.Head: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Noscript: | 
					
						
							|  |  |  | 			// Don't let the tokenizer go into raw text mode even when a <noscript> | 
					
						
							|  |  |  | 			// tag is in "in head noscript" insertion mode. | 
					
						
							|  |  |  | 			p.tokenizer.NextIsNotRawText() | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Noscript, a.Br: | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		s := strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(s) == 0 { | 
					
						
							|  |  |  | 			// It was all whitespace. | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		return inHeadIM(p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.oe.pop() | 
					
						
							|  |  |  | 	if p.top().DataAtom != a.Head { | 
					
						
							|  |  |  | 		panic("html: the new current node will be a head element.") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.im = inHeadIM | 
					
						
							|  |  |  | 	if p.tok.DataAtom == a.Noscript { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.6. | 
					
						
							|  |  |  | func afterHeadIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		s := strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(s) < len(p.tok.Data) { | 
					
						
							|  |  |  | 			// Add the initial whitespace to the current node. | 
					
						
							|  |  |  | 			p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) | 
					
						
							|  |  |  | 			if s == "" { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.tok.Data = s | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		case a.Body: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.im = inBodyIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Frameset: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.im = inFramesetIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title: | 
					
						
							|  |  |  | 			p.oe = append(p.oe, p.head) | 
					
						
							|  |  |  | 			defer p.oe.remove(p.head) | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		case a.Head: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Body, a.Html, a.Br: | 
					
						
							|  |  |  | 			// Drop down to creating an implied <body> tag. | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	p.parseImpliedToken(StartTagToken, a.Body, a.Body.String()) | 
					
						
							|  |  |  | 	p.framesetOK = true | 
					
						
							| 
									
										
										
										
											2025-01-14 13:10:39 +00:00
										 |  |  | 	if p.tok.Type == ErrorToken { | 
					
						
							|  |  |  | 		// Stop parsing. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // copyAttributes copies attributes of src not found on dst to dst. | 
					
						
							|  |  |  | func copyAttributes(dst *Node, src Token) { | 
					
						
							|  |  |  | 	if len(src.Attr) == 0 { | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	attr := map[string]string{} | 
					
						
							|  |  |  | 	for _, t := range dst.Attr { | 
					
						
							|  |  |  | 		attr[t.Key] = t.Val | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for _, t := range src.Attr { | 
					
						
							|  |  |  | 		if _, ok := attr[t.Key]; !ok { | 
					
						
							|  |  |  | 			dst.Attr = append(dst.Attr, t) | 
					
						
							|  |  |  | 			attr[t.Key] = t.Val | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.7. | 
					
						
							|  |  |  | func inBodyIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		d := p.tok.Data | 
					
						
							|  |  |  | 		switch n := p.oe.top(); n.DataAtom { | 
					
						
							|  |  |  | 		case a.Pre, a.Listing: | 
					
						
							|  |  |  | 			if n.FirstChild == nil { | 
					
						
							|  |  |  | 				// Ignore a newline at the start of a <pre> block. | 
					
						
							|  |  |  | 				if d != "" && d[0] == '\r' { | 
					
						
							|  |  |  | 					d = d[1:] | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				if d != "" && d[0] == '\n' { | 
					
						
							|  |  |  | 					d = d[1:] | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		d = strings.Replace(d, "\x00", "", -1) | 
					
						
							|  |  |  | 		if d == "" { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 		p.addText(d) | 
					
						
							|  |  |  | 		if p.framesetOK && strings.TrimLeft(d, whitespace) != "" { | 
					
						
							|  |  |  | 			// There were non-whitespace characters inserted. | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			if p.oe.contains(a.Template) { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			copyAttributes(p.oe[0], p.tok) | 
					
						
							|  |  |  | 		case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		case a.Body: | 
					
						
							|  |  |  | 			if p.oe.contains(a.Template) { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if len(p.oe) >= 2 { | 
					
						
							|  |  |  | 				body := p.oe[1] | 
					
						
							|  |  |  | 				if body.Type == ElementNode && body.DataAtom == a.Body { | 
					
						
							|  |  |  | 					p.framesetOK = false | 
					
						
							|  |  |  | 					copyAttributes(body, p.tok) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case a.Frameset: | 
					
						
							|  |  |  | 			if !p.framesetOK || len(p.oe) < 2 || p.oe[1].DataAtom != a.Body { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			body := p.oe[1] | 
					
						
							|  |  |  | 			if body.Parent != nil { | 
					
						
							|  |  |  | 				body.Parent.RemoveChild(body) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.oe = p.oe[:1] | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.im = inFramesetIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Address, a.Article, a.Aside, a.Blockquote, a.Center, a.Details, a.Dialog, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Main, a.Menu, a.Nav, a.Ol, a.P, a.Section, a.Summary, a.Ul: | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6: | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			switch n := p.top(); n.DataAtom { | 
					
						
							|  |  |  | 			case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6: | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Pre, a.Listing: | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			// The newline, if any, will be dealt with by the TextToken case. | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 		case a.Form: | 
					
						
							|  |  |  | 			if p.form != nil && !p.oe.contains(a.Template) { | 
					
						
							|  |  |  | 				// Ignore the token | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			if !p.oe.contains(a.Template) { | 
					
						
							|  |  |  | 				p.form = p.top() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case a.Li: | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 				node := p.oe[i] | 
					
						
							|  |  |  | 				switch node.DataAtom { | 
					
						
							|  |  |  | 				case a.Li: | 
					
						
							|  |  |  | 					p.oe = p.oe[:i] | 
					
						
							|  |  |  | 				case a.Address, a.Div, a.P: | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				default: | 
					
						
							|  |  |  | 					if !isSpecialElement(node) { | 
					
						
							|  |  |  | 						continue | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Dd, a.Dt: | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 				node := p.oe[i] | 
					
						
							|  |  |  | 				switch node.DataAtom { | 
					
						
							|  |  |  | 				case a.Dd, a.Dt: | 
					
						
							|  |  |  | 					p.oe = p.oe[:i] | 
					
						
							|  |  |  | 				case a.Address, a.Div, a.P: | 
					
						
							|  |  |  | 					continue | 
					
						
							|  |  |  | 				default: | 
					
						
							|  |  |  | 					if !isSpecialElement(node) { | 
					
						
							|  |  |  | 						continue | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Plaintext: | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Button: | 
					
						
							|  |  |  | 			p.popUntil(defaultScope, a.Button) | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 		case a.A: | 
					
						
							|  |  |  | 			for i := len(p.afe) - 1; i >= 0 && p.afe[i].Type != scopeMarkerNode; i-- { | 
					
						
							|  |  |  | 				if n := p.afe[i]; n.Type == ElementNode && n.DataAtom == a.A { | 
					
						
							|  |  |  | 					p.inBodyEndTagFormatting(a.A, "a") | 
					
						
							|  |  |  | 					p.oe.remove(n) | 
					
						
							|  |  |  | 					p.afe.remove(n) | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addFormattingElement() | 
					
						
							|  |  |  | 		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: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addFormattingElement() | 
					
						
							|  |  |  | 		case a.Nobr: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			if p.elementInScope(defaultScope, a.Nobr) { | 
					
						
							|  |  |  | 				p.inBodyEndTagFormatting(a.Nobr, "nobr") | 
					
						
							|  |  |  | 				p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addFormattingElement() | 
					
						
							|  |  |  | 		case a.Applet, a.Marquee, a.Object: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.afe = append(p.afe, &scopeMarker) | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 		case a.Table: | 
					
						
							|  |  |  | 			if !p.quirks { | 
					
						
							|  |  |  | 				p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.im = inTableIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Area, a.Br, a.Embed, a.Img, a.Input, a.Keygen, a.Wbr: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.oe.pop() | 
					
						
							|  |  |  | 			p.acknowledgeSelfClosingTag() | 
					
						
							|  |  |  | 			if p.tok.DataAtom == a.Input { | 
					
						
							|  |  |  | 				for _, t := range p.tok.Attr { | 
					
						
							|  |  |  | 					if t.Key == "type" { | 
					
						
							| 
									
										
										
										
											2025-01-14 13:10:39 +00:00
										 |  |  | 						if strings.EqualFold(t.Val, "hidden") { | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 							// Skip setting framesetOK = false | 
					
						
							|  |  |  | 							return true | 
					
						
							|  |  |  | 						} | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 		case a.Param, a.Source, a.Track: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.oe.pop() | 
					
						
							|  |  |  | 			p.acknowledgeSelfClosingTag() | 
					
						
							|  |  |  | 		case a.Hr: | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.oe.pop() | 
					
						
							|  |  |  | 			p.acknowledgeSelfClosingTag() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 		case a.Image: | 
					
						
							|  |  |  | 			p.tok.DataAtom = a.Img | 
					
						
							|  |  |  | 			p.tok.Data = a.Img.String() | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Textarea: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.setOriginalIM() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.im = textIM | 
					
						
							|  |  |  | 		case a.Xmp: | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.parseGenericRawTextElement() | 
					
						
							|  |  |  | 		case a.Iframe: | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.parseGenericRawTextElement() | 
					
						
							|  |  |  | 		case a.Noembed: | 
					
						
							|  |  |  | 			p.parseGenericRawTextElement() | 
					
						
							|  |  |  | 		case a.Noscript: | 
					
						
							|  |  |  | 			if p.scripting { | 
					
						
							|  |  |  | 				p.parseGenericRawTextElement() | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			// Don't let the tokenizer go into raw text mode when scripting is disabled. | 
					
						
							|  |  |  | 			p.tokenizer.NextIsNotRawText() | 
					
						
							|  |  |  | 		case a.Select: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.im = inSelectIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Optgroup, a.Option: | 
					
						
							|  |  |  | 			if p.top().DataAtom == a.Option { | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Rb, a.Rtc: | 
					
						
							|  |  |  | 			if p.elementInScope(defaultScope, a.Ruby) { | 
					
						
							|  |  |  | 				p.generateImpliedEndTags() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Rp, a.Rt: | 
					
						
							|  |  |  | 			if p.elementInScope(defaultScope, a.Ruby) { | 
					
						
							|  |  |  | 				p.generateImpliedEndTags("rtc") | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Math, a.Svg: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			if p.tok.DataAtom == a.Math { | 
					
						
							|  |  |  | 				adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			adjustForeignAttributes(p.tok.Attr) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.top().Namespace = p.tok.Data | 
					
						
							|  |  |  | 			if p.hasSelfClosingToken { | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 				p.acknowledgeSelfClosingTag() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Caption, a.Col, a.Colgroup, a.Frame, a.Head, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Body: | 
					
						
							|  |  |  | 			if p.elementInScope(defaultScope, a.Body) { | 
					
						
							|  |  |  | 				p.im = afterBodyIM | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			if p.elementInScope(defaultScope, a.Body) { | 
					
						
							|  |  |  | 				p.parseImpliedToken(EndTagToken, a.Body, a.Body.String()) | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Address, a.Article, a.Aside, a.Blockquote, a.Button, a.Center, a.Details, a.Dialog, a.Dir, a.Div, a.Dl, a.Fieldset, a.Figcaption, a.Figure, a.Footer, a.Header, a.Hgroup, a.Listing, a.Main, a.Menu, a.Nav, a.Ol, a.Pre, a.Section, a.Summary, a.Ul: | 
					
						
							|  |  |  | 			p.popUntil(defaultScope, p.tok.DataAtom) | 
					
						
							|  |  |  | 		case a.Form: | 
					
						
							|  |  |  | 			if p.oe.contains(a.Template) { | 
					
						
							|  |  |  | 				i := p.indexOfElementInScope(defaultScope, a.Form) | 
					
						
							|  |  |  | 				if i == -1 { | 
					
						
							|  |  |  | 					// Ignore the token. | 
					
						
							|  |  |  | 					return true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				p.generateImpliedEndTags() | 
					
						
							|  |  |  | 				if p.oe[i].DataAtom != a.Form { | 
					
						
							|  |  |  | 					// Ignore the token. | 
					
						
							|  |  |  | 					return true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				p.popUntil(defaultScope, a.Form) | 
					
						
							|  |  |  | 			} else { | 
					
						
							|  |  |  | 				node := p.form | 
					
						
							|  |  |  | 				p.form = nil | 
					
						
							|  |  |  | 				i := p.indexOfElementInScope(defaultScope, a.Form) | 
					
						
							|  |  |  | 				if node == nil || i == -1 || p.oe[i] != node { | 
					
						
							|  |  |  | 					// Ignore the token. | 
					
						
							|  |  |  | 					return true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				p.generateImpliedEndTags() | 
					
						
							|  |  |  | 				p.oe.remove(node) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case a.P: | 
					
						
							|  |  |  | 			if !p.elementInScope(buttonScope, a.P) { | 
					
						
							|  |  |  | 				p.parseImpliedToken(StartTagToken, a.P, a.P.String()) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.popUntil(buttonScope, a.P) | 
					
						
							|  |  |  | 		case a.Li: | 
					
						
							|  |  |  | 			p.popUntil(listItemScope, a.Li) | 
					
						
							|  |  |  | 		case a.Dd, a.Dt: | 
					
						
							|  |  |  | 			p.popUntil(defaultScope, p.tok.DataAtom) | 
					
						
							|  |  |  | 		case a.H1, a.H2, a.H3, a.H4, a.H5, a.H6: | 
					
						
							|  |  |  | 			p.popUntil(defaultScope, a.H1, a.H2, a.H3, a.H4, a.H5, a.H6) | 
					
						
							|  |  |  | 		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: | 
					
						
							|  |  |  | 			p.inBodyEndTagFormatting(p.tok.DataAtom, p.tok.Data) | 
					
						
							|  |  |  | 		case a.Applet, a.Marquee, a.Object: | 
					
						
							|  |  |  | 			if p.popUntil(defaultScope, p.tok.DataAtom) { | 
					
						
							|  |  |  | 				p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case a.Br: | 
					
						
							|  |  |  | 			p.tok.Type = StartTagToken | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			p.inBodyEndTagOther(p.tok.DataAtom, p.tok.Data) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	case ErrorToken: | 
					
						
							|  |  |  | 		// TODO: remove this divergence from the HTML5 spec. | 
					
						
							|  |  |  | 		if len(p.templateStack) > 0 { | 
					
						
							|  |  |  | 			p.im = inTemplateIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		for _, e := range p.oe { | 
					
						
							|  |  |  | 			switch e.DataAtom { | 
					
						
							|  |  |  | 			case a.Dd, a.Dt, a.Li, a.Optgroup, a.Option, a.P, a.Rb, a.Rp, a.Rt, a.Rtc, a.Tbody, a.Td, a.Tfoot, a.Th, | 
					
						
							|  |  |  | 				a.Thead, a.Tr, a.Body, a.Html: | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *parser) inBodyEndTagFormatting(tagAtom a.Atom, tagName string) { | 
					
						
							|  |  |  | 	// This is the "adoption agency" algorithm, described at | 
					
						
							|  |  |  | 	// https://html.spec.whatwg.org/multipage/syntax.html#adoptionAgency | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// TODO: this is a fairly literal line-by-line translation of that algorithm. | 
					
						
							|  |  |  | 	// Once the code successfully parses the comprehensive test suite, we should | 
					
						
							|  |  |  | 	// refactor this code to be more idiomatic. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Steps 1-2 | 
					
						
							|  |  |  | 	if current := p.oe.top(); current.Data == tagName && p.afe.index(current) == -1 { | 
					
						
							|  |  |  | 		p.oe.pop() | 
					
						
							|  |  |  | 		return | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Steps 3-5. The outer loop. | 
					
						
							|  |  |  | 	for i := 0; i < 8; i++ { | 
					
						
							|  |  |  | 		// Step 6. Find the formatting element. | 
					
						
							|  |  |  | 		var formattingElement *Node | 
					
						
							|  |  |  | 		for j := len(p.afe) - 1; j >= 0; j-- { | 
					
						
							|  |  |  | 			if p.afe[j].Type == scopeMarkerNode { | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if p.afe[j].DataAtom == tagAtom { | 
					
						
							|  |  |  | 				formattingElement = p.afe[j] | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if formattingElement == nil { | 
					
						
							|  |  |  | 			p.inBodyEndTagOther(tagAtom, tagName) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Step 7. Ignore the tag if formatting element is not in the stack of open elements. | 
					
						
							|  |  |  | 		feIndex := p.oe.index(formattingElement) | 
					
						
							|  |  |  | 		if feIndex == -1 { | 
					
						
							|  |  |  | 			p.afe.remove(formattingElement) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// Step 8. Ignore the tag if formatting element is not in the scope. | 
					
						
							|  |  |  | 		if !p.elementInScope(defaultScope, tagAtom) { | 
					
						
							|  |  |  | 			// Ignore the tag. | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Step 9. This step is omitted because it's just a parse error but no need to return. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Steps 10-11. Find the furthest block. | 
					
						
							|  |  |  | 		var furthestBlock *Node | 
					
						
							|  |  |  | 		for _, e := range p.oe[feIndex:] { | 
					
						
							|  |  |  | 			if isSpecialElement(e) { | 
					
						
							|  |  |  | 				furthestBlock = e | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if furthestBlock == nil { | 
					
						
							|  |  |  | 			e := p.oe.pop() | 
					
						
							|  |  |  | 			for e != formattingElement { | 
					
						
							|  |  |  | 				e = p.oe.pop() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.afe.remove(e) | 
					
						
							|  |  |  | 			return | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Steps 12-13. Find the common ancestor and bookmark node. | 
					
						
							|  |  |  | 		commonAncestor := p.oe[feIndex-1] | 
					
						
							|  |  |  | 		bookmark := p.afe.index(formattingElement) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Step 14. The inner loop. Find the lastNode to reparent. | 
					
						
							|  |  |  | 		lastNode := furthestBlock | 
					
						
							|  |  |  | 		node := furthestBlock | 
					
						
							|  |  |  | 		x := p.oe.index(node) | 
					
						
							|  |  |  | 		// Step 14.1. | 
					
						
							|  |  |  | 		j := 0 | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			// Step 14.2. | 
					
						
							|  |  |  | 			j++ | 
					
						
							|  |  |  | 			// Step. 14.3. | 
					
						
							|  |  |  | 			x-- | 
					
						
							|  |  |  | 			node = p.oe[x] | 
					
						
							|  |  |  | 			// Step 14.4. Go to the next step if node is formatting element. | 
					
						
							|  |  |  | 			if node == formattingElement { | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Step 14.5. Remove node from the list of active formatting elements if | 
					
						
							|  |  |  | 			// inner loop counter is greater than three and node is in the list of | 
					
						
							|  |  |  | 			// active formatting elements. | 
					
						
							|  |  |  | 			if ni := p.afe.index(node); j > 3 && ni > -1 { | 
					
						
							|  |  |  | 				p.afe.remove(node) | 
					
						
							|  |  |  | 				// If any element of the list of active formatting elements is removed, | 
					
						
							|  |  |  | 				// we need to take care whether bookmark should be decremented or not. | 
					
						
							|  |  |  | 				// This is because the value of bookmark may exceed the size of the | 
					
						
							|  |  |  | 				// list by removing elements from the list. | 
					
						
							|  |  |  | 				if ni <= bookmark { | 
					
						
							|  |  |  | 					bookmark-- | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Step 14.6. Continue the next inner loop if node is not in the list of | 
					
						
							|  |  |  | 			// active formatting elements. | 
					
						
							|  |  |  | 			if p.afe.index(node) == -1 { | 
					
						
							|  |  |  | 				p.oe.remove(node) | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Step 14.7. | 
					
						
							|  |  |  | 			clone := node.clone() | 
					
						
							|  |  |  | 			p.afe[p.afe.index(node)] = clone | 
					
						
							|  |  |  | 			p.oe[p.oe.index(node)] = clone | 
					
						
							|  |  |  | 			node = clone | 
					
						
							|  |  |  | 			// Step 14.8. | 
					
						
							|  |  |  | 			if lastNode == furthestBlock { | 
					
						
							|  |  |  | 				bookmark = p.afe.index(node) + 1 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Step 14.9. | 
					
						
							|  |  |  | 			if lastNode.Parent != nil { | 
					
						
							|  |  |  | 				lastNode.Parent.RemoveChild(lastNode) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			node.AppendChild(lastNode) | 
					
						
							|  |  |  | 			// Step 14.10. | 
					
						
							|  |  |  | 			lastNode = node | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Step 15. Reparent lastNode to the common ancestor, | 
					
						
							|  |  |  | 		// or for misnested table nodes, to the foster parent. | 
					
						
							|  |  |  | 		if lastNode.Parent != nil { | 
					
						
							|  |  |  | 			lastNode.Parent.RemoveChild(lastNode) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		switch commonAncestor.DataAtom { | 
					
						
							|  |  |  | 		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			p.fosterParent(lastNode) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			commonAncestor.AppendChild(lastNode) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Steps 16-18. Reparent nodes from the furthest block's children | 
					
						
							|  |  |  | 		// to a clone of the formatting element. | 
					
						
							|  |  |  | 		clone := formattingElement.clone() | 
					
						
							|  |  |  | 		reparentChildren(clone, furthestBlock) | 
					
						
							|  |  |  | 		furthestBlock.AppendChild(clone) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Step 19. Fix up the list of active formatting elements. | 
					
						
							|  |  |  | 		if oldLoc := p.afe.index(formattingElement); oldLoc != -1 && oldLoc < bookmark { | 
					
						
							|  |  |  | 			// Move the bookmark with the rest of the list. | 
					
						
							|  |  |  | 			bookmark-- | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		p.afe.remove(formattingElement) | 
					
						
							|  |  |  | 		p.afe.insert(bookmark, clone) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Step 20. Fix up the stack of open elements. | 
					
						
							|  |  |  | 		p.oe.remove(formattingElement) | 
					
						
							|  |  |  | 		p.oe.insert(p.oe.index(furthestBlock)+1, clone) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // inBodyEndTagOther performs the "any other end tag" algorithm for inBodyIM. | 
					
						
							|  |  |  | // "Any other end tag" handling from 12.2.6.5 The rules for parsing tokens in foreign content | 
					
						
							|  |  |  | // https://html.spec.whatwg.org/multipage/syntax.html#parsing-main-inforeign | 
					
						
							|  |  |  | func (p *parser) inBodyEndTagOther(tagAtom a.Atom, tagName string) { | 
					
						
							|  |  |  | 	for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 		// Two element nodes have the same tag if they have the same Data (a | 
					
						
							|  |  |  | 		// string-typed field). As an optimization, for common HTML tags, each | 
					
						
							|  |  |  | 		// Data string is assigned a unique, non-zero DataAtom (a uint32-typed | 
					
						
							|  |  |  | 		// field), since integer comparison is faster than string comparison. | 
					
						
							|  |  |  | 		// Uncommon (custom) tags get a zero DataAtom. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// The if condition here is equivalent to (p.oe[i].Data == tagName). | 
					
						
							|  |  |  | 		if (p.oe[i].DataAtom == tagAtom) && | 
					
						
							|  |  |  | 			((tagAtom != 0) || (p.oe[i].Data == tagName)) { | 
					
						
							|  |  |  | 			p.oe = p.oe[:i] | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if isSpecialElement(p.oe[i]) { | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.8. | 
					
						
							|  |  |  | func textIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case ErrorToken: | 
					
						
							|  |  |  | 		p.oe.pop() | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		d := p.tok.Data | 
					
						
							|  |  |  | 		if n := p.oe.top(); n.DataAtom == a.Textarea && n.FirstChild == nil { | 
					
						
							|  |  |  | 			// Ignore a newline at the start of a <textarea> block. | 
					
						
							|  |  |  | 			if d != "" && d[0] == '\r' { | 
					
						
							|  |  |  | 				d = d[1:] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if d != "" && d[0] == '\n' { | 
					
						
							|  |  |  | 				d = d[1:] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if d == "" { | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		p.addText(d) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		p.oe.pop() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.im = p.originalIM | 
					
						
							|  |  |  | 	p.originalIM = nil | 
					
						
							|  |  |  | 	return p.tok.Type == EndTagToken | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.9. | 
					
						
							|  |  |  | func inTableIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		p.tok.Data = strings.Replace(p.tok.Data, "\x00", "", -1) | 
					
						
							|  |  |  | 		switch p.oe.top().DataAtom { | 
					
						
							|  |  |  | 		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			if strings.Trim(p.tok.Data, whitespace) == "" { | 
					
						
							|  |  |  | 				p.addText(p.tok.Data) | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Caption: | 
					
						
							|  |  |  | 			p.clearStackToContext(tableScope) | 
					
						
							|  |  |  | 			p.afe = append(p.afe, &scopeMarker) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.im = inCaptionIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Colgroup: | 
					
						
							|  |  |  | 			p.clearStackToContext(tableScope) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.im = inColumnGroupIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Col: | 
					
						
							|  |  |  | 			p.parseImpliedToken(StartTagToken, a.Colgroup, a.Colgroup.String()) | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Tbody, a.Tfoot, a.Thead: | 
					
						
							|  |  |  | 			p.clearStackToContext(tableScope) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.im = inTableBodyIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Td, a.Th, a.Tr: | 
					
						
							|  |  |  | 			p.parseImpliedToken(StartTagToken, a.Tbody, a.Tbody.String()) | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Table: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Table) { | 
					
						
							|  |  |  | 				p.resetInsertionMode() | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Style, a.Script, a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		case a.Input: | 
					
						
							|  |  |  | 			for _, t := range p.tok.Attr { | 
					
						
							| 
									
										
										
										
											2025-01-14 13:10:39 +00:00
										 |  |  | 				if t.Key == "type" && strings.EqualFold(t.Val, "hidden") { | 
					
						
							| 
									
										
										
										
											2021-08-12 21:03:24 +02:00
										 |  |  | 					p.addElement() | 
					
						
							|  |  |  | 					p.oe.pop() | 
					
						
							|  |  |  | 					return true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Otherwise drop down to the default action. | 
					
						
							|  |  |  | 		case a.Form: | 
					
						
							|  |  |  | 			if p.oe.contains(a.Template) || p.form != nil { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.form = p.oe.pop() | 
					
						
							|  |  |  | 		case a.Select: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			switch p.top().DataAtom { | 
					
						
							|  |  |  | 			case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: | 
					
						
							|  |  |  | 				p.fosterParenting = true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.fosterParenting = false | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.im = inSelectInTableIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Table: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Table) { | 
					
						
							|  |  |  | 				p.resetInsertionMode() | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Body, a.Caption, a.Col, a.Colgroup, a.Html, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case ErrorToken: | 
					
						
							|  |  |  | 		return inBodyIM(p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	p.fosterParenting = true | 
					
						
							|  |  |  | 	defer func() { p.fosterParenting = false }() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return inBodyIM(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.11. | 
					
						
							|  |  |  | func inCaptionIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Td, a.Tfoot, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			if !p.popUntil(tableScope, a.Caption) { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 			p.im = inTableIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Select: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.im = inSelectInTableIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Caption: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Caption) { | 
					
						
							|  |  |  | 				p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 				p.im = inTableIM | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Table: | 
					
						
							|  |  |  | 			if !p.popUntil(tableScope, a.Caption) { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 			p.im = inTableIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Body, a.Col, a.Colgroup, a.Html, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return inBodyIM(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.12. | 
					
						
							|  |  |  | func inColumnGroupIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		s := strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(s) < len(p.tok.Data) { | 
					
						
							|  |  |  | 			// Add the initial whitespace to the current node. | 
					
						
							|  |  |  | 			p.addText(p.tok.Data[:len(p.tok.Data)-len(s)]) | 
					
						
							|  |  |  | 			if s == "" { | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.tok.Data = s | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		case a.Col: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.oe.pop() | 
					
						
							|  |  |  | 			p.acknowledgeSelfClosingTag() | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Colgroup: | 
					
						
							|  |  |  | 			if p.oe.top().DataAtom == a.Colgroup { | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 				p.im = inTableIM | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Col: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case ErrorToken: | 
					
						
							|  |  |  | 		return inBodyIM(p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if p.oe.top().DataAtom != a.Colgroup { | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.oe.pop() | 
					
						
							|  |  |  | 	p.im = inTableIM | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.13. | 
					
						
							|  |  |  | func inTableBodyIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Tr: | 
					
						
							|  |  |  | 			p.clearStackToContext(tableBodyScope) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.im = inRowIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Td, a.Th: | 
					
						
							|  |  |  | 			p.parseImpliedToken(StartTagToken, a.Tr, a.Tr.String()) | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Tfoot, a.Thead: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Tbody, a.Thead, a.Tfoot) { | 
					
						
							|  |  |  | 				p.im = inTableIM | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Tbody, a.Tfoot, a.Thead: | 
					
						
							|  |  |  | 			if p.elementInScope(tableScope, p.tok.DataAtom) { | 
					
						
							|  |  |  | 				p.clearStackToContext(tableBodyScope) | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 				p.im = inTableIM | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Table: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Tbody, a.Thead, a.Tfoot) { | 
					
						
							|  |  |  | 				p.im = inTableIM | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Body, a.Caption, a.Col, a.Colgroup, a.Html, a.Td, a.Th, a.Tr: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return inTableIM(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.14. | 
					
						
							|  |  |  | func inRowIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Td, a.Th: | 
					
						
							|  |  |  | 			p.clearStackToContext(tableRowScope) | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.afe = append(p.afe, &scopeMarker) | 
					
						
							|  |  |  | 			p.im = inCellIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Tfoot, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Tr) { | 
					
						
							|  |  |  | 				p.im = inTableBodyIM | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Tr: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Tr) { | 
					
						
							|  |  |  | 				p.im = inTableBodyIM | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Table: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Tr) { | 
					
						
							|  |  |  | 				p.im = inTableBodyIM | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Tbody, a.Tfoot, a.Thead: | 
					
						
							|  |  |  | 			if p.elementInScope(tableScope, p.tok.DataAtom) { | 
					
						
							|  |  |  | 				p.parseImpliedToken(EndTagToken, a.Tr, a.Tr.String()) | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Body, a.Caption, a.Col, a.Colgroup, a.Html, a.Td, a.Th: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return inTableIM(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.15. | 
					
						
							|  |  |  | func inCellIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Caption, a.Col, a.Colgroup, a.Tbody, a.Td, a.Tfoot, a.Th, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Td, a.Th) { | 
					
						
							|  |  |  | 				// Close the cell and reprocess. | 
					
						
							|  |  |  | 				p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 				p.im = inRowIM | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Select: | 
					
						
							|  |  |  | 			p.reconstructActiveFormattingElements() | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.framesetOK = false | 
					
						
							|  |  |  | 			p.im = inSelectInTableIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Td, a.Th: | 
					
						
							|  |  |  | 			if !p.popUntil(tableScope, p.tok.DataAtom) { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 			p.im = inRowIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Body, a.Caption, a.Col, a.Colgroup, a.Html: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr: | 
					
						
							|  |  |  | 			if !p.elementInScope(tableScope, p.tok.DataAtom) { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// Close the cell and reprocess. | 
					
						
							|  |  |  | 			if p.popUntil(tableScope, a.Td, a.Th) { | 
					
						
							|  |  |  | 				p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.im = inRowIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return inBodyIM(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.16. | 
					
						
							|  |  |  | func inSelectIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		p.addText(strings.Replace(p.tok.Data, "\x00", "", -1)) | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		case a.Option: | 
					
						
							|  |  |  | 			if p.top().DataAtom == a.Option { | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Optgroup: | 
					
						
							|  |  |  | 			if p.top().DataAtom == a.Option { | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if p.top().DataAtom == a.Optgroup { | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Select: | 
					
						
							|  |  |  | 			if !p.popUntil(selectScope, a.Select) { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.resetInsertionMode() | 
					
						
							|  |  |  | 		case a.Input, a.Keygen, a.Textarea: | 
					
						
							|  |  |  | 			if p.elementInScope(selectScope, a.Select) { | 
					
						
							|  |  |  | 				p.parseImpliedToken(EndTagToken, a.Select, a.Select.String()) | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// In order to properly ignore <textarea>, we need to change the tokenizer mode. | 
					
						
							|  |  |  | 			p.tokenizer.NextIsNotRawText() | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		case a.Script, a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		case a.Iframe, a.Noembed, a.Noframes, a.Noscript, a.Plaintext, a.Style, a.Title, a.Xmp: | 
					
						
							|  |  |  | 			// Don't let the tokenizer go into raw text mode when there are raw tags | 
					
						
							|  |  |  | 			// to be ignored. These tags should be ignored from the tokenizer | 
					
						
							|  |  |  | 			// properly. | 
					
						
							|  |  |  | 			p.tokenizer.NextIsNotRawText() | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Option: | 
					
						
							|  |  |  | 			if p.top().DataAtom == a.Option { | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case a.Optgroup: | 
					
						
							|  |  |  | 			i := len(p.oe) - 1 | 
					
						
							|  |  |  | 			if p.oe[i].DataAtom == a.Option { | 
					
						
							|  |  |  | 				i-- | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if p.oe[i].DataAtom == a.Optgroup { | 
					
						
							|  |  |  | 				p.oe = p.oe[:i] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		case a.Select: | 
					
						
							|  |  |  | 			if !p.popUntil(selectScope, a.Select) { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.resetInsertionMode() | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case ErrorToken: | 
					
						
							|  |  |  | 		return inBodyIM(p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.17. | 
					
						
							|  |  |  | func inSelectInTableIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case StartTagToken, EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Caption, a.Table, a.Tbody, a.Tfoot, a.Thead, a.Tr, a.Td, a.Th: | 
					
						
							|  |  |  | 			if p.tok.Type == EndTagToken && !p.elementInScope(tableScope, p.tok.DataAtom) { | 
					
						
							|  |  |  | 				// Ignore the token. | 
					
						
							|  |  |  | 				return true | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			// This is like p.popUntil(selectScope, a.Select), but it also | 
					
						
							|  |  |  | 			// matches <math select>, not just <select>. Matching the MathML | 
					
						
							|  |  |  | 			// tag is arguably incorrect (conceptually), but it mimics what | 
					
						
							|  |  |  | 			// Chromium does. | 
					
						
							|  |  |  | 			for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 				if n := p.oe[i]; n.DataAtom == a.Select { | 
					
						
							|  |  |  | 					p.oe = p.oe[:i] | 
					
						
							|  |  |  | 					break | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			p.resetInsertionMode() | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return inSelectIM(p) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.18. | 
					
						
							|  |  |  | func inTemplateIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken, CommentToken, DoctypeToken: | 
					
						
							|  |  |  | 		return inBodyIM(p) | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Base, a.Basefont, a.Bgsound, a.Link, a.Meta, a.Noframes, a.Script, a.Style, a.Template, a.Title: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		case a.Caption, a.Colgroup, a.Tbody, a.Tfoot, a.Thead: | 
					
						
							|  |  |  | 			p.templateStack.pop() | 
					
						
							|  |  |  | 			p.templateStack = append(p.templateStack, inTableIM) | 
					
						
							|  |  |  | 			p.im = inTableIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Col: | 
					
						
							|  |  |  | 			p.templateStack.pop() | 
					
						
							|  |  |  | 			p.templateStack = append(p.templateStack, inColumnGroupIM) | 
					
						
							|  |  |  | 			p.im = inColumnGroupIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Tr: | 
					
						
							|  |  |  | 			p.templateStack.pop() | 
					
						
							|  |  |  | 			p.templateStack = append(p.templateStack, inTableBodyIM) | 
					
						
							|  |  |  | 			p.im = inTableBodyIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		case a.Td, a.Th: | 
					
						
							|  |  |  | 			p.templateStack.pop() | 
					
						
							|  |  |  | 			p.templateStack = append(p.templateStack, inRowIM) | 
					
						
							|  |  |  | 			p.im = inRowIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			p.templateStack.pop() | 
					
						
							|  |  |  | 			p.templateStack = append(p.templateStack, inBodyIM) | 
					
						
							|  |  |  | 			p.im = inBodyIM | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Template: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case ErrorToken: | 
					
						
							|  |  |  | 		if !p.oe.contains(a.Template) { | 
					
						
							|  |  |  | 			// Ignore the token. | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// TODO: remove this divergence from the HTML5 spec. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// See https://bugs.chromium.org/p/chromium/issues/detail?id=829668 | 
					
						
							|  |  |  | 		p.generateImpliedEndTags() | 
					
						
							|  |  |  | 		for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 			if n := p.oe[i]; n.Namespace == "" && n.DataAtom == a.Template { | 
					
						
							|  |  |  | 				p.oe = p.oe[:i] | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		p.clearActiveFormattingElements() | 
					
						
							|  |  |  | 		p.templateStack.pop() | 
					
						
							|  |  |  | 		p.resetInsertionMode() | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.19. | 
					
						
							|  |  |  | func afterBodyIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case ErrorToken: | 
					
						
							|  |  |  | 		// Stop parsing. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		s := strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(s) == 0 { | 
					
						
							|  |  |  | 			// It was all whitespace. | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		if p.tok.DataAtom == a.Html { | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		if p.tok.DataAtom == a.Html { | 
					
						
							|  |  |  | 			if !p.fragment { | 
					
						
							|  |  |  | 				p.im = afterAfterBodyIM | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		// The comment is attached to the <html> element. | 
					
						
							|  |  |  | 		if len(p.oe) < 1 || p.oe[0].DataAtom != a.Html { | 
					
						
							|  |  |  | 			panic("html: bad parser state: <html> element not found, in the after-body insertion mode") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		p.oe[0].AppendChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.im = inBodyIM | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.20. | 
					
						
							|  |  |  | func inFramesetIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		// Ignore all text but whitespace. | 
					
						
							|  |  |  | 		s := strings.Map(func(c rune) rune { | 
					
						
							|  |  |  | 			switch c { | 
					
						
							|  |  |  | 			case ' ', '\t', '\n', '\f', '\r': | 
					
						
							|  |  |  | 				return c | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return -1 | 
					
						
							|  |  |  | 		}, p.tok.Data) | 
					
						
							|  |  |  | 		if s != "" { | 
					
						
							|  |  |  | 			p.addText(s) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		case a.Frameset: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 		case a.Frame: | 
					
						
							|  |  |  | 			p.addElement() | 
					
						
							|  |  |  | 			p.oe.pop() | 
					
						
							|  |  |  | 			p.acknowledgeSelfClosingTag() | 
					
						
							|  |  |  | 		case a.Noframes: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Frameset: | 
					
						
							|  |  |  | 			if p.oe.top().DataAtom != a.Html { | 
					
						
							|  |  |  | 				p.oe.pop() | 
					
						
							|  |  |  | 				if p.oe.top().DataAtom != a.Frameset { | 
					
						
							|  |  |  | 					p.im = afterFramesetIM | 
					
						
							|  |  |  | 					return true | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.21. | 
					
						
							|  |  |  | func afterFramesetIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		// Ignore all text but whitespace. | 
					
						
							|  |  |  | 		s := strings.Map(func(c rune) rune { | 
					
						
							|  |  |  | 			switch c { | 
					
						
							|  |  |  | 			case ' ', '\t', '\n', '\f', '\r': | 
					
						
							|  |  |  | 				return c | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return -1 | 
					
						
							|  |  |  | 		}, p.tok.Data) | 
					
						
							|  |  |  | 		if s != "" { | 
					
						
							|  |  |  | 			p.addText(s) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		case a.Noframes: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			p.im = afterAfterFramesetIM | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.22. | 
					
						
							|  |  |  | func afterAfterBodyIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case ErrorToken: | 
					
						
							|  |  |  | 		// Stop parsing. | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		s := strings.TrimLeft(p.tok.Data, whitespace) | 
					
						
							|  |  |  | 		if len(s) == 0 { | 
					
						
							|  |  |  | 			// It was all whitespace. | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		if p.tok.DataAtom == a.Html { | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.doc.AppendChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		return inBodyIM(p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.im = inBodyIM | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.4.23. | 
					
						
							|  |  |  | func afterAfterFramesetIM(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.doc.AppendChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		// Ignore all text but whitespace. | 
					
						
							|  |  |  | 		s := strings.Map(func(c rune) rune { | 
					
						
							|  |  |  | 			switch c { | 
					
						
							|  |  |  | 			case ' ', '\t', '\n', '\f', '\r': | 
					
						
							|  |  |  | 				return c | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			return -1 | 
					
						
							|  |  |  | 		}, p.tok.Data) | 
					
						
							|  |  |  | 		if s != "" { | 
					
						
							|  |  |  | 			p.tok.Data = s | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		switch p.tok.DataAtom { | 
					
						
							|  |  |  | 		case a.Html: | 
					
						
							|  |  |  | 			return inBodyIM(p) | 
					
						
							|  |  |  | 		case a.Noframes: | 
					
						
							|  |  |  | 			return inHeadIM(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case DoctypeToken: | 
					
						
							|  |  |  | 		return inBodyIM(p) | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func ignoreTheRemainingTokens(p *parser) bool { | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | const whitespaceOrNUL = whitespace + "\x00" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6.5 | 
					
						
							|  |  |  | func parseForeignContent(p *parser) bool { | 
					
						
							|  |  |  | 	switch p.tok.Type { | 
					
						
							|  |  |  | 	case TextToken: | 
					
						
							|  |  |  | 		if p.framesetOK { | 
					
						
							|  |  |  | 			p.framesetOK = strings.TrimLeft(p.tok.Data, whitespaceOrNUL) == "" | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		p.tok.Data = strings.Replace(p.tok.Data, "\x00", "\ufffd", -1) | 
					
						
							|  |  |  | 		p.addText(p.tok.Data) | 
					
						
							|  |  |  | 	case CommentToken: | 
					
						
							|  |  |  | 		p.addChild(&Node{ | 
					
						
							|  |  |  | 			Type: CommentNode, | 
					
						
							|  |  |  | 			Data: p.tok.Data, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	case StartTagToken: | 
					
						
							|  |  |  | 		if !p.fragment { | 
					
						
							|  |  |  | 			b := breakout[p.tok.Data] | 
					
						
							|  |  |  | 			if p.tok.DataAtom == a.Font { | 
					
						
							|  |  |  | 			loop: | 
					
						
							|  |  |  | 				for _, attr := range p.tok.Attr { | 
					
						
							|  |  |  | 					switch attr.Key { | 
					
						
							|  |  |  | 					case "color", "face", "size": | 
					
						
							|  |  |  | 						b = true | 
					
						
							|  |  |  | 						break loop | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if b { | 
					
						
							|  |  |  | 				for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 					n := p.oe[i] | 
					
						
							|  |  |  | 					if n.Namespace == "" || htmlIntegrationPoint(n) || mathMLTextIntegrationPoint(n) { | 
					
						
							|  |  |  | 						p.oe = p.oe[:i+1] | 
					
						
							|  |  |  | 						break | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 				return false | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		current := p.adjustedCurrentNode() | 
					
						
							|  |  |  | 		switch current.Namespace { | 
					
						
							|  |  |  | 		case "math": | 
					
						
							|  |  |  | 			adjustAttributeNames(p.tok.Attr, mathMLAttributeAdjustments) | 
					
						
							|  |  |  | 		case "svg": | 
					
						
							|  |  |  | 			// Adjust SVG tag names. The tokenizer lower-cases tag names, but | 
					
						
							|  |  |  | 			// SVG wants e.g. "foreignObject" with a capital second "O". | 
					
						
							|  |  |  | 			if x := svgTagNameAdjustments[p.tok.Data]; x != "" { | 
					
						
							|  |  |  | 				p.tok.DataAtom = a.Lookup([]byte(x)) | 
					
						
							|  |  |  | 				p.tok.Data = x | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			adjustAttributeNames(p.tok.Attr, svgAttributeAdjustments) | 
					
						
							|  |  |  | 		default: | 
					
						
							|  |  |  | 			panic("html: bad parser state: unexpected namespace") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		adjustForeignAttributes(p.tok.Attr) | 
					
						
							|  |  |  | 		namespace := current.Namespace | 
					
						
							|  |  |  | 		p.addElement() | 
					
						
							|  |  |  | 		p.top().Namespace = namespace | 
					
						
							|  |  |  | 		if namespace != "" { | 
					
						
							|  |  |  | 			// Don't let the tokenizer go into raw text mode in foreign content | 
					
						
							|  |  |  | 			// (e.g. in an SVG <title> tag). | 
					
						
							|  |  |  | 			p.tokenizer.NextIsNotRawText() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if p.hasSelfClosingToken { | 
					
						
							|  |  |  | 			p.oe.pop() | 
					
						
							|  |  |  | 			p.acknowledgeSelfClosingTag() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	case EndTagToken: | 
					
						
							|  |  |  | 		for i := len(p.oe) - 1; i >= 0; i-- { | 
					
						
							|  |  |  | 			if p.oe[i].Namespace == "" { | 
					
						
							|  |  |  | 				return p.im(p) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if strings.EqualFold(p.oe[i].Data, p.tok.Data) { | 
					
						
							|  |  |  | 				p.oe = p.oe[:i] | 
					
						
							|  |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return true | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		// Ignore the token. | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.4.2. | 
					
						
							|  |  |  | func (p *parser) adjustedCurrentNode() *Node { | 
					
						
							|  |  |  | 	if len(p.oe) == 1 && p.fragment && p.context != nil { | 
					
						
							|  |  |  | 		return p.context | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return p.oe.top() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Section 12.2.6. | 
					
						
							|  |  |  | func (p *parser) inForeignContent() bool { | 
					
						
							|  |  |  | 	if len(p.oe) == 0 { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	n := p.adjustedCurrentNode() | 
					
						
							|  |  |  | 	if n.Namespace == "" { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if mathMLTextIntegrationPoint(n) { | 
					
						
							|  |  |  | 		if p.tok.Type == StartTagToken && p.tok.DataAtom != a.Mglyph && p.tok.DataAtom != a.Malignmark { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if p.tok.Type == TextToken { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if n.Namespace == "math" && n.DataAtom == a.AnnotationXml && p.tok.Type == StartTagToken && p.tok.DataAtom == a.Svg { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if htmlIntegrationPoint(n) && (p.tok.Type == StartTagToken || p.tok.Type == TextToken) { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if p.tok.Type == ErrorToken { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return true | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // parseImpliedToken parses a token as though it had appeared in the parser's | 
					
						
							|  |  |  | // input. | 
					
						
							|  |  |  | func (p *parser) parseImpliedToken(t TokenType, dataAtom a.Atom, data string) { | 
					
						
							|  |  |  | 	realToken, selfClosing := p.tok, p.hasSelfClosingToken | 
					
						
							|  |  |  | 	p.tok = Token{ | 
					
						
							|  |  |  | 		Type:     t, | 
					
						
							|  |  |  | 		DataAtom: dataAtom, | 
					
						
							|  |  |  | 		Data:     data, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.hasSelfClosingToken = false | 
					
						
							|  |  |  | 	p.parseCurrentToken() | 
					
						
							|  |  |  | 	p.tok, p.hasSelfClosingToken = realToken, selfClosing | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // parseCurrentToken runs the current token through the parsing routines | 
					
						
							|  |  |  | // until it is consumed. | 
					
						
							|  |  |  | func (p *parser) parseCurrentToken() { | 
					
						
							|  |  |  | 	if p.tok.Type == SelfClosingTagToken { | 
					
						
							|  |  |  | 		p.hasSelfClosingToken = true | 
					
						
							|  |  |  | 		p.tok.Type = StartTagToken | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	consumed := false | 
					
						
							|  |  |  | 	for !consumed { | 
					
						
							|  |  |  | 		if p.inForeignContent() { | 
					
						
							|  |  |  | 			consumed = parseForeignContent(p) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			consumed = p.im(p) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if p.hasSelfClosingToken { | 
					
						
							|  |  |  | 		// This is a parse error, but ignore it. | 
					
						
							|  |  |  | 		p.hasSelfClosingToken = false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *parser) parse() error { | 
					
						
							|  |  |  | 	// Iterate until EOF. Any other error will cause an early return. | 
					
						
							|  |  |  | 	var err error | 
					
						
							|  |  |  | 	for err != io.EOF { | 
					
						
							|  |  |  | 		// CDATA sections are allowed only in foreign content. | 
					
						
							|  |  |  | 		n := p.oe.top() | 
					
						
							|  |  |  | 		p.tokenizer.AllowCDATA(n != nil && n.Namespace != "") | 
					
						
							|  |  |  | 		// Read and parse the next token. | 
					
						
							|  |  |  | 		p.tokenizer.Next() | 
					
						
							|  |  |  | 		p.tok = p.tokenizer.Token() | 
					
						
							|  |  |  | 		if p.tok.Type == ErrorToken { | 
					
						
							|  |  |  | 			err = p.tokenizer.Err() | 
					
						
							|  |  |  | 			if err != nil && err != io.EOF { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		p.parseCurrentToken() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Parse returns the parse tree for the HTML from the given Reader. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It implements the HTML5 parsing algorithm | 
					
						
							|  |  |  | // (https://html.spec.whatwg.org/multipage/syntax.html#tree-construction), | 
					
						
							|  |  |  | // which is very complicated. The resultant tree can contain implicitly created | 
					
						
							|  |  |  | // nodes that have no explicit <tag> listed in r's data, and nodes' parents can | 
					
						
							|  |  |  | // differ from the nesting implied by a naive processing of start and end | 
					
						
							|  |  |  | // <tag>s. Conversely, explicit <tag>s in r's data can be silently dropped, | 
					
						
							|  |  |  | // with no corresponding node in the resulting tree. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // The input is assumed to be UTF-8 encoded. | 
					
						
							|  |  |  | func Parse(r io.Reader) (*Node, error) { | 
					
						
							|  |  |  | 	return ParseWithOptions(r) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ParseFragment parses a fragment of HTML and returns the nodes that were | 
					
						
							|  |  |  | // found. If the fragment is the InnerHTML for an existing element, pass that | 
					
						
							|  |  |  | // element in context. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It has the same intricacies as Parse. | 
					
						
							|  |  |  | func ParseFragment(r io.Reader, context *Node) ([]*Node, error) { | 
					
						
							|  |  |  | 	return ParseFragmentWithOptions(r, context) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ParseOption configures a parser. | 
					
						
							|  |  |  | type ParseOption func(p *parser) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ParseOptionEnableScripting configures the scripting flag. | 
					
						
							|  |  |  | // https://html.spec.whatwg.org/multipage/webappapis.html#enabling-and-disabling-scripting | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // By default, scripting is enabled. | 
					
						
							|  |  |  | func ParseOptionEnableScripting(enable bool) ParseOption { | 
					
						
							|  |  |  | 	return func(p *parser) { | 
					
						
							|  |  |  | 		p.scripting = enable | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ParseWithOptions is like Parse, with options. | 
					
						
							|  |  |  | func ParseWithOptions(r io.Reader, opts ...ParseOption) (*Node, error) { | 
					
						
							|  |  |  | 	p := &parser{ | 
					
						
							|  |  |  | 		tokenizer: NewTokenizer(r), | 
					
						
							|  |  |  | 		doc: &Node{ | 
					
						
							|  |  |  | 			Type: DocumentNode, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		scripting:  true, | 
					
						
							|  |  |  | 		framesetOK: true, | 
					
						
							|  |  |  | 		im:         initialIM, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, f := range opts { | 
					
						
							|  |  |  | 		f(p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := p.parse(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return p.doc, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ParseFragmentWithOptions is like ParseFragment, with options. | 
					
						
							|  |  |  | func ParseFragmentWithOptions(r io.Reader, context *Node, opts ...ParseOption) ([]*Node, error) { | 
					
						
							|  |  |  | 	contextTag := "" | 
					
						
							|  |  |  | 	if context != nil { | 
					
						
							|  |  |  | 		if context.Type != ElementNode { | 
					
						
							|  |  |  | 			return nil, errors.New("html: ParseFragment of non-element Node") | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		// The next check isn't just context.DataAtom.String() == context.Data because | 
					
						
							|  |  |  | 		// it is valid to pass an element whose tag isn't a known atom. For example, | 
					
						
							|  |  |  | 		// DataAtom == 0 and Data = "tagfromthefuture" is perfectly consistent. | 
					
						
							|  |  |  | 		if context.DataAtom != a.Lookup([]byte(context.Data)) { | 
					
						
							|  |  |  | 			return nil, fmt.Errorf("html: inconsistent Node: DataAtom=%q, Data=%q", context.DataAtom, context.Data) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		contextTag = context.DataAtom.String() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p := &parser{ | 
					
						
							|  |  |  | 		doc: &Node{ | 
					
						
							|  |  |  | 			Type: DocumentNode, | 
					
						
							|  |  |  | 		}, | 
					
						
							|  |  |  | 		scripting: true, | 
					
						
							|  |  |  | 		fragment:  true, | 
					
						
							|  |  |  | 		context:   context, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if context != nil && context.Namespace != "" { | 
					
						
							|  |  |  | 		p.tokenizer = NewTokenizer(r) | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		p.tokenizer = NewTokenizerFragment(r, contextTag) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, f := range opts { | 
					
						
							|  |  |  | 		f(p) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	root := &Node{ | 
					
						
							|  |  |  | 		Type:     ElementNode, | 
					
						
							|  |  |  | 		DataAtom: a.Html, | 
					
						
							|  |  |  | 		Data:     a.Html.String(), | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.doc.AppendChild(root) | 
					
						
							|  |  |  | 	p.oe = nodeStack{root} | 
					
						
							|  |  |  | 	if context != nil && context.DataAtom == a.Template { | 
					
						
							|  |  |  | 		p.templateStack = append(p.templateStack, inTemplateIM) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	p.resetInsertionMode() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for n := context; n != nil; n = n.Parent { | 
					
						
							|  |  |  | 		if n.Type == ElementNode && n.DataAtom == a.Form { | 
					
						
							|  |  |  | 			p.form = n | 
					
						
							|  |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := p.parse(); err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	parent := p.doc | 
					
						
							|  |  |  | 	if context != nil { | 
					
						
							|  |  |  | 		parent = root | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	var result []*Node | 
					
						
							|  |  |  | 	for c := parent.FirstChild; c != nil; { | 
					
						
							|  |  |  | 		next := c.NextSibling | 
					
						
							|  |  |  | 		parent.RemoveChild(c) | 
					
						
							|  |  |  | 		result = append(result, c) | 
					
						
							|  |  |  | 		c = next | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return result, nil | 
					
						
							|  |  |  | } |