mirror of
https://github.com/PuerkitoBio/goquery.git
synced 2024-04-21 12:31:36 +00:00
Merge pull request #348 from thiemok/context
Send context to parseHtml. Closes #178 .
This commit is contained in:
+124
-27
@@ -39,8 +39,15 @@ 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) {
|
||||
nextSibling := node.NextSibling
|
||||
for _, n := range nodes {
|
||||
if node.Parent != nil {
|
||||
node.Parent.InsertBefore(n, nextSibling)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// AfterNodes inserts the nodes after each element in the set of matched elements.
|
||||
@@ -85,8 +92,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 +134,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 +201,13 @@ 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) {
|
||||
firstChild := node.FirstChild
|
||||
for _, n := range nodes {
|
||||
node.InsertBefore(n, firstChild)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// PrependNodes prepends the specified nodes to each node in the set of
|
||||
@@ -261,8 +283,16 @@ 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) {
|
||||
nextSibling := node.NextSibling
|
||||
for _, n := range nodes {
|
||||
if node.Parent != nil {
|
||||
node.Parent.InsertBefore(n, nextSibling)
|
||||
}
|
||||
}
|
||||
})
|
||||
return s.Remove()
|
||||
}
|
||||
|
||||
// ReplaceWithNodes replaces each element in the set of matched elements with
|
||||
@@ -277,8 +307,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, false, 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 +373,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[string][]*html.Node)
|
||||
for _, context := range s.Nodes {
|
||||
var parent *html.Node
|
||||
if context.Parent != nil {
|
||||
parent = context.Parent
|
||||
} else {
|
||||
parent = &html.Node{Type: html.ElementNode}
|
||||
}
|
||||
nodes, found := nodesMap[nodeName(parent)]
|
||||
if !found {
|
||||
nodes = parseHtmlWithContext(htmlStr, parent)
|
||||
nodesMap[nodeName(parent)] = 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 +441,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 +521,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[string][]*html.Node)
|
||||
for _, context := range s.Nodes {
|
||||
nodes, found := nodesMap[nodeName(context)]
|
||||
if !found {
|
||||
nodes = parseHtmlWithContext(htmlStr, context)
|
||||
nodesMap[nodeName(context)] = nodes
|
||||
}
|
||||
newSingleSelection(context, s.document).wrapInnerNodes(cloneNodes(nodes)...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// WrapInnerNode wraps an HTML structure, matched by the given selector, around
|
||||
@@ -498,16 +571,14 @@ func parseHtml(h string) []*html.Node {
|
||||
return nodes
|
||||
}
|
||||
|
||||
func setHtmlNodes(s *Selection, ns ...*html.Node) *Selection {
|
||||
for _, n := range s.Nodes {
|
||||
for c := n.FirstChild; c != nil; c = n.FirstChild {
|
||||
n.RemoveChild(c)
|
||||
}
|
||||
for _, c := range ns {
|
||||
n.AppendChild(cloneNode(c))
|
||||
}
|
||||
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 s
|
||||
return nodes
|
||||
}
|
||||
|
||||
// Get the first child that is an ElementNode
|
||||
@@ -577,3 +648,29 @@ func (s *Selection) manipulateNodes(ns []*html.Node, reverse bool,
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// eachNodeHtml parses the given html string and inserts the resulting nodes in the dom with the mergeFn.
|
||||
// The parsed nodes are inserted for each element of the selection.
|
||||
// isParent can be used to indicate that the elements of the selection should be treated as the parent for the parsed html.
|
||||
// A cache is used to avoid parsing the html multiple times should the elements of the selection result in the same context.
|
||||
func (s *Selection) eachNodeHtml(htmlStr string, isParent bool, mergeFn func(n *html.Node, nodes []*html.Node)) *Selection {
|
||||
// cache to avoid parsing the html for the same context multiple times
|
||||
nodeCache := make(map[string][]*html.Node)
|
||||
var context *html.Node
|
||||
for _, n := range s.Nodes {
|
||||
if isParent {
|
||||
context = n.Parent
|
||||
} else {
|
||||
context = n
|
||||
}
|
||||
if context != nil {
|
||||
nodes, found := nodeCache[nodeName(context)]
|
||||
if !found {
|
||||
nodes = parseHtmlWithContext(htmlStr, context)
|
||||
nodeCache[nodeName(context)] = nodes
|
||||
}
|
||||
mergeFn(n, cloneNodes(nodes))
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -56,6 +56,26 @@ func TestAfterHtml(t *testing.T) {
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAfterHtmlContext(t *testing.T) {
|
||||
doc := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Before1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Before2</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`)
|
||||
doc.Find("table tr td").AfterHtml("<td class='c1'>Test</td><td class='c2'>Again</td>")
|
||||
assertLength(t, doc.Find("table tr td").Nodes, 6)
|
||||
assertClass(t, doc.Find("table tr td").Last(), "c2")
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").Append("#nf6")
|
||||
@@ -113,6 +133,27 @@ func TestAppendHtml(t *testing.T) {
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAppendHtmlContext(t *testing.T) {
|
||||
doc := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Before1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Before2</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`)
|
||||
doc.Find("table tr").AppendHtml("<td class='c1'>new1</td><td class='c2'>new2</td>")
|
||||
|
||||
assertLength(t, doc.Find("table td").Nodes, 6)
|
||||
assertClass(t, doc.Find("table td").Last(), "c2")
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestBefore(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").Before("#nf6")
|
||||
@@ -151,6 +192,27 @@ func TestBeforeHtml(t *testing.T) {
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestBeforeHtmlContext(t *testing.T) {
|
||||
doc := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Before1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Before2</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`)
|
||||
doc.Find("table tr td:first-child").BeforeHtml("<td class='c1'>new1</td><td class='c2'>new2</td>")
|
||||
|
||||
assertLength(t, doc.Find("table td").Nodes, 6)
|
||||
assertClass(t, doc.Find("table td").First(), "c1")
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestEmpty(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
s := doc.Find("#main").Empty()
|
||||
@@ -218,6 +280,27 @@ func TestPrependHtml(t *testing.T) {
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestPrependHtmlContext(t *testing.T) {
|
||||
doc := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Before1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Before2</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`)
|
||||
doc.Find("table tr").PrependHtml("<td class='c1'>new node</td><td class='c2'>other new node</td>")
|
||||
|
||||
assertLength(t, doc.Find("table td").Nodes, 6)
|
||||
assertClass(t, doc.Find("table tr td").First(), "c1")
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#nf1").Remove()
|
||||
@@ -278,6 +361,28 @@ func TestReplaceWithHtml(t *testing.T) {
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestReplaceWithHtmlContext(t *testing.T) {
|
||||
doc := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Before1</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Before2</th>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`)
|
||||
doc.Find("table th").ReplaceWithHtml("<td class='c1'>Test</td><td class='c2'>Replace</td>")
|
||||
|
||||
assertLength(t, doc.Find("table th").Nodes, 0)
|
||||
assertLength(t, doc.Find("table tr td").Nodes, 4)
|
||||
assertClass(t, doc.Find("table tr td").First(), "c1")
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestSetHtml(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
q := doc.Find("#main, #foot")
|
||||
@@ -313,6 +418,28 @@ func TestSetHtmlEmpty(t *testing.T) {
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestSetHtmlContext(t *testing.T) {
|
||||
doc := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Before1</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Before2</th>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`)
|
||||
doc.Find("table tr").SetHtml("<td class='c1'>Test</td><td class='c2'>Again</td>")
|
||||
|
||||
assertLength(t, doc.Find("table th").Nodes, 0)
|
||||
assertLength(t, doc.Find("table td").Nodes, 4)
|
||||
assertLength(t, doc.Find("table tr").Nodes, 2)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestSetText(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
q := doc.Find("#main, #foot")
|
||||
@@ -511,3 +638,54 @@ func TestWrapInnerHtml(t *testing.T) {
|
||||
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestParsingRespectsVaryingContext(t *testing.T) {
|
||||
docA := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<a class="x"></a>
|
||||
</body>
|
||||
</html>`)
|
||||
docTable := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<table class="x"></table>
|
||||
</body>
|
||||
</html>`)
|
||||
docBoth := loadString(t, `
|
||||
<html>
|
||||
<body>
|
||||
<table class="x"></table>
|
||||
<a class="x"></a>
|
||||
</body>
|
||||
</html>`)
|
||||
|
||||
sA := docA.Find(".x").AppendHtml("<tr><td>Hello</td></tr>")
|
||||
sTable := docTable.Find(".x").AppendHtml("<tr><td>Hello</td></tr>")
|
||||
sBoth := docBoth.Find(".x").AppendHtml("<tr><td>Hello</td></tr>")
|
||||
|
||||
printSel(t, docA.Selection)
|
||||
printSel(t, docTable.Selection)
|
||||
printSel(t, docBoth.Selection)
|
||||
|
||||
oA, _ := sA.Html()
|
||||
oTable, _ := sTable.Html()
|
||||
|
||||
if oA == oTable {
|
||||
t.Errorf("Expected inner html of <a> and <table> to not be equal, but got %s and %s", oA, oTable)
|
||||
}
|
||||
|
||||
oBothTable, _ := sBoth.First().Html()
|
||||
if oBothTable != oTable {
|
||||
t.Errorf("Expected inner html of <table> and <table> in doc containing both tags to be equal, but got %s and %s",
|
||||
oTable,
|
||||
oBothTable)
|
||||
}
|
||||
|
||||
oBothA, _ := sBoth.Last().Html()
|
||||
if oBothA != oA {
|
||||
t.Errorf("Expected inner html of <a> and <a> in doc containing both tags to be equal, but got %s and %s",
|
||||
oA,
|
||||
oBothA)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,14 @@ func loadDoc(page string) *Document {
|
||||
return NewDocumentFromNode(node)
|
||||
}
|
||||
|
||||
func loadString(t *testing.T, doc string) *Document {
|
||||
d, err := NewDocumentFromReader(strings.NewReader(doc))
|
||||
if err != nil {
|
||||
t.Error("Failed to parse test document")
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func TestNewDocument(t *testing.T) {
|
||||
if f, e := os.Open("./testdata/page.html"); e != nil {
|
||||
t.Error(e.Error())
|
||||
|
||||
+14
-4
@@ -36,12 +36,22 @@ func NodeName(s *Selection) string {
|
||||
if s.Length() == 0 {
|
||||
return ""
|
||||
}
|
||||
switch n := s.Get(0); n.Type {
|
||||
return nodeName(s.Get(0))
|
||||
}
|
||||
|
||||
// nodeName returns the node name of the given html node.
|
||||
// See NodeName for additional details on behaviour.
|
||||
func nodeName(node *html.Node) string {
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch node.Type {
|
||||
case html.ElementNode, html.DoctypeNode:
|
||||
return n.Data
|
||||
return node.Data
|
||||
default:
|
||||
if n.Type >= 0 && int(n.Type) < len(nodeNames) {
|
||||
return nodeNames[n.Type]
|
||||
if node.Type >= 0 && int(node.Type) < len(nodeNames) {
|
||||
return nodeNames[node.Type]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user