diff --git a/manipulation.go b/manipulation.go index b7f2fe5..257f207 100644 --- a/manipulation.go +++ b/manipulation.go @@ -39,8 +39,14 @@ func (s *Selection) AfterSelection(sel *Selection) *Selection { // AfterHtml parses the html and inserts it after the set of matched elements. // // This follows the same rules as Selection.Append. -func (s *Selection) AfterHtml(html string) *Selection { - return s.AfterNodes(parseHtml(html)...) +func (s *Selection) AfterHtml(htmlStr string) *Selection { + return s.eachNodeHtml(htmlStr, true, func(node *html.Node, nodes []*html.Node) { + for _, n := range nodes { + if node.Parent != nil { + node.Parent.InsertBefore(n, node.NextSibling) + } + } + }) } // AfterNodes inserts the nodes after each element in the set of matched elements. @@ -85,8 +91,12 @@ func (s *Selection) AppendSelection(sel *Selection) *Selection { } // AppendHtml parses the html and appends it to the set of matched elements. -func (s *Selection) AppendHtml(html string) *Selection { - return s.AppendNodes(parseHtml(html)...) +func (s *Selection) AppendHtml(htmlStr string) *Selection { + return s.eachNodeHtml(htmlStr, false, func(node *html.Node, nodes []*html.Node) { + for _, n := range nodes { + node.AppendChild(n) + } + }) } // AppendNodes appends the specified nodes to each node in the set of matched elements. @@ -123,8 +133,14 @@ func (s *Selection) BeforeSelection(sel *Selection) *Selection { // BeforeHtml parses the html and inserts it before the set of matched elements. // // This follows the same rules as Selection.Append. -func (s *Selection) BeforeHtml(html string) *Selection { - return s.BeforeNodes(parseHtml(html)...) +func (s *Selection) BeforeHtml(htmlStr string) *Selection { + return s.eachNodeHtml(htmlStr, true, func(node *html.Node, nodes []*html.Node) { + for _, n := range nodes { + if node.Parent != nil { + node.Parent.InsertBefore(n, node) + } + } + }) } // BeforeNodes inserts the nodes before each element in the set of matched elements. @@ -184,8 +200,12 @@ func (s *Selection) PrependSelection(sel *Selection) *Selection { } // PrependHtml parses the html and prepends it to the set of matched elements. -func (s *Selection) PrependHtml(html string) *Selection { - return s.PrependNodes(parseHtml(html)...) +func (s *Selection) PrependHtml(htmlStr string) *Selection { + return s.eachNodeHtml(htmlStr, false, func(node *html.Node, nodes []*html.Node) { + for _, n := range nodes { + node.InsertBefore(n, node.FirstChild) + } + }) } // PrependNodes prepends the specified nodes to each node in the set of @@ -261,8 +281,15 @@ func (s *Selection) ReplaceWithSelection(sel *Selection) *Selection { // It returns the removed elements. // // This follows the same rules as Selection.Append. -func (s *Selection) ReplaceWithHtml(html string) *Selection { - return s.ReplaceWithNodes(parseHtml(html)...) +func (s *Selection) ReplaceWithHtml(htmlStr string) *Selection { + s.eachNodeHtml(htmlStr, true, func(node *html.Node, nodes []*html.Node) { + for _, n := range nodes { + if node.Parent != nil { + node.Parent.InsertBefore(n, node.NextSibling) + } + } + }) + return s.Remove() } // ReplaceWithNodes replaces each element in the set of matched elements with @@ -277,8 +304,17 @@ func (s *Selection) ReplaceWithNodes(ns ...*html.Node) *Selection { // SetHtml sets the html content of each element in the selection to // specified html string. -func (s *Selection) SetHtml(html string) *Selection { - return setHtmlNodes(s, parseHtml(html)...) +func (s *Selection) SetHtml(htmlStr string) *Selection { + for _, context := range s.Nodes { + for c := context.FirstChild; c != nil; c = context.FirstChild { + context.RemoveChild(c) + } + } + return s.eachNodeHtml(htmlStr, true, func(node *html.Node, nodes []*html.Node) { + for _, n := range nodes { + node.AppendChild(n) + } + }) } // SetText sets the content of each element in the selection to specified content. @@ -334,8 +370,23 @@ func (s *Selection) WrapSelection(sel *Selection) *Selection { // most child of the given HTML. // // It returns the original set of elements. -func (s *Selection) WrapHtml(html string) *Selection { - return s.wrapNodes(parseHtml(html)...) +func (s *Selection) WrapHtml(htmlStr string) *Selection { + nodesMap := make(map[html.NodeType][]*html.Node) + var parent *html.Node + for _, context := range s.Nodes { + if context.Parent != nil { + parent = context.Parent + } else { + parent = &html.Node{Type: html.ElementNode} + } + nodes, found := nodesMap[parent.Type] + if !found { + nodes = parseHtmlWithContext(htmlStr, parent) + nodesMap[parent.Type] = nodes + } + newSingleSelection(context, s.document).wrapAllNodes(cloneNodes(nodes)...) + } + return s } // WrapNode wraps each element in the set of matched elements inside the inner- @@ -387,8 +438,18 @@ func (s *Selection) WrapAllSelection(sel *Selection) *Selection { // document. // // It returns the original set of elements. -func (s *Selection) WrapAllHtml(html string) *Selection { - return s.wrapAllNodes(parseHtml(html)...) +func (s *Selection) WrapAllHtml(htmlStr string) *Selection { + var context *html.Node + var nodes []*html.Node + if len(s.Nodes) > 0 { + context = s.Nodes[0] + if context.Parent != nil { + nodes = parseHtmlWithContext(htmlStr, context) + } else { + nodes = parseHtml(htmlStr) + } + } + return s.wrapAllNodes(nodes...) } func (s *Selection) wrapAllNodes(ns ...*html.Node) *Selection { @@ -457,8 +518,17 @@ func (s *Selection) WrapInnerSelection(sel *Selection) *Selection { // cloned before being inserted into the document. // // It returns the original set of elements. -func (s *Selection) WrapInnerHtml(html string) *Selection { - return s.wrapInnerNodes(parseHtml(html)...) +func (s *Selection) WrapInnerHtml(htmlStr string) *Selection { + nodesMap := make(map[html.NodeType][]*html.Node) + for _, context := range s.Nodes { + nodes, found := nodesMap[context.Type] + if !found { + nodes = parseHtmlWithContext(htmlStr, context) + nodesMap[context.Type] = nodes + } + newSingleSelection(context, s.document).wrapInnerNodes(cloneNodes(nodes)...) + } + return s } // WrapInnerNode wraps an HTML structure, matched by the given selector, around @@ -498,6 +568,16 @@ func parseHtml(h string) []*html.Node { return nodes } +func parseHtmlWithContext(h string, context *html.Node) []*html.Node { + // Errors are only returned when the io.Reader returns any error besides + // EOF, but strings.Reader never will + nodes, err := html.ParseFragment(strings.NewReader(h), context) + if err != nil { + panic("goquery: failed to parse HTML: " + err.Error()) + } + return nodes +} + func setHtmlNodes(s *Selection, ns ...*html.Node) *Selection { for _, n := range s.Nodes { for c := n.FirstChild; c != nil; c = n.FirstChild { @@ -577,3 +657,24 @@ func (s *Selection) manipulateNodes(ns []*html.Node, reverse bool, return s } + +func (s *Selection) eachNodeHtml(htmlStr string, parent bool, fn func(n *html.Node, nodes []*html.Node)) *Selection { + nodesMap := make(map[html.NodeType][]*html.Node) + var context *html.Node + for _, n := range s.Nodes { + if parent { + context = n.Parent + } else { + context = n + } + if context != nil { + nodes, found := nodesMap[context.Type] + if !found { + nodes = parseHtmlWithContext(htmlStr, context) + nodesMap[context.Type] = nodes + } + fn(n, cloneNodes(nodes)) + } + } + return s +} diff --git a/manipulation_test.go b/manipulation_test.go index c5f5022..fcf0d1f 100644 --- a/manipulation_test.go +++ b/manipulation_test.go @@ -56,6 +56,13 @@ func TestAfterHtml(t *testing.T) { printSel(t, doc.Selection) } +func TestAfterHtmlContext(t *testing.T) { + doc := DocBClone() + doc.Find("table tr td").AfterHtml("Test") + assertLength(t, doc.Find("table tr td").Nodes, 14) + printSel(t, doc.Selection) +} + func TestAppend(t *testing.T) { doc := Doc2Clone() doc.Find("#main").Append("#nf6") @@ -113,6 +120,15 @@ func TestAppendHtml(t *testing.T) { printSel(t, doc.Selection) } +func TestAppendHtmlContext(t *testing.T) { + doc := DocBClone() + doc.Find("table tr").AppendHtml("new node") + + assertLength(t, doc.Find("table td").Nodes, 11) + assertLength(t, doc.Find("table tr td.new-node:last-child").Nodes, 4) + printSel(t, doc.Selection) +} + func TestBefore(t *testing.T) { doc := Doc2Clone() doc.Find("#main").Before("#nf6") @@ -151,6 +167,14 @@ func TestBeforeHtml(t *testing.T) { printSel(t, doc.Selection) } +func TestBeforeHtmlContext(t *testing.T) { + doc := DocBClone() + doc.Find("table tr td:first-child").BeforeHtml("new node") + + assertLength(t, doc.Find("table td.new-node:first-child").Nodes, 3) + printSel(t, doc.Selection) +} + func TestEmpty(t *testing.T) { doc := Doc2Clone() s := doc.Find("#main").Empty() @@ -218,6 +242,15 @@ func TestPrependHtml(t *testing.T) { printSel(t, doc.Selection) } +func TestPrependHtmlContext(t *testing.T) { + doc := DocBClone() + doc.Find("table tr").PrependHtml("new node") + + assertLength(t, doc.Find("table td").Nodes, 11) + assertLength(t, doc.Find("table tr td.new-node:first-child").Nodes, 4) + printSel(t, doc.Selection) +} + func TestRemove(t *testing.T) { doc := Doc2Clone() doc.Find("#nf1").Remove() @@ -278,6 +311,15 @@ func TestReplaceWithHtml(t *testing.T) { printSel(t, doc.Selection) } +func TestReplaceWithHtmlContext(t *testing.T) { + doc := DocBClone() + doc.Find("table th").ReplaceWithHtml("TestReplace") + + assertLength(t, doc.Find("table th").Nodes, 0) + assertLength(t, doc.Find("table tr:first-child td").Nodes, 6) + printSel(t, doc.Selection) +} + func TestSetHtml(t *testing.T) { doc := Doc2Clone() q := doc.Find("#main, #foot") @@ -313,6 +355,15 @@ func TestSetHtmlEmpty(t *testing.T) { printSel(t, doc.Selection) } +func TestSetHtmlContext(t *testing.T) { + doc := DocBClone() + doc.Find("table tr").SetHtml("Test") + + assertLength(t, doc.Find("table th").Nodes, 0) + assertLength(t, doc.Find("table td.new-node").Nodes, 4) + printSel(t, doc.Selection) +} + func TestSetText(t *testing.T) { doc := Doc2Clone() q := doc.Find("#main, #foot") diff --git a/type_test.go b/type_test.go index 1e82d5e..d263163 100644 --- a/type_test.go +++ b/type_test.go @@ -53,6 +53,10 @@ func DocB() *Document { return docB } +func DocBClone() *Document { + return CloneDocument(DocB()) +} + func DocW() *Document { if docW == nil { docW = loadDoc("gowiki.html")