mirror of
https://github.com/PuerkitoBio/goquery.git
synced 2024-04-21 12:31:36 +00:00
manipulation: Add Append*() functions
This commit is contained in:
+132
@@ -0,0 +1,132 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/cascadia"
|
||||
"code.google.com/p/go.net/html"
|
||||
)
|
||||
|
||||
func parseHtml(html string) *Selection {
|
||||
// Errors are only returned when the io.Reader returns any error besides
|
||||
// EOF, but strings.Reader never will
|
||||
doc, err := NewDocumentFromReader(strings.NewReader(html))
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not parse HTML: %s", err))
|
||||
}
|
||||
return doc.Find("body").Children()
|
||||
}
|
||||
|
||||
// Deep copy a slice of nodes.
|
||||
func cloneNodes(ns []*html.Node) []*html.Node {
|
||||
cns := make([]*html.Node, 0, len(ns))
|
||||
|
||||
for _, n := range ns {
|
||||
cns = append(cns, cloneNode(n))
|
||||
}
|
||||
|
||||
return cns
|
||||
}
|
||||
|
||||
// Deep copy a node. The new node has clones of all the original node's
|
||||
// children but none of its parents or siblings.
|
||||
func cloneNode(n *html.Node) *html.Node {
|
||||
nn := &html.Node{
|
||||
Type: n.Type,
|
||||
DataAtom: n.DataAtom,
|
||||
Data: n.Data,
|
||||
Attr: make([]html.Attribute, len(n.Attr)),
|
||||
}
|
||||
|
||||
copy(nn.Attr, n.Attr)
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
nn.AppendChild(cloneNode(c))
|
||||
}
|
||||
|
||||
return nn
|
||||
}
|
||||
|
||||
func (s *Selection) manipulateNodes(
|
||||
ns []*html.Node,
|
||||
reverse bool,
|
||||
f func(sn *html.Node, n *html.Node)) *Selection {
|
||||
|
||||
lasti := s.Size() - 1
|
||||
|
||||
// net.Html doesn't provide document fragments for insertion, so to get
|
||||
// things in the correct order with After() and Prepend(), the callback
|
||||
// needs to be called on the reverse of the nodes.
|
||||
if reverse {
|
||||
for i, j := 0, len(ns)-1; i < j; i, j = i+1, j-1 {
|
||||
ns[i], ns[j] = ns[j], ns[i]
|
||||
}
|
||||
}
|
||||
|
||||
for i, sn := range s.Nodes {
|
||||
for _, n := range ns {
|
||||
if i != lasti {
|
||||
f(sn, cloneNode(n))
|
||||
} else {
|
||||
if n.Parent != nil {
|
||||
n.Parent.RemoveChild(n)
|
||||
}
|
||||
f(sn, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Append the elements, specified by the selector, to the end of each element
|
||||
// in the set of matched elements.
|
||||
//
|
||||
// Take note:
|
||||
//
|
||||
// 1) The selector is applied to the root document.
|
||||
//
|
||||
// 2) If any elements specified in the parameter are still part of the
|
||||
// document, they will be moved to the new location.
|
||||
//
|
||||
// 3) If there are multiple locations to append to, cloned nodes will be
|
||||
// appended to all target locations except the last, which will be moved
|
||||
// as noted in (1).
|
||||
func (s *Selection) Append(selector string) *Selection {
|
||||
return s.AppendSelector(cascadia.MustCompile(selector))
|
||||
}
|
||||
|
||||
// From the root document, apply the cascadia selector, and append those nodes
|
||||
// to the set of matched elements.
|
||||
// This follows the same rules as Selection.Append().
|
||||
func (s *Selection) AppendSelector(cs cascadia.Selector) *Selection {
|
||||
return s.AppendNodes(cs.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// Append the elements in the selection to the end of each element in the
|
||||
// set of matched elements.
|
||||
// This follows the same rules as Selection.Append().
|
||||
func (s *Selection) AppendSelection(sel *Selection) *Selection {
|
||||
return s.AppendNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// Parse the html and append it to the set of matched elements
|
||||
func (s *Selection) AppendHtml(html string) *Selection {
|
||||
return s.AppendSelection(parseHtml(html))
|
||||
}
|
||||
|
||||
// Append the specified nodes to each node in the set of matched elements.
|
||||
// This follows the same rules as Selection.Append().
|
||||
func (s *Selection) AppendNodes(ns ...*html.Node) *Selection {
|
||||
return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) {
|
||||
sn.AppendChild(n)
|
||||
})
|
||||
}
|
||||
|
||||
// Create a deep copy of the set of matched nodes. The new nodes will not be
|
||||
// attached to the document.
|
||||
func (s *Selection) Clone() *Selection {
|
||||
ns := newEmptySelection(s.document)
|
||||
ns.Nodes = cloneNodes(s.Nodes)
|
||||
return ns
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").Append("#nf6")
|
||||
|
||||
AssertLength(t, doc.Find("#foot #nf6").Nodes, 0)
|
||||
AssertLength(t, doc.Find("#main #nf6").Nodes, 1)
|
||||
}
|
||||
|
||||
func TestAppendBody(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("body").Append("#nf6")
|
||||
|
||||
AssertLength(t, doc.Find("#foot #nf6").Nodes, 0)
|
||||
AssertLength(t, doc.Find("#main #nf6").Nodes, 0)
|
||||
AssertLength(t, doc.Find("body > #nf6").Nodes, 1)
|
||||
}
|
||||
|
||||
func TestAppendSelection(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").AppendSelection(doc.Find("#nf1, #nf2"))
|
||||
|
||||
AssertLength(t, doc.Find("#foot #nf1").Nodes, 0)
|
||||
AssertLength(t, doc.Find("#foot #nf2").Nodes, 0)
|
||||
AssertLength(t, doc.Find("#main #nf1").Nodes, 1)
|
||||
AssertLength(t, doc.Find("#main #nf2").Nodes, 1)
|
||||
}
|
||||
|
||||
func TestAppendClone(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#n1").AppendSelection(doc.Find("#nf1").Clone())
|
||||
|
||||
AssertLength(t, doc.Find("#foot #nf1").Nodes, 1)
|
||||
AssertLength(t, doc.Find("#main #nf1").Nodes, 1)
|
||||
}
|
||||
|
||||
func TestAppendHtml(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("div").AppendHtml("<strong>new node</strong>")
|
||||
|
||||
AssertLength(t, doc.Find("strong").Nodes, 14)
|
||||
}
|
||||
@@ -70,6 +70,11 @@ func NewDocumentFromResponse(res *http.Response) (*Document, error) {
|
||||
return newDocument(root, res.Request.URL), nil
|
||||
}
|
||||
|
||||
// Create a deep-clone of a document
|
||||
func NewDocumentFromDocument(doc *Document) *Document {
|
||||
return newDocument(cloneNode(doc.rootNode), doc.Url)
|
||||
}
|
||||
|
||||
// Private constructor, make sure all fields are correctly filled.
|
||||
func newDocument(root *html.Node, url *url.URL) *Document {
|
||||
// Create and fill the document
|
||||
|
||||
@@ -21,24 +21,36 @@ func Doc() *Document {
|
||||
}
|
||||
return doc
|
||||
}
|
||||
func DocClone() *Document {
|
||||
return NewDocumentFromDocument(Doc())
|
||||
}
|
||||
func Doc2() *Document {
|
||||
if doc2 == nil {
|
||||
doc2 = LoadDoc("page2.html")
|
||||
}
|
||||
return doc2
|
||||
}
|
||||
func Doc2Clone() *Document {
|
||||
return NewDocumentFromDocument(Doc2())
|
||||
}
|
||||
func DocB() *Document {
|
||||
if docB == nil {
|
||||
docB = LoadDoc("gotesting.html")
|
||||
}
|
||||
return docB
|
||||
}
|
||||
func DocBClone() *Document {
|
||||
return NewDocumentFromDocument(DocB())
|
||||
}
|
||||
func DocW() *Document {
|
||||
if docW == nil {
|
||||
docW = LoadDoc("gowiki.html")
|
||||
}
|
||||
return docW
|
||||
}
|
||||
func DocWClone() *Document {
|
||||
return NewDocumentFromDocument(DocW())
|
||||
}
|
||||
|
||||
func AssertLength(t *testing.T, nodes []*html.Node, length int) {
|
||||
if len(nodes) != length {
|
||||
|
||||
Reference in New Issue
Block a user