Compare commits

..
Author SHA1 Message Date
Martin Angers b0437209ef add NodeName and OuterHtml utility functions 2016-02-02 12:28:50 -05:00
Martin Angers 417cce822c return error if response`s request is nil on NewDocumentFromResponse 2015-09-27 16:28:20 -04:00
Martin Angers b4440419d8 make AttrOr return default value if selection is empty 2015-06-05 22:16:32 -04:00
Martin Angers 60ec77b003 doc: document AttrOr addition 2015-04-20 19:20:24 -04:00
Martin Angers a8c8239eb6 Merge pull request #73 from piotrkowalczuk/master
Selection.AttrOr shorthand method
2015-04-20 19:17:32 -04:00
Piotr Kowalczuk c416dcdfae Selection.AttrOr test 2015-04-17 00:38:27 +02:00
Piotr Kowalczuk 23904f6805 Selection.AttrOr shorthand method 2015-04-17 00:30:41 +02:00
Martin Angers 5203bc06da Merge pull request #72 from bfontaine/patch-1
Typo in NewDocumentFromResponse doc
2015-04-09 13:50:05 -04:00
Baptiste Fontaine 2d28dba982 Typo in NewDocumentFromResponse doc 2015-04-09 19:33:38 +02:00
Martin Angers 269246d9e7 doc: fix obsolete links in README 2015-03-28 09:30:56 -04:00
Martin Angers 55f7879c98 Merge pull request #68 from pacificporter/cascadia_has_moved
cascadia has moved.
2015-03-28 09:20:13 -04:00
HARUYAMA Seigo d9b2b5ed51 cascadia has moved.
code.google.com/p/cascadia
->
github.com/andybalholm/cascadia
2015-03-28 11:15:11 +09:00
Martin Angers 4cf64c51f7 Merge branch 'thatguystone-master' 2015-02-04 20:43:27 -05:00
Martin Angers fe61b74344 document Prepend addition 2015-02-04 20:43:02 -05:00
Andrew Stone edcb3e4b1e manipulation: Add Prepend*()
Looks like I missed yet another manipulation function.
2015-02-04 11:28:51 -05:00
Martin Angers 12a46cd4a1 Merge pull request #58 from jpillora/master
SetAttr now creates attributes, in addition to updating attributes, clos...
2014-12-09 22:34:46 -05:00
Jaime Pillora 51379dc225 SetAttr now creates attributes, in addition to updating attributes, closes #57 2014-12-06 18:53:56 +11:00
Martin Angers 0db8a55dc4 fix doc alignment 2014-11-28 11:55:26 -05:00
Martin Angers 2922326512 doc 2014-11-28 11:50:40 -05:00
Martin Angers a923db6a4b review PR, fix WrapInner bug 2014-11-28 11:46:58 -05:00
Andrew Stone 2455631714 manipulation: Add Unwrap() 2014-11-12 10:37:11 -05:00
Andrew Stone 162ad3a767 manipulation: add ReplaceWith*() functions 2014-11-12 10:37:11 -05:00
Andrew Stone ca48f5f409 manipulation: add Wrap*() functions 2014-11-12 10:37:11 -05:00
Martin Angers ffa8a68c9f accept variadic class names in AddClass 2014-11-07 22:08:57 -05:00
Martin Angers 39d99e8fa4 remove fmt dependency 2014-11-07 13:21:28 -05:00
16 changed files with 869 additions and 29 deletions
+11 -4
View File
@@ -30,6 +30,10 @@ Please note that because of the net/html dependency, goquery requires Go1.1+.
**Note that goquery's API is now stable, and will not break.**
* **2016-02-02** : Add `NodeName` utility function similar to the DOM's `nodeName` property. It returns the tag name of the first element in a selection, and other relevant values of non-element nodes (see godoc for details). Add `OuterHtml` utility function similar to the DOM's `outerHTML` property (named `OuterHtml` in small caps for consistency with the existing `Html` method on the `Selection`).
* **2015-04-20** : Add `AttrOr` helper method to return the attribute's value or a default value if absent. Thanks to [piotrkowalczuk][piotr].
* **2015-02-04** : Add more manipulation functions - Prepend* - thanks again to [Andrew Stone][thatguystone].
* **2014-11-28** : Add more manipulation functions - ReplaceWith*, Wrap* and Unwrap - thanks again to [Andrew Stone][thatguystone].
* **2014-11-07** : Add manipulation functions (thanks to [Andrew Stone][thatguystone]) and `*Matcher` functions, that receive compiled cascadia selectors instead of selector strings, thus avoiding potential panics thrown by goquery via `cascadia.MustCompile` calls. This results in better performance (selectors can be compiled once and reused) and more idiomatic error handling (you can handle cascadia's compilation errors, instead of recovering from panics, which had been bugging me for a long time). Note that the actual type expected is a `Matcher` interface, that `cascadia.Selector` implements. Other matcher implementations could be used.
* **2014-11-06** : Change import paths of net/html to golang.org/x/net/html (see https://groups.google.com/forum/#!topic/golang-nuts/eD8dh3T9yyA). Make sure to update your code to use the new import path too when you call goquery with `html.Node`s.
* **v0.3.2** : Add `NewDocumentFromReader()` (thanks jweir) which allows creating a goquery document from an io.Reader.
@@ -53,6 +57,8 @@ jQuery often has many variants for the same function (no argument, a selector st
* The signatures accepting a function as argument in jQuery are defined in goquery as `XxxFunction()` and take a function as argument (e.g.: `FilterFunction()`)
* The goquery methods that can be called with a selector string have a corresponding version that take a `Matcher` interface and are defined as `XxxMatcher()` (e.g.: `IsMatcher()`)
Utility functions that are not in jQuery but are useful in Go are implemented as functions (that take a `*Selection` as parameter), to avoid a potential naming clash on the `*Selection`'s methods (reserved for jQuery-equivalent behaviour).
The complete [godoc reference documentation can be found here][doc].
Please note that Cascadia's selectors do not necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details.
@@ -97,13 +103,14 @@ The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia'
[jquery]: http://jquery.com/
[go]: http://golang.org/
[cascadia]: http://code.google.com/p/cascadia/
[cascadia]: https://github.com/andybalholm/cascadia
[bsd]: http://opensource.org/licenses/BSD-3-Clause
[golic]: http://golang.org/LICENSE
[caslic]: http://code.google.com/p/cascadia/source/browse/LICENSE
[caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE
[doc]: http://godoc.org/github.com/PuerkitoBio/goquery
[index]: http://api.jquery.com/index/
[gonet]: http://code.google.com/p/go/source/detail?r=f7f5159120f51ba0070774d3c5907969b5fe7858&repo=net
[html]: http://godoc.org/code.google.com/p/go.net/html
[gonet]: https://github.com/golang/net/
[html]: http://godoc.org/golang.org/x/net/html
[wiki]: https://github.com/PuerkitoBio/goquery/wiki/Tips-and-tricks
[thatguystone]: https://github.com/thatguystone
[piotr]: https://github.com/piotrkowalczuk
+13 -2
View File
@@ -35,7 +35,7 @@ Also, because the net/html parser requires UTF-8 encoding, so does goquery: it i
the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML.
See the repository's wiki for various options on how to do this.
Syntax-wise, it is as close as possible to jQuery, with the same function names when
Syntax-wise, it is as close as possible to jQuery, with the same method names when
possible, and that warm and fuzzy chainable interface. jQuery being the
ultra-popular library that it is, writing a similar HTML-manipulating
library was better to follow its API than to start anew (in the same spirit as
@@ -81,10 +81,16 @@ The three dots (...) indicate that various "overloads" are available.
- Before...()
- Clone()
- Empty()
- Prepend...()
- Remove...()
- ReplaceWith...()
- Unwrap()
- Wrap...()
- WrapAll...()
- WrapInner...()
* property.go : methods that inspect and get the node's properties values.
- Attr(), RemoveAttr(), SetAttr()
- Attr*(), RemoveAttr(), SetAttr()
- AddClass(), HasClass(), RemoveClass(), ToggleClass()
- Html()
- Length()
@@ -108,5 +114,10 @@ The three dots (...) indicate that various "overloads" are available.
- Document
- Selection
- Matcher
* utilities.go : definition of helper functions (and not methods on a *Selection)
that are not part of jQuery, but are useful to goquery.
- NodeName
- OuterHtml
*/
package goquery
+1 -1
View File
@@ -1,7 +1,7 @@
package goquery
import (
"code.google.com/p/cascadia"
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
+1 -1
View File
@@ -1,7 +1,7 @@
package goquery
import (
"code.google.com/p/cascadia"
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
+302 -8
View File
@@ -1,10 +1,9 @@
package goquery
import (
"fmt"
"strings"
"code.google.com/p/cascadia"
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
@@ -14,7 +13,7 @@ import (
// If one of the matched elements in the selection is not currently in the
// document, it's impossible to insert nodes after it, so it will be ignored.
//
// This follows the same rules outlined in Selection.Append.
// This follows the same rules as Selection.Append.
func (s *Selection) After(selector string) *Selection {
return s.AfterMatcher(cascadia.MustCompile(selector))
}
@@ -25,7 +24,7 @@ func (s *Selection) After(selector string) *Selection {
// If one of the matched elements in the selection is not currently in the
// document, it's impossible to insert nodes after it, so it will be ignored.
//
// This follows the same rules outlined in Selection.Append.
// This follows the same rules as Selection.Append.
func (s *Selection) AfterMatcher(m Matcher) *Selection {
return s.AfterNodes(m.MatchAll(s.document.rootNode)...)
}
@@ -33,21 +32,21 @@ func (s *Selection) AfterMatcher(m Matcher) *Selection {
// AfterSelection inserts the elements in the selection after each element in the set of matched
// elements.
//
// This follows the same rules outlined in Selection.Append.
// This follows the same rules as Selection.Append.
func (s *Selection) AfterSelection(sel *Selection) *Selection {
return s.AfterNodes(sel.Nodes...)
}
// AfterHtml parses the html and inserts it after the set of matched elements.
//
// This follows the same rules outlined in Selection.Append.
// This follows the same rules as Selection.Append.
func (s *Selection) AfterHtml(html string) *Selection {
return s.AfterNodes(parseHtml(html)...)
}
// AfterNodes inserts the nodes after each element in the set of matched elements.
//
// This follows the same rules outlined in Selection.Append.
// This follows the same rules as Selection.Append.
func (s *Selection) AfterNodes(ns ...*html.Node) *Selection {
return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) {
if sn.Parent != nil {
@@ -163,6 +162,45 @@ func (s *Selection) Empty() *Selection {
return pushStack(s, nodes)
}
// Prepend prepends the elements specified by the selector to each element in
// the set of matched elements, following the same rules as Append.
func (s *Selection) Prepend(selector string) *Selection {
return s.PrependMatcher(cascadia.MustCompile(selector))
}
// PrependMatcher prepends the elements specified by the matcher to each
// element in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) PrependMatcher(m Matcher) *Selection {
return s.PrependNodes(m.MatchAll(s.document.rootNode)...)
}
// PrependSelection prepends the elements in the selection to each element in
// the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) PrependSelection(sel *Selection) *Selection {
return s.PrependNodes(sel.Nodes...)
}
// 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)...)
}
// PrependNodes prepends the specified nodes to each node in the set of
// matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) PrependNodes(ns ...*html.Node) *Selection {
return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) {
// sn.FirstChild may be nil, in which case this functions like
// sn.AppendChild()
sn.InsertBefore(n, sn.FirstChild)
})
}
// Remove removes the set of matched elements from the document.
// It returns the same selection, now consisting of nodes not in the document.
func (s *Selection) Remove() *Selection {
@@ -187,16 +225,272 @@ func (s *Selection) RemoveMatcher(m Matcher) *Selection {
return s.FilterMatcher(m).Remove()
}
// ReplaceWith replaces each element in the set of matched elements with the
// nodes matched by the given selector.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWith(selector string) *Selection {
return s.ReplaceWithMatcher(cascadia.MustCompile(selector))
}
// ReplaceWithMatcher replaces each element in the set of matched elements with
// the nodes matched by the given Matcher.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWithMatcher(m Matcher) *Selection {
return s.ReplaceWithNodes(m.MatchAll(s.document.rootNode)...)
}
// ReplaceWithSelection replaces each element in the set of matched elements with
// the nodes from the given Selection.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWithSelection(sel *Selection) *Selection {
return s.ReplaceWithNodes(sel.Nodes...)
}
// ReplaceWithHtml replaces each element in the set of matched elements with
// the parsed HTML.
// 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)...)
}
// ReplaceWithNodes replaces each element in the set of matched elements with
// the given nodes.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWithNodes(ns ...*html.Node) *Selection {
s.AfterNodes(ns...)
return s.Remove()
}
// Unwrap removes the parents of the set of matched elements, leaving the matched
// elements (and their siblings, if any) in their place.
// It returns the original selection.
func (s *Selection) Unwrap() *Selection {
s.Parent().Each(func(i int, ss *Selection) {
// For some reason, jquery allows unwrap to remove the <head> element, so
// allowing it here too. Same for <html>. Why it allows those elements to
// be unwrapped while not allowing body is a mystery to me.
if ss.Nodes[0].Data != "body" {
ss.ReplaceWithSelection(ss.Contents())
}
})
return s
}
// Wrap wraps each element in the set of matched elements inside the first
// element matched by the given selector. The matched child is cloned before
// being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) Wrap(selector string) *Selection {
return s.WrapMatcher(cascadia.MustCompile(selector))
}
// WrapMatcher wraps each element in the set of matched elements inside the
// first element matched by the given matcher. The matched child is cloned
// before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapMatcher(m Matcher) *Selection {
return s.wrapNodes(m.MatchAll(s.document.rootNode)...)
}
// WrapSelection wraps each element in the set of matched elements inside the
// first element in the given Selection. The element is cloned before being
// inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapSelection(sel *Selection) *Selection {
return s.wrapNodes(sel.Nodes...)
}
// WrapHtml wraps each element in the set of matched elements inside the inner-
// 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)...)
}
// WrapNode wraps each element in the set of matched elements inside the inner-
// most child of the given node. The given node is copied before being inserted
// into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapNode(n *html.Node) *Selection {
return s.wrapNodes(n)
}
func (s *Selection) wrapNodes(ns ...*html.Node) *Selection {
s.Each(func(i int, ss *Selection) {
ss.wrapAllNodes(ns...)
})
return s
}
// WrapAll wraps a single HTML structure, matched by the given selector, around
// all elements in the set of matched elements. The matched child is cloned
// before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapAll(selector string) *Selection {
return s.WrapAllMatcher(cascadia.MustCompile(selector))
}
// WrapAllMatcher wraps a single HTML structure, matched by the given Matcher,
// around all elements in the set of matched elements. The matched child is
// cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllMatcher(m Matcher) *Selection {
return s.wrapAllNodes(m.MatchAll(s.document.rootNode)...)
}
// WrapAllSelection wraps a single HTML structure, the first node of the given
// Selection, around all elements in the set of matched elements. The matched
// child is cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllSelection(sel *Selection) *Selection {
return s.wrapAllNodes(sel.Nodes...)
}
// WrapAllHtml wraps the given HTML structure around all elements in the set of
// matched elements. The matched child is cloned before being inserted into the
// document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllHtml(html string) *Selection {
return s.wrapAllNodes(parseHtml(html)...)
}
func (s *Selection) wrapAllNodes(ns ...*html.Node) *Selection {
if len(ns) > 0 {
return s.WrapAllNode(ns[0])
}
return s
}
// WrapAllNode wraps the given node around the first element in the Selection,
// making all other nodes in the Selection children of the given node. The node
// is cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllNode(n *html.Node) *Selection {
if s.Size() == 0 {
return s
}
wrap := cloneNode(n)
first := s.Nodes[0]
if first.Parent != nil {
first.Parent.InsertBefore(wrap, first)
first.Parent.RemoveChild(first)
}
for c := getFirstChildEl(wrap); c != nil; c = getFirstChildEl(wrap) {
wrap = c
}
newSingleSelection(wrap, s.document).AppendSelection(s)
return s
}
// WrapInner wraps an HTML structure, matched by the given selector, around the
// content of element in the set of matched elements. The matched child is
// cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInner(selector string) *Selection {
return s.WrapInnerMatcher(cascadia.MustCompile(selector))
}
// WrapInnerMatcher wraps an HTML structure, matched by the given selector,
// around the content of element in the set of matched elements. The matched
// child is cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInnerMatcher(m Matcher) *Selection {
return s.wrapInnerNodes(m.MatchAll(s.document.rootNode)...)
}
// WrapInnerSelection wraps an HTML structure, matched by the given selector,
// around the content of element in the set of matched elements. The matched
// child is cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInnerSelection(sel *Selection) *Selection {
return s.wrapInnerNodes(sel.Nodes...)
}
// WrapInnerHtml wraps an HTML structure, matched by the given selector, around
// the content of element in the set of matched elements. The matched child is
// 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)...)
}
// WrapInnerNode wraps an HTML structure, matched by the given selector, around
// the content of element in the set of matched elements. The matched child is
// cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInnerNode(n *html.Node) *Selection {
return s.wrapInnerNodes(n)
}
func (s *Selection) wrapInnerNodes(ns ...*html.Node) *Selection {
if len(ns) == 0 {
return s
}
s.Each(func(i int, s *Selection) {
contents := s.Contents()
if contents.Size() > 0 {
contents.wrapAllNodes(ns...)
} else {
s.AppendNodes(cloneNode(ns[0]))
}
})
return s
}
func parseHtml(h string) []*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), &html.Node{Type: html.ElementNode})
if err != nil {
panic(fmt.Sprintf("goquery: failed to parse HTML: %s", err))
panic("goquery: failed to parse HTML: " + err.Error())
}
return nodes
}
// Get the first child that is an ElementNode
func getFirstChildEl(n *html.Node) *html.Node {
c := n.FirstChild
for c != nil && c.Type != html.ElementNode {
c = c.NextSibling
}
return c
}
// Deep copy a slice of nodes.
func cloneNodes(ns []*html.Node) []*html.Node {
cns := make([]*html.Node, 0, len(ns))
+259
View File
@@ -4,6 +4,10 @@ import (
"testing"
)
const (
wrapHtml = "<div id=\"ins\">test string<div><p><em><b></b></em></p></div></div>"
)
func TestAfter(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").After("#nf6")
@@ -156,6 +160,64 @@ func TestEmpty(t *testing.T) {
printSel(t, doc.Selection)
}
func TestPrepend(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").Prepend("#nf6")
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
assertLength(t, doc.Find("#main #nf6:first-child").Nodes, 1)
printSel(t, doc.Selection)
}
func TestPrependBody(t *testing.T) {
doc := Doc2Clone()
doc.Find("body").Prepend("#nf6")
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
assertLength(t, doc.Find("#main #nf6").Nodes, 0)
assertLength(t, doc.Find("body > #nf6:first-child").Nodes, 1)
printSel(t, doc.Selection)
}
func TestPrependSelection(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").PrependSelection(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:first-child").Nodes, 1)
assertLength(t, doc.Find("#main #nf2:nth-child(2)").Nodes, 1)
printSel(t, doc.Selection)
}
func TestPrependSelectionExisting(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").PrependSelection(doc.Find("#n5, #n6"))
assertClass(t, doc.Find("#main :nth-child(1)"), "five")
assertClass(t, doc.Find("#main :nth-child(2)"), "six")
assertClass(t, doc.Find("#main :nth-child(5)"), "three")
assertClass(t, doc.Find("#main :nth-child(6)"), "four")
printSel(t, doc.Selection)
}
func TestPrependClone(t *testing.T) {
doc := Doc2Clone()
doc.Find("#n1").PrependSelection(doc.Find("#nf1").Clone())
assertLength(t, doc.Find("#foot #nf1:first-child").Nodes, 1)
assertLength(t, doc.Find("#main #nf1:first-child").Nodes, 1)
printSel(t, doc.Selection)
}
func TestPrependHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("div").PrependHtml("<strong>new node</strong>")
assertLength(t, doc.Find("strong:first-child").Nodes, 14)
printSel(t, doc.Selection)
}
func TestRemove(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").Remove()
@@ -192,3 +254,200 @@ func TestRemoveFiltered(t *testing.T) {
}
printSel(t, doc.Selection)
}
func TestReplaceWith(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf6").ReplaceWith("#main")
assertLength(t, doc.Find("#foot #main:last-child").Nodes, 1)
printSel(t, doc.Selection)
doc.Find("#foot").ReplaceWith("#main")
assertLength(t, doc.Find("#foot").Nodes, 0)
assertLength(t, doc.Find("#main").Nodes, 1)
printSel(t, doc.Selection)
}
func TestReplaceWithHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main, #foot").ReplaceWithHtml("<div id=\"replace\"></div>")
assertLength(t, doc.Find("#replace").Nodes, 2)
printSel(t, doc.Selection)
}
func TestReplaceWithSelection(t *testing.T) {
doc := Doc2Clone()
sel := doc.Find("#nf6").ReplaceWithSelection(doc.Find("#nf5"))
assertSelectionIs(t, sel, "#nf6")
assertLength(t, doc.Find("#nf6").Nodes, 0)
assertLength(t, doc.Find("#nf5").Nodes, 1)
printSel(t, doc.Selection)
}
func TestUnwrap(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf5").Unwrap()
assertLength(t, doc.Find("#foot").Nodes, 0)
assertLength(t, doc.Find("body > #nf1").Nodes, 1)
assertLength(t, doc.Find("body > #nf5").Nodes, 1)
printSel(t, doc.Selection)
doc = Doc2Clone()
doc.Find("#nf5, #n1").Unwrap()
assertLength(t, doc.Find("#foot").Nodes, 0)
assertLength(t, doc.Find("#main").Nodes, 0)
assertLength(t, doc.Find("body > #n1").Nodes, 1)
assertLength(t, doc.Find("body > #nf5").Nodes, 1)
printSel(t, doc.Selection)
}
func TestUnwrapBody(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").Unwrap()
assertLength(t, doc.Find("body").Nodes, 1)
assertLength(t, doc.Find("body > #main").Nodes, 1)
printSel(t, doc.Selection)
}
func TestUnwrapHead(t *testing.T) {
doc := Doc2Clone()
doc.Find("title").Unwrap()
assertLength(t, doc.Find("head").Nodes, 0)
assertLength(t, doc.Find("head > title").Nodes, 0)
assertLength(t, doc.Find("title").Nodes, 1)
printSel(t, doc.Selection)
}
func TestUnwrapHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("head").Unwrap()
assertLength(t, doc.Find("html").Nodes, 0)
assertLength(t, doc.Find("html head").Nodes, 0)
assertLength(t, doc.Find("head").Nodes, 1)
printSel(t, doc.Selection)
}
func TestWrap(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").Wrap("#nf2")
nf1 := doc.Find("#foot #nf2 #nf1")
assertLength(t, nf1.Nodes, 1)
nf2 := doc.Find("#nf2")
assertLength(t, nf2.Nodes, 2)
printSel(t, doc.Selection)
}
func TestWrapEmpty(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").Wrap("#doesnt-exist")
origHtml, _ := Doc2().Html()
newHtml, _ := doc.Html()
if origHtml != newHtml {
t.Error("Expected the two documents to be identical.")
}
printSel(t, doc.Selection)
}
func TestWrapHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find(".odd").WrapHtml(wrapHtml)
nf2 := doc.Find("#ins #nf2")
assertLength(t, nf2.Nodes, 1)
printSel(t, doc.Selection)
}
func TestWrapSelection(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").WrapSelection(doc.Find("#nf2"))
nf1 := doc.Find("#foot #nf2 #nf1")
assertLength(t, nf1.Nodes, 1)
nf2 := doc.Find("#nf2")
assertLength(t, nf2.Nodes, 2)
printSel(t, doc.Selection)
}
func TestWrapAll(t *testing.T) {
doc := Doc2Clone()
doc.Find(".odd").WrapAll("#nf1")
nf1 := doc.Find("#main #nf1")
assertLength(t, nf1.Nodes, 1)
sel := nf1.Find("#n2 ~ #n4 ~ #n6 ~ #nf2 ~ #nf4 ~ #nf6")
assertLength(t, sel.Nodes, 1)
printSel(t, doc.Selection)
}
func TestWrapAllHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find(".odd").WrapAllHtml(wrapHtml)
nf1 := doc.Find("#main div#ins div p em b #n2 ~ #n4 ~ #n6 ~ #nf2 ~ #nf4 ~ #nf6")
assertLength(t, nf1.Nodes, 1)
printSel(t, doc.Selection)
}
func TestWrapInnerNoContent(t *testing.T) {
doc := Doc2Clone()
doc.Find(".one").WrapInner(".two")
twos := doc.Find(".two")
assertLength(t, twos.Nodes, 4)
assertLength(t, doc.Find(".one .two").Nodes, 2)
printSel(t, doc.Selection)
}
func TestWrapInnerWithContent(t *testing.T) {
doc := Doc3Clone()
doc.Find(".one").WrapInner(".two")
twos := doc.Find(".two")
assertLength(t, twos.Nodes, 4)
assertLength(t, doc.Find(".one .two").Nodes, 2)
printSel(t, doc.Selection)
}
func TestWrapInnerNoWrapper(t *testing.T) {
doc := Doc2Clone()
doc.Find(".one").WrapInner(".not-exist")
twos := doc.Find(".two")
assertLength(t, twos.Nodes, 2)
assertLength(t, doc.Find(".one").Nodes, 2)
assertLength(t, doc.Find(".one .two").Nodes, 0)
printSel(t, doc.Selection)
}
func TestWrapInnerHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("#foot").WrapInnerHtml(wrapHtml)
foot := doc.Find("#foot div#ins div p em b #nf1 ~ #nf2 ~ #nf3")
assertLength(t, foot.Nodes, 1)
printSel(t, doc.Selection)
}
+25 -6
View File
@@ -20,6 +20,20 @@ func (s *Selection) Attr(attrName string) (val string, exists bool) {
return getAttributeValue(attrName, s.Nodes[0])
}
// AttrOr works like Attr but returns default value if attribute is not present.
func (s *Selection) AttrOr(attrName, defaultValue string) string {
if len(s.Nodes) == 0 {
return defaultValue
}
val, exists := getAttributeValue(attrName, s.Nodes[0])
if !exists {
return defaultValue
}
return val
}
// RemoveAttr removes the named attribute from each element in the set of matched elements.
func (s *Selection) RemoveAttr(attrName string) *Selection {
for _, n := range s.Nodes {
@@ -32,7 +46,10 @@ func (s *Selection) RemoveAttr(attrName string) *Selection {
// SetAttr sets the given attribute on each element in the set of matched elements.
func (s *Selection) SetAttr(attrName, val string) *Selection {
for _, n := range s.Nodes {
if attr := getAttributePtr(attrName, n); attr != nil {
attr := getAttributePtr(attrName, n)
if attr == nil {
n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val})
} else {
attr.Val = val
}
}
@@ -83,16 +100,18 @@ func (s *Selection) Html() (ret string, e error) {
}
// AddClass adds the given class(es) to each element in the set of matched elements.
// Multiple class names can be specified, separated by a space.
func (s *Selection) AddClass(class string) *Selection {
if class == "" {
// Multiple class names can be specified, separated by a space or via multiple arguments.
func (s *Selection) AddClass(class ...string) *Selection {
classStr := strings.TrimSpace(strings.Join(class, " "))
if classStr == "" {
return s
}
slClasses := getClassesSlice(class)
tcls := getClassesSlice(classStr)
for _, n := range s.Nodes {
curClasses, attr := getClassesAndAttr(n, true)
for _, newClass := range slClasses {
for _, newClass := range tcls {
if strings.Index(curClasses, " "+newClass+" ") == -1 {
curClasses += newClass + " "
}
+28
View File
@@ -14,6 +14,19 @@ func TestAttrExists(t *testing.T) {
}
}
func TestAttrOr(t *testing.T) {
if val := Doc().Find("a").AttrOr("fake-attribute", "alternative"); val != "alternative" {
t.Error("Expected an alternative value for 'fake-attribute' attribute.")
} else {
t.Logf("Value returned for not existing attribute: %v.", val)
}
if val := Doc().Find("zz").AttrOr("fake-attribute", "alternative"); val != "alternative" {
t.Error("Expected an alternative value for 'fake-attribute' on an empty selection.")
} else {
t.Logf("Value returned for empty selection: %v.", val)
}
}
func TestAttrNotExist(t *testing.T) {
if val, ok := Doc().Find("div.row-fluid").Attr("href"); ok {
t.Errorf("Expected no value for the href attribute, got %v.", val)
@@ -46,6 +59,21 @@ func TestSetAttr(t *testing.T) {
}
}
func TestSetAttr2(t *testing.T) {
sel := Doc2Clone().Find("#main")
sel.SetAttr("foo", "bar")
val, ok := sel.Attr("foo")
if !ok {
t.Error("Expected an 'foo' attribute on main")
}
if val != "bar" {
t.Errorf("Expected an attribute 'foo' to be 'bar', got '%s'", val)
}
}
func TestText(t *testing.T) {
txt := Doc().Find("h1").Text()
if strings.Trim(txt, " \n\r\t") != "Provok.in" {
+1 -1
View File
@@ -1,7 +1,7 @@
package goquery
import (
"code.google.com/p/cascadia"
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
+2 -2
View File
@@ -3,7 +3,7 @@
<head>
<title>Tests for siblings</title>
</head>
<body>
<BODY>
<div id="main">
<div id="n1" class="one even row"></div>
<div id="n2" class="two odd row"></div>
@@ -20,5 +20,5 @@
<div id="nf5" class="five even row odder"></div>
<div id="nf6" class="six odd row"></div>
</div>
</body>
</BODY>
</html>
+24
View File
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Tests for siblings</title>
</head>
<BODY>
<div id="main">
<div id="n1" class="one even row">hello</div>
<div id="n2" class="two odd row"></div>
<div id="n3" class="three even row"></div>
<div id="n4" class="four odd row"></div>
<div id="n5" class="five even row"></div>
<div id="n6" class="six odd row"></div>
</div>
<div id="foot">
<div id="nf1" class="one even row">text</div>
<div id="nf2" class="two odd row"></div>
<div id="nf3" class="three even row"></div>
<div id="nf4" class="four odd row"></div>
<div id="nf5" class="five even row odder"></div>
<div id="nf6" class="six odd row"></div>
</div>
</BODY>
</html>
+1 -1
View File
@@ -1,7 +1,7 @@
package goquery
import (
"code.google.com/p/cascadia"
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
+5 -3
View File
@@ -51,15 +51,17 @@ func NewDocumentFromReader(r io.Reader) (*Document, error) {
return newDocument(root, nil), nil
}
// NewDocumentFromResponse is another Document constructor that takes an http resonse as argument.
// NewDocumentFromResponse is another Document constructor that takes an http response as argument.
// It loads the specified response's document, parses it, and stores the root Document
// node, ready to be manipulated. The response's body is closed on return.
func NewDocumentFromResponse(res *http.Response) (*Document, error) {
if res == nil {
return nil, errors.New("Response is nil pointer")
return nil, errors.New("Response is nil")
}
defer res.Body.Close()
if res.Request == nil {
return nil, errors.New("Response.Request is nil")
}
// Parse the HTML into nodes
root, e := html.Parse(res.Body)
+10
View File
@@ -12,6 +12,7 @@ import (
// Test helper functions and members
var doc *Document
var doc2 *Document
var doc3 *Document
var docB *Document
var docW *Document
@@ -33,6 +34,15 @@ func Doc2() *Document {
func Doc2Clone() *Document {
return CloneDocument(Doc2())
}
func Doc3() *Document {
if doc3 == nil {
doc3 = loadDoc("page3.html")
}
return doc3
}
func Doc3Clone() *Document {
return CloneDocument(Doc3())
}
func DocB() *Document {
if docB == nil {
docB = loadDoc("gotesting.html")
+58
View File
@@ -1,9 +1,67 @@
package goquery
import (
"bytes"
"golang.org/x/net/html"
)
var nodeNames = []string{
html.ErrorNode: "#error",
html.TextNode: "#text",
html.DocumentNode: "#document",
html.CommentNode: "#comment",
}
// NodeName returns the node name of the first element in the selection.
// It tries to behave in a similar way as the DOM's nodeName property
// (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName).
//
// Go's net/html package defines the following node types, listed with
// the corresponding returned value from this function:
//
// ErrorNode : #error
// TextNode : #text
// DocumentNode : #document
// ElementNode : the element's tag name
// CommentNode : #comment
// DoctypeNode : the name of the document type
//
func NodeName(s *Selection) string {
if s.Length() == 0 {
return ""
}
switch n := s.Get(0); n.Type {
case html.ElementNode, html.DoctypeNode:
return n.Data
default:
if n.Type >= 0 && int(n.Type) < len(nodeNames) {
return nodeNames[n.Type]
}
return ""
}
}
// OuterHtml returns the outer HTML rendering of the first item in
// the selection - that is, the HTML including the first element's
// tag and attributes.
//
// Unlike InnerHtml, this is a function and not a method on the Selection,
// because this is not a jQuery method (in javascript-land, this is
// a property provided by the DOM).
func OuterHtml(s *Selection) (string, error) {
var buf bytes.Buffer
if s.Length() == 0 {
return "", nil
}
n := s.Get(0)
if err := html.Render(&buf, n); err != nil {
return "", err
}
return buf.String(), nil
}
func getChildren(n *html.Node) (result []*html.Node) {
for c := n.FirstChild; c != nil; c = c.NextSibling {
result = append(result, c)
+128
View File
@@ -0,0 +1,128 @@
package goquery
import (
"reflect"
"sort"
"strings"
"testing"
"golang.org/x/net/html"
)
var allNodes = `<!doctype html>
<html>
<head>
<meta a="b">
</head>
<body>
<p><!-- this is a comment -->
This is some text.
</p>
<div></div>
<h1 class="header"></h1>
<h2 class="header"></h2>
</body>
</html>`
func TestNodeName(t *testing.T) {
doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
if err != nil {
t.Fatal(err)
}
n0 := doc.Nodes[0]
nDT := n0.FirstChild
sMeta := doc.Find("meta")
nMeta := sMeta.Get(0)
sP := doc.Find("p")
nP := sP.Get(0)
nComment := nP.FirstChild
nText := nComment.NextSibling
cases := []struct {
node *html.Node
typ html.NodeType
want string
}{
{n0, html.DocumentNode, nodeNames[html.DocumentNode]},
{nDT, html.DoctypeNode, "html"},
{nMeta, html.ElementNode, "meta"},
{nP, html.ElementNode, "p"},
{nComment, html.CommentNode, nodeNames[html.CommentNode]},
{nText, html.TextNode, nodeNames[html.TextNode]},
}
for i, c := range cases {
got := NodeName(newSingleSelection(c.node, doc))
if c.node.Type != c.typ {
t.Errorf("%d: want type %v, got %v", i, c.typ, c.node.Type)
}
if got != c.want {
t.Errorf("%d: want %q, got %q", i, c.want, got)
}
}
}
func TestNodeNameMultiSel(t *testing.T) {
doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
if err != nil {
t.Fatal(err)
}
in := []string{"p", "h1", "div"}
var out []string
doc.Find(strings.Join(in, ", ")).Each(func(i int, s *Selection) {
got := NodeName(s)
out = append(out, got)
})
sort.Strings(in)
sort.Strings(out)
if !reflect.DeepEqual(in, out) {
t.Error("want %v, got %v")
}
}
func TestOuterHtml(t *testing.T) {
doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
if err != nil {
t.Fatal(err)
}
n0 := doc.Nodes[0]
nDT := n0.FirstChild
sMeta := doc.Find("meta")
sP := doc.Find("p")
nP := sP.Get(0)
nComment := nP.FirstChild
nText := nComment.NextSibling
sHeaders := doc.Find(".header")
cases := []struct {
node *html.Node
sel *Selection
want string
}{
{nDT, nil, "<!DOCTYPE html>"}, // render makes DOCTYPE all caps
{nil, sMeta, `<meta a="b"/>`}, // and auto-closes the meta
{nil, sP, `<p><!-- this is a comment -->
This is some text.
</p>`},
{nComment, nil, "<!-- this is a comment -->"},
{nText, nil, `
This is some text.
`},
{nil, sHeaders, `<h1 class="header"></h1>`},
}
for i, c := range cases {
if c.sel == nil {
c.sel = newSingleSelection(c.node, doc)
}
got, err := OuterHtml(c.sel)
if err != nil {
t.Fatal(err)
}
if got != c.want {
t.Errorf("%d: want %q, got %q", i, c.want, got)
}
}
}