Apply the document's parse options to HTML added to it later
This commit is contained in:
1 parent
bff34b529a
commit
52ce458d92
3 files changed
+53
-15
No files matched your search
+20
-11
@@ -388,7 +388,7 @@ func (s *Selection) WrapHtml(htmlStr string) *Selection {
|
|||||||
if parent == nil {
|
if parent == nil {
|
||||||
parent = &html.Node{Type: html.ElementNode}
|
parent = &html.Node{Type: html.ElementNode}
|
||||||
}
|
}
|
||||||
nodes := cachedParseHtmlWithContext(cache, htmlStr, parent)
|
nodes := cachedParseHtmlWithContext(cache, htmlStr, parent, s.parseOpts())
|
||||||
newSingleSelection(n, s.document).wrapAllNodes(cloneNodes(nodes)...)
|
newSingleSelection(n, s.document).wrapAllNodes(cloneNodes(nodes)...)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
@@ -449,9 +449,9 @@ func (s *Selection) WrapAllHtml(htmlStr string) *Selection {
|
|||||||
if len(s.Nodes) > 0 {
|
if len(s.Nodes) > 0 {
|
||||||
context = s.Nodes[0]
|
context = s.Nodes[0]
|
||||||
if context.Parent != nil {
|
if context.Parent != nil {
|
||||||
nodes = parseHtmlWithContext(htmlStr, context)
|
nodes = parseHtmlWithContext(htmlStr, context, s.parseOpts())
|
||||||
} else {
|
} else {
|
||||||
nodes = parseHtml(htmlStr)
|
nodes = parseHtml(htmlStr, s.parseOpts())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return s.wrapAllNodes(nodes...)
|
return s.wrapAllNodes(nodes...)
|
||||||
@@ -526,7 +526,7 @@ func (s *Selection) WrapInnerSelection(sel *Selection) *Selection {
|
|||||||
func (s *Selection) WrapInnerHtml(htmlStr string) *Selection {
|
func (s *Selection) WrapInnerHtml(htmlStr string) *Selection {
|
||||||
cache := make(map[string][]*html.Node)
|
cache := make(map[string][]*html.Node)
|
||||||
for _, n := range s.Nodes {
|
for _, n := range s.Nodes {
|
||||||
nodes := cachedParseHtmlWithContext(cache, htmlStr, n)
|
nodes := cachedParseHtmlWithContext(cache, htmlStr, n, s.parseOpts())
|
||||||
newSingleSelection(n, s.document).wrapInnerNodes(cloneNodes(nodes)...)
|
newSingleSelection(n, s.document).wrapInnerNodes(cloneNodes(nodes)...)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
@@ -559,14 +559,23 @@ func (s *Selection) wrapInnerNodes(ns ...*html.Node) *Selection {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHtml(h string) []*html.Node {
|
// parseOpts returns the parse options of the document this selection belongs to,
|
||||||
return parseHtmlWithContext(h, &html.Node{Type: html.ElementNode})
|
// so an HTML string added later is parsed the same way the document was.
|
||||||
|
func (s *Selection) parseOpts() []html.ParseOption {
|
||||||
|
if s == nil || s.document == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return s.document.parseOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseHtmlWithContext(h string, context *html.Node) []*html.Node {
|
func parseHtml(h string, opts []html.ParseOption) []*html.Node {
|
||||||
|
return parseHtmlWithContext(h, &html.Node{Type: html.ElementNode}, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseHtmlWithContext(h string, context *html.Node, opts []html.ParseOption) []*html.Node {
|
||||||
// Errors are only returned when the io.Reader returns any error besides
|
// Errors are only returned when the io.Reader returns any error besides
|
||||||
// EOF, but strings.Reader never will
|
// EOF, but strings.Reader never will
|
||||||
nodes, err := html.ParseFragment(strings.NewReader(h), context)
|
nodes, err := html.ParseFragmentWithOptions(strings.NewReader(h), context, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("goquery: failed to parse HTML: " + err.Error())
|
panic("goquery: failed to parse HTML: " + err.Error())
|
||||||
}
|
}
|
||||||
@@ -660,7 +669,7 @@ func (s *Selection) eachNodeHtml(htmlStr string, isParent bool, mergeFn func(n *
|
|||||||
if context == nil {
|
if context == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
nodes := cachedParseHtmlWithContext(cache, htmlStr, context)
|
nodes := cachedParseHtmlWithContext(cache, htmlStr, context, s.parseOpts())
|
||||||
mergeFn(n, cloneNodes(nodes))
|
mergeFn(n, cloneNodes(nodes))
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
@@ -669,12 +678,12 @@ func (s *Selection) eachNodeHtml(htmlStr string, isParent bool, mergeFn func(n *
|
|||||||
// cachedParseHtmlWithContext returns parseHtmlWithContext(htmlStr, context), reusing a prior
|
// cachedParseHtmlWithContext returns parseHtmlWithContext(htmlStr, context), reusing a prior
|
||||||
// result when context's nodeName has already been seen. Callers pass their own
|
// result when context's nodeName has already been seen. Callers pass their own
|
||||||
// cache map so the cache lifetime matches the caller's loop scope.
|
// cache map so the cache lifetime matches the caller's loop scope.
|
||||||
func cachedParseHtmlWithContext(cache map[string][]*html.Node, htmlStr string, context *html.Node) []*html.Node {
|
func cachedParseHtmlWithContext(cache map[string][]*html.Node, htmlStr string, context *html.Node, opts []html.ParseOption) []*html.Node {
|
||||||
key := nodeName(context)
|
key := nodeName(context)
|
||||||
if nodes, ok := cache[key]; ok {
|
if nodes, ok := cache[key]; ok {
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
nodes := parseHtmlWithContext(htmlStr, context)
|
nodes := parseHtmlWithContext(htmlStr, context, opts)
|
||||||
cache[key] = nodes
|
cache[key] = nodes
|
||||||
return nodes
|
return nodes
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,9 @@ type Document struct {
|
|||||||
*Selection
|
*Selection
|
||||||
Url *url.URL
|
Url *url.URL
|
||||||
rootNode *html.Node
|
rootNode *html.Node
|
||||||
|
// parse options this document was built with, reused whenever an HTML
|
||||||
|
// string is parsed into it later on
|
||||||
|
parseOptions []html.ParseOption
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDocumentFromNode is a Document constructor that takes a root html Node
|
// NewDocumentFromNode is a Document constructor that takes a root html Node
|
||||||
@@ -71,7 +74,7 @@ func NewDocumentFromReaderWithOptions(r io.Reader, opts ...html.ParseOption) (*D
|
|||||||
if e != nil {
|
if e != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
return newDocument(root, nil), nil
|
return newDocument(root, nil, opts...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDocumentFromResponse is another Document constructor that takes an http response as argument.
|
// NewDocumentFromResponse is another Document constructor that takes an http response as argument.
|
||||||
@@ -100,13 +103,13 @@ func NewDocumentFromResponse(res *http.Response) (*Document, error) {
|
|||||||
|
|
||||||
// CloneDocument creates a deep-clone of a document.
|
// CloneDocument creates a deep-clone of a document.
|
||||||
func CloneDocument(doc *Document) *Document {
|
func CloneDocument(doc *Document) *Document {
|
||||||
return newDocument(cloneNode(doc.rootNode), doc.Url)
|
return newDocument(cloneNode(doc.rootNode), doc.Url, doc.parseOptions...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private constructor, make sure all fields are correctly filled.
|
// Private constructor, make sure all fields are correctly filled.
|
||||||
func newDocument(root *html.Node, url *url.URL) *Document {
|
func newDocument(root *html.Node, url *url.URL, opts ...html.ParseOption) *Document {
|
||||||
// Create and fill the document
|
// Create and fill the document
|
||||||
d := &Document{nil, url, root}
|
d := &Document{Url: url, rootNode: root, parseOptions: opts}
|
||||||
d.Selection = newSingleSelection(root, d)
|
d.Selection = newSingleSelection(root, d)
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,3 +290,29 @@ func TestNewDocumentFromReaderWithOptionsNoOptions(t *testing.T) {
|
|||||||
t.Errorf("got %q, want %q", got, "hi")
|
t.Errorf("got %q, want %q", got, "hi")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewDocumentFromReaderWithOptionsAppliesToAddedHtml(t *testing.T) {
|
||||||
|
const doc = `<html><body><p>before</p></body></html>`
|
||||||
|
const frag = `<noscript><a href="http://example.org">click this link</a></noscript>`
|
||||||
|
|
||||||
|
// The options given to the constructor hold for HTML added later, so the
|
||||||
|
// fragment is parsed the same way the document was.
|
||||||
|
d, e := NewDocumentFromReaderWithOptions(strings.NewReader(doc), html.ParseOptionEnableScripting(false))
|
||||||
|
if e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
d.Find("body").AppendHtml(frag)
|
||||||
|
if _, ok := d.Find("noscript a").Attr("href"); !ok {
|
||||||
|
t.Error("expected the added noscript to be parsed with scripting disabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Without the options the added fragment keeps the default behaviour.
|
||||||
|
d, e = NewDocumentFromReader(strings.NewReader(doc))
|
||||||
|
if e != nil {
|
||||||
|
t.Fatal(e)
|
||||||
|
}
|
||||||
|
d.Find("body").AppendHtml(frag)
|
||||||
|
if _, ok := d.Find("noscript a").Attr("href"); ok {
|
||||||
|
t.Error("expected no match inside the added noscript with scripting enabled")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user