mirror of
https://github.com/PuerkitoBio/goquery.git
synced 2024-04-21 12:31:36 +00:00
Compare commits
177
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e11510b114 | ||
|
|
a9cfd6779b | ||
|
|
b3953ef42a | ||
|
|
ee8a7e0a3a | ||
|
|
0126a1fd88 | ||
|
|
e1f2d60505 | ||
|
|
1ba0ef7e9b | ||
|
|
795f67a092 | ||
|
|
b67d192c91 | ||
|
|
bedb4e7a88 | ||
|
|
a29eef7e71 | ||
|
|
aa734f3932 | ||
|
|
74649be271 | ||
|
|
9d962837e1 | ||
|
|
7fc894a0ba | ||
|
|
171b719dd6 | ||
|
|
f41c56001f | ||
|
|
8a11cc402f | ||
|
|
70a02e53e3 | ||
|
|
3798c63c0d | ||
|
|
705a0066a5 | ||
|
|
a48bafac4b | ||
|
|
af965a4d72 | ||
|
|
b3706f8903 | ||
|
|
7ebd145bd7 | ||
|
|
4a687a672f | ||
|
|
94c2530dd8 | ||
|
|
5d7b0271f0 | ||
|
|
d89086ead1 | ||
|
|
99c8f1ecd5 | ||
|
|
c942deb265 | ||
|
|
0132bb8147 | ||
|
|
6f0f9d6b87 | ||
|
|
cd12820764 | ||
|
|
51a0e1a4bb | ||
|
|
89946c829f | ||
|
|
8b2a6e25d5 | ||
|
|
538dd4a913 | ||
|
|
844d6bd46b | ||
|
|
1b16b92ddf | ||
|
|
3d11372b35 | ||
|
|
bd23f0c6e0 | ||
|
|
56ba1e6f08 | ||
|
|
b17a2bbb6d | ||
|
|
c70bac62f5 | ||
|
|
8674076365 | ||
|
|
6dcff85b2e | ||
|
|
3dcf72e6c1 | ||
|
|
22fd749627 | ||
|
|
9b813b2309 | ||
|
|
0255f16068 | ||
|
|
2d2796f417 | ||
|
|
80f0084f4c | ||
|
|
68fe358134 | ||
|
|
2af3d16e2b | ||
|
|
6c2cec4567 | ||
|
|
8311f594d7 | ||
|
|
dc2ec5c7ca | ||
|
|
7757e27950 | ||
|
|
ea1bc64a63 | ||
|
|
dd77530fdb | ||
|
|
a86ea07301 | ||
|
|
b4912d23e5 | ||
|
|
106fcbf95a | ||
|
|
61aa1975b1 | ||
|
|
7a8f63580d | ||
|
|
6285ceb1e9 | ||
|
|
b55d492104 | ||
|
|
ef34a70bad | ||
|
|
5819122076 | ||
|
|
09540e5659 | ||
|
|
46f28d87ad | ||
|
|
520f19d599 | ||
|
|
2324bda66f | ||
|
|
3855699cc0 | ||
|
|
a41cf096e6 | ||
|
|
7d9aa5c1e6 | ||
|
|
c56812c109 | ||
|
|
cab5e2c9d6 | ||
|
|
802bece6d0 | ||
|
|
499bd47957 | ||
|
|
bc4e06eb07 | ||
|
|
d8ab9abbf4 | ||
|
|
1a71cc719d | ||
|
|
4b3a6d701e | ||
|
|
63e7f870c2 | ||
|
|
109d3a6a56 | ||
|
|
ce645ea5e6 | ||
|
|
8806ada2a4 | ||
|
|
2dc93891ab | ||
|
|
4a7586a83f | ||
|
|
ed7d758e9a | ||
|
|
c641b87978 | ||
|
|
28076b7c97 | ||
|
|
e1271ee34c | ||
|
|
f5c5de8530 | ||
|
|
46c32cef0e | ||
|
|
755fd592bb | ||
|
|
3cb3b86568 | ||
|
|
2ee2e49ac5 | ||
|
|
c0b805c7dc | ||
|
|
152b1a2c8f | ||
|
|
9043fe2599 | ||
|
|
56a198aba8 | ||
|
|
5dccaa115b | ||
|
|
26f4f54c91 | ||
|
|
e89fe653fd | ||
|
|
ab2bb46123 | ||
|
|
b91267330e | ||
|
|
d85ad7e2ed | ||
|
|
6841d28c6f | ||
|
|
7bd630023a | ||
|
|
c7533ba696 | ||
|
|
ad8b3d8222 | ||
|
|
7f4291566c | ||
|
|
f0d75731e0 | ||
|
|
77cbaef46a | ||
|
|
2441483128 | ||
|
|
2ab590df05 | ||
|
|
9cc531274b | ||
|
|
2cd8b4e49a | ||
|
|
aeecb06b1e | ||
|
|
2e29ea41f0 | ||
|
|
5cc721859e | ||
|
|
4126223a86 | ||
|
|
81e6517758 | ||
|
|
bb16855614 | ||
|
|
b0437209ef | ||
|
|
417cce822c | ||
|
|
b4440419d8 | ||
|
|
60ec77b003 | ||
|
|
a8c8239eb6 | ||
|
|
c416dcdfae | ||
|
|
23904f6805 | ||
|
|
5203bc06da | ||
|
|
2d28dba982 | ||
|
|
269246d9e7 | ||
|
|
55f7879c98 | ||
|
|
d9b2b5ed51 | ||
|
|
4cf64c51f7 | ||
|
|
fe61b74344 | ||
|
|
edcb3e4b1e | ||
|
|
12a46cd4a1 | ||
|
|
51379dc225 | ||
|
|
0db8a55dc4 | ||
|
|
2922326512 | ||
|
|
a923db6a4b | ||
|
|
2455631714 | ||
|
|
162ad3a767 | ||
|
|
ca48f5f409 | ||
|
|
ffa8a68c9f | ||
|
|
39d99e8fa4 | ||
|
|
522e116ada | ||
|
|
b89597bb36 | ||
|
|
fb2312a925 | ||
|
|
ef025592df | ||
|
|
c0464bb4c9 | ||
|
|
ac1e87b973 | ||
|
|
958027e55f | ||
|
|
1ff3f2a033 | ||
|
|
3aedb35ed4 | ||
|
|
409e94058f | ||
|
|
54d43ec4b0 | ||
|
|
875b65f385 | ||
|
|
251095afb5 | ||
|
|
cc9b6f8c6f | ||
|
|
98316c249d | ||
|
|
9324e7076d | ||
|
|
e7ef1f75eb | ||
|
|
9b29a63ff5 | ||
|
|
25807d1884 | ||
|
|
2abc410148 | ||
|
|
2bd0871a77 | ||
|
|
b7dabffd85 | ||
|
|
b800859dea | ||
|
|
9848519841 | ||
|
|
0e970f1f85 |
@@ -0,0 +1,104 @@
|
||||
image: fedora/latest
|
||||
packages:
|
||||
- tar
|
||||
environment:
|
||||
GOROOT: /tmp/go
|
||||
GOPATH: /home/build
|
||||
GOBIN: /home/build/bin
|
||||
sources:
|
||||
- https://github.com/PuerkitoBio/goquery
|
||||
tasks:
|
||||
- setup: |
|
||||
mkdir bin
|
||||
mkdir -p src/github.com/PuerkitoBio
|
||||
mv goquery src/github.com/PuerkitoBio/
|
||||
- go1_7: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.7.6.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_8: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.8.7.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_9: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.9.7.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_10: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.10.8.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_11: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.11.13.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_12: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.12.17.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_13: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.13.15.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_14: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.14.12.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_15: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.15.5.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
- go1_16: |
|
||||
cd /tmp
|
||||
rm -rf go
|
||||
curl -Lo golang.tar.gz https://golang.org/dl/go1.16beta1.linux-amd64.tar.gz
|
||||
tar xvf golang.tar.gz
|
||||
cd /home/build/src/github.com/PuerkitoBio/goquery
|
||||
/tmp/go/bin/go version
|
||||
/tmp/go/bin/go get -t .
|
||||
/tmp/go/bin/go test -v
|
||||
@@ -0,0 +1 @@
|
||||
testdata/* linguist-vendored
|
||||
@@ -0,0 +1,2 @@
|
||||
github: [mna]
|
||||
custom: ["https://www.buymeacoffee.com/mna"]
|
||||
+10
-1
@@ -1,7 +1,16 @@
|
||||
# editor temporary files
|
||||
*.sublime-*
|
||||
.DS_Store
|
||||
*.swp
|
||||
#*.*#
|
||||
tags
|
||||
goquery.test
|
||||
|
||||
# direnv config
|
||||
.env*
|
||||
|
||||
# test binaries
|
||||
*.test
|
||||
|
||||
# coverage and profilte outputs
|
||||
*.out
|
||||
|
||||
|
||||
+27
-2
@@ -1,6 +1,31 @@
|
||||
arch:
|
||||
- amd64
|
||||
- ppc64le
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.1
|
||||
- 1.2
|
||||
- 1.7.x
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
- 1.15.x
|
||||
- tip
|
||||
|
||||
jobs:
|
||||
exclude:
|
||||
- arch: ppc64le
|
||||
go: 1.7.x
|
||||
- arch: ppc64le
|
||||
go: 1.8.x
|
||||
- arch: ppc64le
|
||||
go: 1.9.x
|
||||
- arch: ppc64le
|
||||
go: 1.10.x
|
||||
- arch: ppc64le
|
||||
go: 1.11.x
|
||||
- arch: ppc64le
|
||||
go: 1.12.x
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012-2014, Martin Angers & Contributors
|
||||
Copyright (c) 2012-2021, Martin Angers & Contributors
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
# goquery - a little like that j-thing, only in Go
|
||||
[](https://builds.sr.ht/~mna/goquery/commits/fedora.yml?) [](http://travis-ci.org/PuerkitoBio/goquery) [](https://pkg.go.dev/github.com/PuerkitoBio/goquery) [](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge)
|
||||
|
||||
[](http://travis-ci.org/PuerkitoBio/goquery)
|
||||
goquery brings a syntax and a set of features similar to [jQuery][] to the [Go language][go]. It is based on Go's [net/html package][html] and the CSS Selector library [cascadia][]. Since the net/html parser returns nodes, and not a full-featured DOM tree, jQuery's stateful manipulation functions (like height(), css(), detach()) have been left off.
|
||||
|
||||
[](http://godoc.org/github.com/PuerkitoBio/goquery)
|
||||
|
||||
GoQuery brings a syntax and a set of features similar to [jQuery][] to the [Go language][go]. It is based on Go's [net/html package][html] and the CSS Selector library [cascadia][]. Since the net/html parser returns tokens (nodes), and not a full-featured DOM object, jQuery's manipulation and modification functions have been left off (no point in modifying data in the parsed tree of the HTML, it has no effect).
|
||||
|
||||
Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML.
|
||||
|
||||
Supported functions are query-oriented features (`hasClass()`, `attr()` and the likes), as well as traversing functions that make sense given what we have to work with. This makes GoQuery a great library for scraping web pages.
|
||||
Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML. See the [wiki][] for various options to do this.
|
||||
|
||||
Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Installation](#installation)
|
||||
* [Changelog](#changelog)
|
||||
* [API](#api)
|
||||
* [Examples](#examples)
|
||||
* [Related Projects](#related-projects)
|
||||
* [Support](#support)
|
||||
* [License](#license)
|
||||
|
||||
## Installation
|
||||
|
||||
Please note that because of the net/html dependency, goquery requires Go1.1+.
|
||||
Please note that because of the net/html dependency, goquery requires Go1.1+ and is tested on Go1.7+.
|
||||
|
||||
$ go get github.com/PuerkitoBio/goquery
|
||||
|
||||
(optional) To run unit tests:
|
||||
|
||||
|
||||
$ cd $GOPATH/src/github.com/PuerkitoBio/goquery
|
||||
$ go test
|
||||
|
||||
@@ -32,6 +37,26 @@ 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.**
|
||||
|
||||
* **2021-06-14 (v1.7.0)** : Add `Single` and `SingleMatcher` functions to optimize first-match selection (thanks [@gdollardollar](https://github.com/gdollardollar)).
|
||||
* **2021-01-11 (v1.6.1)** : Fix panic when calling `{Prepend,Append,Set}Html` on a `Selection` that contains non-Element nodes.
|
||||
* **2020-10-08 (v1.6.0)** : Parse html in context of the container node for all functions that deal with html strings (`AfterHtml`, `AppendHtml`, etc.). Thanks to [@thiemok][thiemok] and [@davidjwilkins][djw] for their work on this.
|
||||
* **2020-02-04 (v1.5.1)** : Update module dependencies.
|
||||
* **2018-11-15 (v1.5.0)** : Go module support (thanks @Zaba505).
|
||||
* **2018-06-07 (v1.4.1)** : Add `NewDocumentFromReader` examples.
|
||||
* **2018-03-24 (v1.4.0)** : Deprecate `NewDocument(url)` and `NewDocumentFromResponse(response)`.
|
||||
* **2018-01-28 (v1.3.0)** : Add `ToEnd` constant to `Slice` until the end of the selection (thanks to @davidjwilkins for raising the issue).
|
||||
* **2018-01-11 (v1.2.0)** : Add `AddBack*` and deprecate `AndSelf` (thanks to @davidjwilkins).
|
||||
* **2017-02-12 (v1.1.0)** : Add `SetHtml` and `SetText` (thanks to @glebtv).
|
||||
* **2016-12-29 (v1.0.2)** : Optimize allocations for `Selection.Text` (thanks to @radovskyb).
|
||||
* **2016-08-28 (v1.0.1)** : Optimize performance for large documents.
|
||||
* **2016-07-27 (v1.0.0)** : Tag version 1.0.0.
|
||||
* **2016-06-15** : Invalid selector strings internally compile to a `Matcher` implementation that never matches any node (instead of a panic). So for example, `doc.Find("~")` returns an empty `*Selection` object.
|
||||
* **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 [doc][] 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.
|
||||
* **v0.3.1** : Add `NewDocumentFromResponse()` (thanks assassingj) which allows creating a goquery document from an http response.
|
||||
* **v0.3.0** : Add `EachWithBreak()` which allows to break out of an `Each()` loop by returning false. This function was added instead of changing the existing `Each()` to avoid breaking compatibility.
|
||||
@@ -42,25 +67,33 @@ Please note that because of the net/html dependency, goquery requires Go1.1+.
|
||||
|
||||
## API
|
||||
|
||||
GoQuery exposes two classes, `Document` and `Selection`. Unlike jQuery, which is loaded as part of a DOM document, and thus acts on its containing document, GoQuery doesn't know which HTML document to act upon. So it needs to be told, and that's what the `Document` class is for. It holds the root document node as the initial Selection object to manipulate.
|
||||
goquery exposes two structs, `Document` and `Selection`, and the `Matcher` interface. Unlike jQuery, which is loaded as part of a DOM document, and thus acts on its containing document, goquery doesn't know which HTML document to act upon. So it needs to be told, and that's what the `Document` type is for. It holds the root document node as the initial Selection value to manipulate.
|
||||
|
||||
jQuery often has many variants for the same function (no argument, a selector string argument, a jQuery object argument, a DOM element argument, ...). Instead of exposing the same features in GoQuery as a single method with variadic empty interface arguments, I use statically-typed signatures following this naming convention:
|
||||
jQuery often has many variants for the same function (no argument, a selector string argument, a jQuery object argument, a DOM element argument, ...). Instead of exposing the same features in goquery as a single method with variadic empty interface arguments, statically-typed signatures are used following this naming convention:
|
||||
|
||||
* When the jQuery equivalent can be called with no argument, it has the same name as jQuery for the no argument signature (e.g.: `Prev()`), and the version with a selector string argument is called `XxxFiltered()` (e.g.: `PrevFiltered()`)
|
||||
* When the jQuery equivalent **requires** one argument, the same name as jQuery is used for the selector string version (e.g.: `Is()`)
|
||||
* The signatures accepting a jQuery object as argument are defined in GoQuery as `XxxSelection()` and take a `*Selection` object as argument (e.g.: `FilterSelection()`)
|
||||
* The signatures accepting a DOM element as argument in jQuery are defined in GoQuery as `XxxNodes()` and take a variadic argument of type `*html.Node` (e.g.: `FilterNodes()`)
|
||||
* Finally, 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 signatures accepting a jQuery object as argument are defined in goquery as `XxxSelection()` and take a `*Selection` object as argument (e.g.: `FilterSelection()`)
|
||||
* The signatures accepting a DOM element as argument in jQuery are defined in goquery as `XxxNodes()` and take a variadic argument of type `*html.Node` (e.g.: `FilterNodes()`)
|
||||
* 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()`)
|
||||
|
||||
GoQuery's complete [godoc reference documentation can be found here][doc].
|
||||
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).
|
||||
|
||||
Please note that Cascadia's selectors do NOT necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details.
|
||||
The complete [package 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. Invalid selector strings compile to a `Matcher` that fails to match any node. Behaviour of the various functions that take a selector string as argument follows from that fact, e.g. (where `~` is an invalid selector string):
|
||||
|
||||
* `Find("~")` returns an empty selection because the selector string doesn't match anything.
|
||||
* `Add("~")` returns a new selection that holds the same nodes as the original selection, because it didn't add any node (selector string didn't match anything).
|
||||
* `ParentsFiltered("~")` returns an empty selection because the selector string doesn't match anything.
|
||||
* `ParentsUntil("~")` returns all parents of the selection because the selector string didn't match any element to stop before the top element.
|
||||
|
||||
## Examples
|
||||
|
||||
See some tips and tricks in the [wiki][].
|
||||
|
||||
Taken (and adapted as if executed from outside the goquery package) from example_test.go:
|
||||
Adapted from example_test.go:
|
||||
|
||||
```Go
|
||||
package main
|
||||
@@ -68,21 +101,34 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
func ExampleScrape() {
|
||||
doc, err := goquery.NewDocument("http://metalsucks.net")
|
||||
// Request the HTML page.
|
||||
res, err := http.Get("http://metalsucks.net")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
|
||||
}
|
||||
|
||||
// Load the HTML document
|
||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
doc.Find(".reviews-wrap article .review-rhs").Each(func(i int, s *goquery.Selection) {
|
||||
band := s.Find("h3").Text()
|
||||
title := s.Find("i").Text()
|
||||
fmt.Printf("Review %d: %s - %s\n", i, band, title)
|
||||
})
|
||||
// Find the review items
|
||||
doc.Find(".left-content article .post-title").Each(func(i int, s *goquery.Selection) {
|
||||
// For each item found, get the title
|
||||
title := s.Find("a").Text()
|
||||
fmt.Printf("Review %d: %s\n", i, title)
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -90,18 +136,55 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
## Related Projects
|
||||
|
||||
- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags.
|
||||
- [andybalholm/cascadia][cascadia], the CSS selector library used by goquery.
|
||||
- [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors.
|
||||
- [gocolly/colly](https://github.com/gocolly/colly), a lightning fast and elegant Scraping Framework
|
||||
- [gnulnx/goperf](https://github.com/gnulnx/goperf), a website performance test tool that also fetches static assets.
|
||||
- [MontFerret/ferret](https://github.com/MontFerret/ferret), declarative web scraping.
|
||||
- [tacusci/berrycms](https://github.com/tacusci/berrycms), a modern simple to use CMS with easy to write plugins
|
||||
- [Dataflow kit](https://github.com/slotix/dataflowkit), Web Scraping framework for Gophers.
|
||||
- [Geziyor](https://github.com/geziyor/geziyor), a fast web crawling & scraping framework for Go. Supports JS rendering.
|
||||
- [Pagser](https://github.com/foolin/pagser), a simple, easy, extensible, configurable HTML parser to struct based on goquery and struct tags.
|
||||
- [stitcherd](https://github.com/vhodges/stitcherd), A server for doing server side includes using css selectors and DOM updates.
|
||||
|
||||
## Support
|
||||
|
||||
There are a number of ways you can support the project:
|
||||
|
||||
* Use it, star it, build something with it, spread the word!
|
||||
- If you do build something open-source or otherwise publicly-visible, let me know so I can add it to the [Related Projects](#related-projects) section!
|
||||
* Raise issues to improve the project (note: doc typos and clarifications are issues too!)
|
||||
- Please search existing issues before opening a new one - it may have already been adressed.
|
||||
* Pull requests: please discuss new code in an issue first, unless the fix is really trivial.
|
||||
- Make sure new code is tested.
|
||||
- Be mindful of existing code - PRs that break existing code have a high probability of being declined, unless it fixes a serious issue.
|
||||
* Sponsor the developer
|
||||
- See the Github Sponsor button at the top of the repo on github
|
||||
- or via BuyMeACoffee.com, below
|
||||
|
||||
<a href="https://www.buymeacoffee.com/mna" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
|
||||
|
||||
## License
|
||||
|
||||
The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic].
|
||||
|
||||
[jquery]: http://jquery.com/
|
||||
[go]: http://golang.org/
|
||||
[cascadia]: http://code.google.com/p/cascadia/
|
||||
[cascadia]: https://github.com/andybalholm/cascadia
|
||||
[cascadiacli]: https://github.com/suntong/cascadia
|
||||
[bsd]: http://opensource.org/licenses/BSD-3-Clause
|
||||
[golic]: http://golang.org/LICENSE
|
||||
[caslic]: http://code.google.com/p/cascadia/source/browse/LICENSE
|
||||
[doc]: http://godoc.org/github.com/PuerkitoBio/goquery
|
||||
[caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE
|
||||
[doc]: https://pkg.go.dev/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]: https://pkg.go.dev/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
|
||||
[goq]: https://github.com/andrewstuart/goq
|
||||
[thiemok]: https://github.com/thiemok
|
||||
[djw]: https://github.com/davidjwilkins
|
||||
|
||||
@@ -4,6 +4,16 @@ import (
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
const (
|
||||
maxUint = ^uint(0)
|
||||
maxInt = int(maxUint >> 1)
|
||||
|
||||
// ToEnd is a special index value that can be used as end index in a call
|
||||
// to Slice so that all elements are selected until the end of the Selection.
|
||||
// It is equivalent to passing (*Selection).Length().
|
||||
ToEnd = maxInt
|
||||
)
|
||||
|
||||
// First reduces the set of matched elements to the first in the set.
|
||||
// It returns a new Selection object, and an empty Selection object if the
|
||||
// the selection is empty.
|
||||
@@ -35,12 +45,23 @@ func (s *Selection) Eq(index int) *Selection {
|
||||
}
|
||||
|
||||
// Slice reduces the set of matched elements to a subset specified by a range
|
||||
// of indices.
|
||||
func (s *Selection) Slice(start int, end int) *Selection {
|
||||
// of indices. The start index is 0-based and indicates the index of the first
|
||||
// element to select. The end index is 0-based and indicates the index at which
|
||||
// the elements stop being selected (the end index is not selected).
|
||||
//
|
||||
// The indices may be negative, in which case they represent an offset from the
|
||||
// end of the selection.
|
||||
//
|
||||
// The special value ToEnd may be specified as end index, in which case all elements
|
||||
// until the end are selected. This works both for a positive and negative start
|
||||
// index.
|
||||
func (s *Selection) Slice(start, end int) *Selection {
|
||||
if start < 0 {
|
||||
start += len(s.Nodes)
|
||||
}
|
||||
if end < 0 {
|
||||
if end == ToEnd {
|
||||
end = len(s.Nodes)
|
||||
} else if end < 0 {
|
||||
end += len(s.Nodes)
|
||||
}
|
||||
return pushStack(s, s.Nodes[start:end])
|
||||
@@ -76,6 +97,17 @@ func (s *Selection) IndexSelector(selector string) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// IndexMatcher returns the position of the first element within the
|
||||
// Selection object relative to the elements matched by the matcher, or -1 if
|
||||
// not found.
|
||||
func (s *Selection) IndexMatcher(m Matcher) int {
|
||||
if len(s.Nodes) > 0 {
|
||||
sel := s.document.FindMatcher(m)
|
||||
return indexInSlice(sel.Nodes, s.Nodes[0])
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// IndexOfNode returns the position of the specified node within the Selection
|
||||
// object, or -1 if not found.
|
||||
func (s *Selection) IndexOfNode(node *html.Node) int {
|
||||
|
||||
+79
-25
@@ -6,23 +6,28 @@ import (
|
||||
|
||||
func TestFirst(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").First()
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestFirstEmpty(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-zzcontentzz").First()
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestFirstInvalid(t *testing.T) {
|
||||
sel := Doc().Find("").First()
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestFirstRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.First().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestLast(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Last()
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
|
||||
// Should contain Footer
|
||||
foot := Doc().Find(".footer")
|
||||
@@ -33,23 +38,28 @@ func TestLast(t *testing.T) {
|
||||
|
||||
func TestLastEmpty(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-zzcontentzz").Last()
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestLastInvalid(t *testing.T) {
|
||||
sel := Doc().Find("").Last()
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestLastRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Last().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestEq(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Eq(1)
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestEqNegative(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Eq(-1)
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
|
||||
// Should contain Footer
|
||||
foot := Doc().Find(".footer")
|
||||
@@ -60,65 +70,102 @@ func TestEqNegative(t *testing.T) {
|
||||
|
||||
func TestEqEmpty(t *testing.T) {
|
||||
sel := Doc().Find("something_random_that_does_not_exists").Eq(0)
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestEqInvalid(t *testing.T) {
|
||||
sel := Doc().Find("").Eq(0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestEqInvalidPositive(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Eq(3)
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestEqInvalidNegative(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Eq(-4)
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestEqRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Eq(1).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestSlice(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Slice(0, 2)
|
||||
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel, "#pc1", "#pc2")
|
||||
}
|
||||
|
||||
func TestSliceToEnd(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Slice(1, ToEnd)
|
||||
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel.Eq(0), "#pc2")
|
||||
if _, ok := sel.Eq(1).Attr("id"); ok {
|
||||
t.Error("Want no attribute ID, got one")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceEmpty(t *testing.T) {
|
||||
defer assertPanic(t)
|
||||
Doc().Find("x").Slice(0, 2)
|
||||
}
|
||||
|
||||
func TestSliceInvalid(t *testing.T) {
|
||||
defer assertPanic(t)
|
||||
Doc().Find("").Slice(0, 2)
|
||||
}
|
||||
|
||||
func TestSliceInvalidToEnd(t *testing.T) {
|
||||
defer assertPanic(t)
|
||||
Doc().Find("").Slice(2, ToEnd)
|
||||
}
|
||||
|
||||
func TestSliceOutOfBounds(t *testing.T) {
|
||||
defer AssertPanic(t)
|
||||
defer assertPanic(t)
|
||||
Doc().Find(".pvk-content").Slice(2, 12)
|
||||
}
|
||||
|
||||
func TestNegativeSliceStart(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").Slice(-2, 3)
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
AssertSelectionIs(t, sel.Eq(0), "#cf3")
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
assertSelectionIs(t, sel.Eq(0), "#cf3")
|
||||
}
|
||||
|
||||
func TestNegativeSliceEnd(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").Slice(1, -1)
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
AssertSelectionIs(t, sel.Eq(0), "#cf2")
|
||||
AssertSelectionIs(t, sel.Eq(1), "#cf3")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel.Eq(0), "#cf2")
|
||||
assertSelectionIs(t, sel.Eq(1), "#cf3")
|
||||
}
|
||||
|
||||
func TestNegativeSliceBoth(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").Slice(-3, -1)
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
AssertSelectionIs(t, sel.Eq(0), "#cf2")
|
||||
AssertSelectionIs(t, sel.Eq(1), "#cf3")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel.Eq(0), "#cf2")
|
||||
assertSelectionIs(t, sel.Eq(1), "#cf3")
|
||||
}
|
||||
|
||||
func TestNegativeSliceToEnd(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").Slice(-3, ToEnd)
|
||||
assertLength(t, sel.Nodes, 3)
|
||||
assertSelectionIs(t, sel, "#cf2", "#cf3", "#cf4")
|
||||
}
|
||||
|
||||
func TestNegativeSliceOutOfBounds(t *testing.T) {
|
||||
defer AssertPanic(t)
|
||||
defer assertPanic(t)
|
||||
Doc().Find(".container-fluid").Slice(-12, -7)
|
||||
}
|
||||
|
||||
func TestSliceRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Slice(0, 2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
@@ -138,7 +185,7 @@ func TestGetNegative(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetInvalid(t *testing.T) {
|
||||
defer AssertPanic(t)
|
||||
defer assertPanic(t)
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel.Get(129)
|
||||
}
|
||||
@@ -157,6 +204,13 @@ func TestIndexSelector(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexSelectorInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".hero-unit")
|
||||
if i := sel.IndexSelector(""); i != -1 {
|
||||
t.Errorf("Expected index of -1, got %v.", i)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexOfNode(t *testing.T) {
|
||||
sel := Doc().Find("div.pvk-gutter")
|
||||
if i := sel.IndexOfNode(sel.Nodes[1]); i != 1 {
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
BenchmarkFirst-4 30000000 50.7 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkLast-4 30000000 50.9 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkEq-4 30000000 55.7 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkSlice-4 500000000 3.45 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGet-4 2000000000 1.68 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIndex-4 3000000 541 ns/op 248 B/op 10 allocs/op
|
||||
BenchmarkIndexSelector-4 200000 10749 ns/op 2464 B/op 17 allocs/op
|
||||
BenchmarkIndexOfNode-4 200000000 6.47 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIndexOfSelection-4 200000000 7.27 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMetalReviewExample-4 10000 138426 ns/op 12240 B/op 319 allocs/op
|
||||
BenchmarkAdd-4 200000 10192 ns/op 208 B/op 9 allocs/op
|
||||
BenchmarkAddSelection-4 10000000 158 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkAddNodes-4 10000000 156 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkAndSelf-4 1000000 1588 ns/op 1008 B/op 5 allocs/op
|
||||
BenchmarkFilter-4 100000 20427 ns/op 360 B/op 8 allocs/op
|
||||
BenchmarkNot-4 100000 23508 ns/op 136 B/op 5 allocs/op
|
||||
BenchmarkFilterFunction-4 50000 34178 ns/op 22976 B/op 755 allocs/op
|
||||
BenchmarkNotFunction-4 50000 38173 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkFilterNodes-4 50000 34001 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkNotNodes-4 30000 40344 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkFilterSelection-4 50000 33308 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkNotSelection-4 30000 40748 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkHas-4 5000 263346 ns/op 1816 B/op 48 allocs/op
|
||||
BenchmarkHasNodes-4 10000 160840 ns/op 21184 B/op 752 allocs/op
|
||||
BenchmarkHasSelection-4 10000 165410 ns/op 21184 B/op 752 allocs/op
|
||||
BenchmarkEnd-4 2000000000 1.01 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkEach-4 300000 4664 ns/op 3304 B/op 118 allocs/op
|
||||
BenchmarkMap-4 200000 8286 ns/op 5572 B/op 184 allocs/op
|
||||
BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op
|
||||
BenchmarkAttr-4 100000000 21.6 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkText-4 200000 8909 ns/op 7536 B/op 110 allocs/op
|
||||
BenchmarkLength-4 2000000000 0.34 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkHtml-4 3000000 422 ns/op 120 B/op 2 allocs/op
|
||||
BenchmarkIs-4 100000 22615 ns/op 88 B/op 4 allocs/op
|
||||
BenchmarkIsPositional-4 50000 26655 ns/op 1112 B/op 10 allocs/op
|
||||
BenchmarkIsFunction-4 1000000 1208 ns/op 784 B/op 28 allocs/op
|
||||
BenchmarkIsSelection-4 50000 33497 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkIsNodes-4 50000 33572 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkHasClass-4 10000 232802 ns/op 14944 B/op 976 allocs/op
|
||||
BenchmarkContains-4 200000000 7.33 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFind-4 200000 10715 ns/op 2464 B/op 17 allocs/op
|
||||
BenchmarkFindWithinSelection-4 50000 35878 ns/op 2176 B/op 78 allocs/op
|
||||
BenchmarkFindSelection-4 10000 194356 ns/op 2672 B/op 82 allocs/op
|
||||
BenchmarkFindNodes-4 10000 195510 ns/op 2672 B/op 82 allocs/op
|
||||
BenchmarkContents-4 1000000 2252 ns/op 864 B/op 34 allocs/op
|
||||
BenchmarkContentsFiltered-4 500000 3015 ns/op 1016 B/op 39 allocs/op
|
||||
BenchmarkChildren-4 5000000 364 ns/op 152 B/op 7 allocs/op
|
||||
BenchmarkChildrenFiltered-4 1000000 2212 ns/op 352 B/op 15 allocs/op
|
||||
BenchmarkParent-4 50000 24643 ns/op 4048 B/op 381 allocs/op
|
||||
BenchmarkParentFiltered-4 50000 25967 ns/op 4248 B/op 388 allocs/op
|
||||
BenchmarkParents-4 30000 50000 ns/op 27776 B/op 830 allocs/op
|
||||
BenchmarkParentsFiltered-4 30000 53107 ns/op 28360 B/op 838 allocs/op
|
||||
BenchmarkParentsUntil-4 100000 22423 ns/op 10352 B/op 353 allocs/op
|
||||
BenchmarkParentsUntilSelection-4 20000 86925 ns/op 51144 B/op 1516 allocs/op
|
||||
BenchmarkParentsUntilNodes-4 20000 87597 ns/op 51144 B/op 1516 allocs/op
|
||||
BenchmarkParentsFilteredUntil-4 300000 5568 ns/op 2232 B/op 86 allocs/op
|
||||
BenchmarkParentsFilteredUntilSelection-4 200000 10966 ns/op 5440 B/op 190 allocs/op
|
||||
BenchmarkParentsFilteredUntilNodes-4 200000 10919 ns/op 5440 B/op 190 allocs/op
|
||||
BenchmarkSiblings-4 30000 46018 ns/op 15400 B/op 204 allocs/op
|
||||
BenchmarkSiblingsFiltered-4 30000 50566 ns/op 16496 B/op 213 allocs/op
|
||||
BenchmarkNext-4 200000 7921 ns/op 3216 B/op 112 allocs/op
|
||||
BenchmarkNextFiltered-4 200000 8804 ns/op 3416 B/op 118 allocs/op
|
||||
BenchmarkNextAll-4 50000 31098 ns/op 9912 B/op 138 allocs/op
|
||||
BenchmarkNextAllFiltered-4 50000 34677 ns/op 11008 B/op 147 allocs/op
|
||||
BenchmarkPrev-4 200000 7920 ns/op 3216 B/op 112 allocs/op
|
||||
BenchmarkPrevFiltered-4 200000 8913 ns/op 3416 B/op 118 allocs/op
|
||||
BenchmarkPrevAll-4 200000 10845 ns/op 4376 B/op 113 allocs/op
|
||||
BenchmarkPrevAllFiltered-4 100000 12030 ns/op 4576 B/op 119 allocs/op
|
||||
BenchmarkNextUntil-4 100000 19193 ns/op 5760 B/op 260 allocs/op
|
||||
BenchmarkNextUntilSelection-4 50000 34829 ns/op 18480 B/op 542 allocs/op
|
||||
BenchmarkNextUntilNodes-4 100000 14459 ns/op 7944 B/op 248 allocs/op
|
||||
BenchmarkPrevUntil-4 20000 66296 ns/op 12856 B/op 448 allocs/op
|
||||
BenchmarkPrevUntilSelection-4 30000 45037 ns/op 23432 B/op 689 allocs/op
|
||||
BenchmarkPrevUntilNodes-4 200000 11525 ns/op 6152 B/op 203 allocs/op
|
||||
BenchmarkNextFilteredUntil-4 100000 12940 ns/op 4512 B/op 173 allocs/op
|
||||
BenchmarkNextFilteredUntilSelection-4 50000 38924 ns/op 19160 B/op 567 allocs/op
|
||||
BenchmarkNextFilteredUntilNodes-4 50000 38528 ns/op 19160 B/op 567 allocs/op
|
||||
BenchmarkPrevFilteredUntil-4 100000 12980 ns/op 4664 B/op 175 allocs/op
|
||||
BenchmarkPrevFilteredUntilSelection-4 50000 39671 ns/op 19936 B/op 587 allocs/op
|
||||
BenchmarkPrevFilteredUntilNodes-4 50000 39484 ns/op 19936 B/op 587 allocs/op
|
||||
BenchmarkClosest-4 500000 3310 ns/op 160 B/op 8 allocs/op
|
||||
BenchmarkClosestSelection-4 5000000 361 ns/op 96 B/op 6 allocs/op
|
||||
BenchmarkClosestNodes-4 5000000 359 ns/op 96 B/op 6 allocs/op
|
||||
PASS
|
||||
ok github.com/PuerkitoBio/goquery 163.718s
|
||||
@@ -0,0 +1,85 @@
|
||||
BenchmarkFirst-4 30000000 50.9 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkLast-4 30000000 50.0 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkEq-4 30000000 50.5 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkSlice-4 500000000 3.53 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGet-4 2000000000 1.66 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIndex-4 2000000 832 ns/op 248 B/op 10 allocs/op
|
||||
BenchmarkIndexSelector-4 100000 16073 ns/op 3839 B/op 21 allocs/op
|
||||
BenchmarkIndexOfNode-4 200000000 6.38 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIndexOfSelection-4 200000000 7.14 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMetalReviewExample-4 10000 140737 ns/op 12418 B/op 320 allocs/op
|
||||
BenchmarkAdd-4 100000 13162 ns/op 974 B/op 10 allocs/op
|
||||
BenchmarkAddSelection-4 500000 3160 ns/op 814 B/op 2 allocs/op
|
||||
BenchmarkAddNodes-4 500000 3159 ns/op 814 B/op 2 allocs/op
|
||||
BenchmarkAndSelf-4 200000 7423 ns/op 2404 B/op 9 allocs/op
|
||||
BenchmarkFilter-4 100000 19671 ns/op 360 B/op 8 allocs/op
|
||||
BenchmarkNot-4 100000 22577 ns/op 136 B/op 5 allocs/op
|
||||
BenchmarkFilterFunction-4 50000 33960 ns/op 22976 B/op 755 allocs/op
|
||||
BenchmarkNotFunction-4 50000 37909 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkFilterNodes-4 50000 34196 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkNotNodes-4 30000 40446 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkFilterSelection-4 50000 33091 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkNotSelection-4 30000 40609 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkHas-4 5000 262936 ns/op 2371 B/op 50 allocs/op
|
||||
BenchmarkHasNodes-4 10000 148631 ns/op 21184 B/op 752 allocs/op
|
||||
BenchmarkHasSelection-4 10000 153117 ns/op 21184 B/op 752 allocs/op
|
||||
BenchmarkEnd-4 2000000000 1.02 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkEach-4 300000 4653 ns/op 3304 B/op 118 allocs/op
|
||||
BenchmarkMap-4 200000 8257 ns/op 5572 B/op 184 allocs/op
|
||||
BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op
|
||||
BenchmarkAttr-4 100000000 22.0 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkText-4 200000 8913 ns/op 7536 B/op 110 allocs/op
|
||||
BenchmarkLength-4 2000000000 0.35 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkHtml-4 5000000 398 ns/op 120 B/op 2 allocs/op
|
||||
BenchmarkIs-4 100000 22392 ns/op 88 B/op 4 allocs/op
|
||||
BenchmarkIsPositional-4 50000 26259 ns/op 1112 B/op 10 allocs/op
|
||||
BenchmarkIsFunction-4 1000000 1212 ns/op 784 B/op 28 allocs/op
|
||||
BenchmarkIsSelection-4 50000 33222 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkIsNodes-4 50000 33408 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkHasClass-4 10000 233208 ns/op 14944 B/op 976 allocs/op
|
||||
BenchmarkContains-4 200000000 7.57 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFind-4 100000 16121 ns/op 3839 B/op 21 allocs/op
|
||||
BenchmarkFindWithinSelection-4 20000 68019 ns/op 11521 B/op 97 allocs/op
|
||||
BenchmarkFindSelection-4 5000 387582 ns/op 59787 B/op 176 allocs/op
|
||||
BenchmarkFindNodes-4 5000 389246 ns/op 59797 B/op 176 allocs/op
|
||||
BenchmarkContents-4 200000 11475 ns/op 2878 B/op 42 allocs/op
|
||||
BenchmarkContentsFiltered-4 200000 11222 ns/op 2498 B/op 46 allocs/op
|
||||
BenchmarkChildren-4 2000000 650 ns/op 152 B/op 7 allocs/op
|
||||
BenchmarkChildrenFiltered-4 500000 2568 ns/op 352 B/op 15 allocs/op
|
||||
BenchmarkParent-4 2000 702513 ns/op 194478 B/op 828 allocs/op
|
||||
BenchmarkParentFiltered-4 2000 690778 ns/op 194658 B/op 835 allocs/op
|
||||
BenchmarkParents-4 10000 124855 ns/op 49869 B/op 868 allocs/op
|
||||
BenchmarkParentsFiltered-4 10000 128535 ns/op 50456 B/op 876 allocs/op
|
||||
BenchmarkParentsUntil-4 20000 72982 ns/op 23802 B/op 388 allocs/op
|
||||
BenchmarkParentsUntilSelection-4 10000 156099 ns/op 72453 B/op 1549 allocs/op
|
||||
BenchmarkParentsUntilNodes-4 10000 156610 ns/op 72455 B/op 1549 allocs/op
|
||||
BenchmarkParentsFilteredUntil-4 100000 15549 ns/op 4068 B/op 94 allocs/op
|
||||
BenchmarkParentsFilteredUntilSelection-4 100000 20564 ns/op 7276 B/op 198 allocs/op
|
||||
BenchmarkParentsFilteredUntilNodes-4 100000 20635 ns/op 7276 B/op 198 allocs/op
|
||||
BenchmarkSiblings-4 3000 565114 ns/op 205910 B/op 336 allocs/op
|
||||
BenchmarkSiblingsFiltered-4 3000 580264 ns/op 206993 B/op 345 allocs/op
|
||||
BenchmarkNext-4 20000 93177 ns/op 26810 B/op 169 allocs/op
|
||||
BenchmarkNextFiltered-4 20000 94171 ns/op 27013 B/op 175 allocs/op
|
||||
BenchmarkNextAll-4 5000 270320 ns/op 89289 B/op 237 allocs/op
|
||||
BenchmarkNextAllFiltered-4 5000 275283 ns/op 90375 B/op 246 allocs/op
|
||||
BenchmarkPrev-4 20000 92777 ns/op 26810 B/op 169 allocs/op
|
||||
BenchmarkPrevFiltered-4 20000 95577 ns/op 27007 B/op 175 allocs/op
|
||||
BenchmarkPrevAll-4 20000 86339 ns/op 27515 B/op 151 allocs/op
|
||||
BenchmarkPrevAllFiltered-4 20000 87759 ns/op 27715 B/op 157 allocs/op
|
||||
BenchmarkNextUntil-4 10000 163930 ns/op 48541 B/op 330 allocs/op
|
||||
BenchmarkNextUntilSelection-4 30000 56382 ns/op 23880 B/op 556 allocs/op
|
||||
BenchmarkNextUntilNodes-4 100000 18883 ns/op 8703 B/op 252 allocs/op
|
||||
BenchmarkPrevUntil-4 3000 484668 ns/op 145402 B/op 611 allocs/op
|
||||
BenchmarkPrevUntilSelection-4 20000 72125 ns/op 28865 B/op 705 allocs/op
|
||||
BenchmarkPrevUntilNodes-4 100000 14722 ns/op 6510 B/op 205 allocs/op
|
||||
BenchmarkNextFilteredUntil-4 50000 39006 ns/op 10990 B/op 192 allocs/op
|
||||
BenchmarkNextFilteredUntilSelection-4 20000 66048 ns/op 25641 B/op 586 allocs/op
|
||||
BenchmarkNextFilteredUntilNodes-4 20000 65314 ns/op 25640 B/op 586 allocs/op
|
||||
BenchmarkPrevFilteredUntil-4 50000 33312 ns/op 9709 B/op 189 allocs/op
|
||||
BenchmarkPrevFilteredUntilSelection-4 20000 64197 ns/op 24981 B/op 601 allocs/op
|
||||
BenchmarkPrevFilteredUntilNodes-4 20000 64505 ns/op 24982 B/op 601 allocs/op
|
||||
BenchmarkClosest-4 500000 4065 ns/op 160 B/op 8 allocs/op
|
||||
BenchmarkClosestSelection-4 2000000 756 ns/op 96 B/op 6 allocs/op
|
||||
BenchmarkClosestNodes-4 2000000 753 ns/op 96 B/op 6 allocs/op
|
||||
PASS
|
||||
ok github.com/PuerkitoBio/goquery 162.053s
|
||||
@@ -0,0 +1,85 @@
|
||||
BenchmarkFirst-4 30000000 51.8 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkLast-4 30000000 50.1 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkEq-4 30000000 51.4 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkSlice-4 500000000 3.52 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGet-4 2000000000 1.65 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIndex-4 2000000 787 ns/op 248 B/op 10 allocs/op
|
||||
BenchmarkIndexSelector-4 100000 16952 ns/op 3839 B/op 21 allocs/op
|
||||
BenchmarkIndexOfNode-4 200000000 6.42 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIndexOfSelection-4 200000000 7.12 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMetalReviewExample-4 10000 141994 ns/op 12418 B/op 320 allocs/op
|
||||
BenchmarkAdd-4 200000 10367 ns/op 208 B/op 9 allocs/op
|
||||
BenchmarkAddSelection-4 10000000 152 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkAddNodes-4 10000000 147 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkAndSelf-4 1000000 1647 ns/op 1008 B/op 5 allocs/op
|
||||
BenchmarkFilter-4 100000 19522 ns/op 360 B/op 8 allocs/op
|
||||
BenchmarkNot-4 100000 22546 ns/op 136 B/op 5 allocs/op
|
||||
BenchmarkFilterFunction-4 50000 35087 ns/op 22976 B/op 755 allocs/op
|
||||
BenchmarkNotFunction-4 50000 39123 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkFilterNodes-4 50000 34890 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkNotNodes-4 30000 41145 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkFilterSelection-4 50000 33735 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkNotSelection-4 30000 41334 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkHas-4 5000 264058 ns/op 2370 B/op 50 allocs/op
|
||||
BenchmarkHasNodes-4 10000 151718 ns/op 21184 B/op 752 allocs/op
|
||||
BenchmarkHasSelection-4 10000 156955 ns/op 21184 B/op 752 allocs/op
|
||||
BenchmarkEnd-4 2000000000 1.01 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkEach-4 300000 4660 ns/op 3304 B/op 118 allocs/op
|
||||
BenchmarkMap-4 200000 8404 ns/op 5572 B/op 184 allocs/op
|
||||
BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op
|
||||
BenchmarkAttr-4 100000000 21.6 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkText-4 200000 8911 ns/op 7536 B/op 110 allocs/op
|
||||
BenchmarkLength-4 2000000000 0.34 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkHtml-4 3000000 405 ns/op 120 B/op 2 allocs/op
|
||||
BenchmarkIs-4 100000 22228 ns/op 88 B/op 4 allocs/op
|
||||
BenchmarkIsPositional-4 50000 26469 ns/op 1112 B/op 10 allocs/op
|
||||
BenchmarkIsFunction-4 1000000 1240 ns/op 784 B/op 28 allocs/op
|
||||
BenchmarkIsSelection-4 50000 33709 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkIsNodes-4 50000 33711 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkHasClass-4 10000 236005 ns/op 14944 B/op 976 allocs/op
|
||||
BenchmarkContains-4 200000000 7.47 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFind-4 100000 16075 ns/op 3839 B/op 21 allocs/op
|
||||
BenchmarkFindWithinSelection-4 30000 41418 ns/op 3539 B/op 82 allocs/op
|
||||
BenchmarkFindSelection-4 10000 209490 ns/op 5616 B/op 89 allocs/op
|
||||
BenchmarkFindNodes-4 10000 208206 ns/op 5614 B/op 89 allocs/op
|
||||
BenchmarkContents-4 300000 4751 ns/op 1420 B/op 36 allocs/op
|
||||
BenchmarkContentsFiltered-4 300000 5454 ns/op 1570 B/op 41 allocs/op
|
||||
BenchmarkChildren-4 3000000 527 ns/op 152 B/op 7 allocs/op
|
||||
BenchmarkChildrenFiltered-4 1000000 2484 ns/op 352 B/op 15 allocs/op
|
||||
BenchmarkParent-4 50000 34724 ns/op 6940 B/op 387 allocs/op
|
||||
BenchmarkParentFiltered-4 50000 35596 ns/op 7141 B/op 394 allocs/op
|
||||
BenchmarkParents-4 20000 62094 ns/op 30720 B/op 837 allocs/op
|
||||
BenchmarkParentsFiltered-4 20000 63223 ns/op 31304 B/op 845 allocs/op
|
||||
BenchmarkParentsUntil-4 50000 30391 ns/op 11828 B/op 358 allocs/op
|
||||
BenchmarkParentsUntilSelection-4 20000 99962 ns/op 54075 B/op 1523 allocs/op
|
||||
BenchmarkParentsUntilNodes-4 20000 98763 ns/op 54073 B/op 1523 allocs/op
|
||||
BenchmarkParentsFilteredUntil-4 200000 7982 ns/op 2787 B/op 88 allocs/op
|
||||
BenchmarkParentsFilteredUntilSelection-4 100000 13618 ns/op 5995 B/op 192 allocs/op
|
||||
BenchmarkParentsFilteredUntilNodes-4 100000 13639 ns/op 5994 B/op 192 allocs/op
|
||||
BenchmarkSiblings-4 20000 75287 ns/op 28453 B/op 225 allocs/op
|
||||
BenchmarkSiblingsFiltered-4 20000 80139 ns/op 29543 B/op 234 allocs/op
|
||||
BenchmarkNext-4 100000 14270 ns/op 4659 B/op 117 allocs/op
|
||||
BenchmarkNextFiltered-4 100000 15352 ns/op 4860 B/op 123 allocs/op
|
||||
BenchmarkNextAll-4 20000 60811 ns/op 22771 B/op 157 allocs/op
|
||||
BenchmarkNextAllFiltered-4 20000 69079 ns/op 23871 B/op 166 allocs/op
|
||||
BenchmarkPrev-4 100000 14417 ns/op 4659 B/op 117 allocs/op
|
||||
BenchmarkPrevFiltered-4 100000 15443 ns/op 4859 B/op 123 allocs/op
|
||||
BenchmarkPrevAll-4 100000 22008 ns/op 7346 B/op 120 allocs/op
|
||||
BenchmarkPrevAllFiltered-4 100000 23212 ns/op 7544 B/op 126 allocs/op
|
||||
BenchmarkNextUntil-4 50000 30589 ns/op 8767 B/op 267 allocs/op
|
||||
BenchmarkNextUntilSelection-4 30000 40875 ns/op 19862 B/op 546 allocs/op
|
||||
BenchmarkNextUntilNodes-4 100000 15987 ns/op 8134 B/op 249 allocs/op
|
||||
BenchmarkPrevUntil-4 20000 98799 ns/op 25727 B/op 467 allocs/op
|
||||
BenchmarkPrevUntilSelection-4 30000 51874 ns/op 24875 B/op 694 allocs/op
|
||||
BenchmarkPrevUntilNodes-4 100000 12901 ns/op 6334 B/op 204 allocs/op
|
||||
BenchmarkNextFilteredUntil-4 100000 19869 ns/op 5909 B/op 177 allocs/op
|
||||
BenchmarkNextFilteredUntilSelection-4 30000 45412 ns/op 20557 B/op 571 allocs/op
|
||||
BenchmarkNextFilteredUntilNodes-4 30000 45363 ns/op 20557 B/op 571 allocs/op
|
||||
BenchmarkPrevFilteredUntil-4 100000 19357 ns/op 6033 B/op 179 allocs/op
|
||||
BenchmarkPrevFilteredUntilSelection-4 30000 46396 ns/op 21305 B/op 591 allocs/op
|
||||
BenchmarkPrevFilteredUntilNodes-4 30000 46133 ns/op 21305 B/op 591 allocs/op
|
||||
BenchmarkClosest-4 500000 3448 ns/op 160 B/op 8 allocs/op
|
||||
BenchmarkClosestSelection-4 3000000 528 ns/op 96 B/op 6 allocs/op
|
||||
BenchmarkClosestNodes-4 3000000 523 ns/op 96 B/op 6 allocs/op
|
||||
PASS
|
||||
ok github.com/PuerkitoBio/goquery 162.012s
|
||||
@@ -0,0 +1,86 @@
|
||||
BenchmarkFirst-4 30000000 51.7 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkLast-4 30000000 51.9 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkEq-4 30000000 50.0 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkSlice-4 500000000 3.47 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkGet-4 2000000000 1.68 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIndex-4 2000000 804 ns/op 248 B/op 10 allocs/op
|
||||
BenchmarkIndexSelector-4 100000 16285 ns/op 3839 B/op 21 allocs/op
|
||||
BenchmarkIndexOfNode-4 200000000 6.50 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkIndexOfSelection-4 200000000 7.02 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkMetalReviewExample-4 10000 143160 ns/op 12417 B/op 320 allocs/op
|
||||
BenchmarkAdd-4 200000 10326 ns/op 208 B/op 9 allocs/op
|
||||
BenchmarkAddSelection-4 10000000 155 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkAddNodes-4 10000000 156 ns/op 48 B/op 1 allocs/op
|
||||
BenchmarkAddNodesBig-4 20000 94439 ns/op 21847 B/op 37 allocs/op
|
||||
BenchmarkAndSelf-4 1000000 1791 ns/op 1008 B/op 5 allocs/op
|
||||
BenchmarkFilter-4 100000 19470 ns/op 360 B/op 8 allocs/op
|
||||
BenchmarkNot-4 100000 22500 ns/op 136 B/op 5 allocs/op
|
||||
BenchmarkFilterFunction-4 50000 34578 ns/op 22976 B/op 755 allocs/op
|
||||
BenchmarkNotFunction-4 50000 38703 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkFilterNodes-4 50000 34486 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkNotNodes-4 30000 41094 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkFilterSelection-4 50000 33623 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkNotSelection-4 30000 41483 ns/op 29120 B/op 757 allocs/op
|
||||
BenchmarkHas-4 5000 266628 ns/op 2371 B/op 50 allocs/op
|
||||
BenchmarkHasNodes-4 10000 152617 ns/op 21184 B/op 752 allocs/op
|
||||
BenchmarkHasSelection-4 10000 156682 ns/op 21184 B/op 752 allocs/op
|
||||
BenchmarkEnd-4 2000000000 1.00 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkEach-4 300000 4712 ns/op 3304 B/op 118 allocs/op
|
||||
BenchmarkMap-4 200000 8434 ns/op 5572 B/op 184 allocs/op
|
||||
BenchmarkEachWithBreak-4 2000000 819 ns/op 560 B/op 20 allocs/op
|
||||
BenchmarkAttr-4 100000000 21.7 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkText-4 200000 9376 ns/op 7536 B/op 110 allocs/op
|
||||
BenchmarkLength-4 2000000000 0.35 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkHtml-4 5000000 401 ns/op 120 B/op 2 allocs/op
|
||||
BenchmarkIs-4 100000 22214 ns/op 88 B/op 4 allocs/op
|
||||
BenchmarkIsPositional-4 50000 26559 ns/op 1112 B/op 10 allocs/op
|
||||
BenchmarkIsFunction-4 1000000 1228 ns/op 784 B/op 28 allocs/op
|
||||
BenchmarkIsSelection-4 50000 33471 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkIsNodes-4 50000 34461 ns/op 20960 B/op 749 allocs/op
|
||||
BenchmarkHasClass-4 10000 232429 ns/op 14944 B/op 976 allocs/op
|
||||
BenchmarkContains-4 200000000 7.62 ns/op 0 B/op 0 allocs/op
|
||||
BenchmarkFind-4 100000 16114 ns/op 3839 B/op 21 allocs/op
|
||||
BenchmarkFindWithinSelection-4 30000 42520 ns/op 3540 B/op 82 allocs/op
|
||||
BenchmarkFindSelection-4 10000 209801 ns/op 5615 B/op 89 allocs/op
|
||||
BenchmarkFindNodes-4 10000 209082 ns/op 5614 B/op 89 allocs/op
|
||||
BenchmarkContents-4 300000 4836 ns/op 1420 B/op 36 allocs/op
|
||||
BenchmarkContentsFiltered-4 200000 5495 ns/op 1570 B/op 41 allocs/op
|
||||
BenchmarkChildren-4 3000000 527 ns/op 152 B/op 7 allocs/op
|
||||
BenchmarkChildrenFiltered-4 500000 2499 ns/op 352 B/op 15 allocs/op
|
||||
BenchmarkParent-4 50000 34072 ns/op 6942 B/op 387 allocs/op
|
||||
BenchmarkParentFiltered-4 50000 36077 ns/op 7141 B/op 394 allocs/op
|
||||
BenchmarkParents-4 20000 64118 ns/op 30719 B/op 837 allocs/op
|
||||
BenchmarkParentsFiltered-4 20000 63432 ns/op 31303 B/op 845 allocs/op
|
||||
BenchmarkParentsUntil-4 50000 29589 ns/op 11829 B/op 358 allocs/op
|
||||
BenchmarkParentsUntilSelection-4 10000 101033 ns/op 54076 B/op 1523 allocs/op
|
||||
BenchmarkParentsUntilNodes-4 10000 100584 ns/op 54076 B/op 1523 allocs/op
|
||||
BenchmarkParentsFilteredUntil-4 200000 8061 ns/op 2787 B/op 88 allocs/op
|
||||
BenchmarkParentsFilteredUntilSelection-4 100000 13848 ns/op 5995 B/op 192 allocs/op
|
||||
BenchmarkParentsFilteredUntilNodes-4 100000 13766 ns/op 5995 B/op 192 allocs/op
|
||||
BenchmarkSiblings-4 20000 75135 ns/op 28453 B/op 225 allocs/op
|
||||
BenchmarkSiblingsFiltered-4 20000 80532 ns/op 29544 B/op 234 allocs/op
|
||||
BenchmarkNext-4 100000 14200 ns/op 4660 B/op 117 allocs/op
|
||||
BenchmarkNextFiltered-4 100000 15284 ns/op 4859 B/op 123 allocs/op
|
||||
BenchmarkNextAll-4 20000 60889 ns/op 22774 B/op 157 allocs/op
|
||||
BenchmarkNextAllFiltered-4 20000 65125 ns/op 23869 B/op 166 allocs/op
|
||||
BenchmarkPrev-4 100000 14448 ns/op 4659 B/op 117 allocs/op
|
||||
BenchmarkPrevFiltered-4 100000 15444 ns/op 4859 B/op 123 allocs/op
|
||||
BenchmarkPrevAll-4 100000 22019 ns/op 7344 B/op 120 allocs/op
|
||||
BenchmarkPrevAllFiltered-4 100000 23307 ns/op 7545 B/op 126 allocs/op
|
||||
BenchmarkNextUntil-4 50000 30287 ns/op 8766 B/op 267 allocs/op
|
||||
BenchmarkNextUntilSelection-4 30000 41476 ns/op 19862 B/op 546 allocs/op
|
||||
BenchmarkNextUntilNodes-4 100000 16106 ns/op 8133 B/op 249 allocs/op
|
||||
BenchmarkPrevUntil-4 20000 98951 ns/op 25728 B/op 467 allocs/op
|
||||
BenchmarkPrevUntilSelection-4 30000 52390 ns/op 24875 B/op 694 allocs/op
|
||||
BenchmarkPrevUntilNodes-4 100000 12986 ns/op 6334 B/op 204 allocs/op
|
||||
BenchmarkNextFilteredUntil-4 100000 19365 ns/op 5908 B/op 177 allocs/op
|
||||
BenchmarkNextFilteredUntilSelection-4 30000 45334 ns/op 20555 B/op 571 allocs/op
|
||||
BenchmarkNextFilteredUntilNodes-4 30000 45292 ns/op 20556 B/op 571 allocs/op
|
||||
BenchmarkPrevFilteredUntil-4 100000 19412 ns/op 6032 B/op 179 allocs/op
|
||||
BenchmarkPrevFilteredUntilSelection-4 30000 46286 ns/op 21304 B/op 591 allocs/op
|
||||
BenchmarkPrevFilteredUntilNodes-4 30000 46554 ns/op 21305 B/op 591 allocs/op
|
||||
BenchmarkClosest-4 500000 3480 ns/op 160 B/op 8 allocs/op
|
||||
BenchmarkClosestSelection-4 2000000 722 ns/op 96 B/op 6 allocs/op
|
||||
BenchmarkClosestNodes-4 2000000 719 ns/op 96 B/op 6 allocs/op
|
||||
PASS
|
||||
ok github.com/PuerkitoBio/goquery 160.565s
|
||||
+12
-4
@@ -70,7 +70,9 @@ func BenchmarkIndex(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
j = sel.Index()
|
||||
}
|
||||
b.Logf("Index=%d", j)
|
||||
if j != 3 {
|
||||
b.Fatalf("want 3, got %d", j)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIndexSelector(b *testing.B) {
|
||||
@@ -82,7 +84,9 @@ func BenchmarkIndexSelector(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
j = sel.IndexSelector("dd")
|
||||
}
|
||||
b.Logf("IndexSelector=%d", j)
|
||||
if j != 4 {
|
||||
b.Fatalf("want 4, got %d", j)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIndexOfNode(b *testing.B) {
|
||||
@@ -96,7 +100,9 @@ func BenchmarkIndexOfNode(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
j = sel.IndexOfNode(n)
|
||||
}
|
||||
b.Logf("IndexOfNode=%d", j)
|
||||
if j != 2 {
|
||||
b.Fatalf("want 2, got %d", j)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIndexOfSelection(b *testing.B) {
|
||||
@@ -108,5 +114,7 @@ func BenchmarkIndexOfSelection(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
j = sel.IndexOfSelection(sel2)
|
||||
}
|
||||
b.Logf("IndexOfSelection=%d", j)
|
||||
if j != 2 {
|
||||
b.Fatalf("want 2, got %d", j)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ func BenchmarkMetalReviewExample(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
b.StopTimer()
|
||||
doc := LoadDoc("metalreview.html")
|
||||
doc := loadDoc("metalreview.html")
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
doc.Find(".slider-row:nth-child(1) .slider-item").Each(func(i int, s *Selection) {
|
||||
@@ -37,6 +37,4 @@ func BenchmarkMetalReviewExample(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
b.Log(buf.String())
|
||||
b.Logf("MetalReviewExample=%d", n)
|
||||
}
|
||||
|
||||
+36
-4
@@ -17,7 +17,9 @@ func BenchmarkAdd(b *testing.B) {
|
||||
sel.Add("h2[title]")
|
||||
}
|
||||
}
|
||||
b.Logf("Add=%d", n)
|
||||
if n != 43 {
|
||||
b.Fatalf("want 43, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddSelection(b *testing.B) {
|
||||
@@ -34,7 +36,9 @@ func BenchmarkAddSelection(b *testing.B) {
|
||||
sel.AddSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("AddSelection=%d", n)
|
||||
if n != 43 {
|
||||
b.Fatalf("want 43, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddNodes(b *testing.B) {
|
||||
@@ -52,7 +56,33 @@ func BenchmarkAddNodes(b *testing.B) {
|
||||
sel.AddNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("AddNodes=%d", n)
|
||||
if n != 43 {
|
||||
b.Fatalf("want 43, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAddNodesBig(b *testing.B) {
|
||||
var n int
|
||||
|
||||
doc := DocW()
|
||||
sel := doc.Find("li")
|
||||
// make nodes > 1000
|
||||
nodes := sel.Nodes
|
||||
nodes = append(nodes, nodes...)
|
||||
nodes = append(nodes, nodes...)
|
||||
sel = doc.Find("xyz")
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
if n == 0 {
|
||||
n = sel.AddNodes(nodes...).Length()
|
||||
} else {
|
||||
sel.AddNodes(nodes...)
|
||||
}
|
||||
}
|
||||
if n != 373 {
|
||||
b.Fatalf("want 373, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAndSelf(b *testing.B) {
|
||||
@@ -68,5 +98,7 @@ func BenchmarkAndSelf(b *testing.B) {
|
||||
sel.AndSelf()
|
||||
}
|
||||
}
|
||||
b.Logf("AndSelf=%d", n)
|
||||
if n != 44 {
|
||||
b.Fatalf("want 44, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
+36
-12
@@ -17,7 +17,9 @@ func BenchmarkFilter(b *testing.B) {
|
||||
sel.Filter(".toclevel-1")
|
||||
}
|
||||
}
|
||||
b.Logf("Filter=%d", n)
|
||||
if n != 13 {
|
||||
b.Fatalf("want 13, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNot(b *testing.B) {
|
||||
@@ -33,7 +35,9 @@ func BenchmarkNot(b *testing.B) {
|
||||
sel.Filter(".toclevel-2")
|
||||
}
|
||||
}
|
||||
b.Logf("Not=%d", n)
|
||||
if n != 371 {
|
||||
b.Fatalf("want 371, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterFunction(b *testing.B) {
|
||||
@@ -52,7 +56,9 @@ func BenchmarkFilterFunction(b *testing.B) {
|
||||
sel.FilterFunction(f)
|
||||
}
|
||||
}
|
||||
b.Logf("FilterFunction=%d", n)
|
||||
if n != 112 {
|
||||
b.Fatalf("want 112, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotFunction(b *testing.B) {
|
||||
@@ -71,7 +77,9 @@ func BenchmarkNotFunction(b *testing.B) {
|
||||
sel.NotFunction(f)
|
||||
}
|
||||
}
|
||||
b.Logf("NotFunction=%d", n)
|
||||
if n != 261 {
|
||||
b.Fatalf("want 261, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterNodes(b *testing.B) {
|
||||
@@ -89,7 +97,9 @@ func BenchmarkFilterNodes(b *testing.B) {
|
||||
sel.FilterNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("FilterNodes=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotNodes(b *testing.B) {
|
||||
@@ -107,7 +117,9 @@ func BenchmarkNotNodes(b *testing.B) {
|
||||
sel.NotNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("NotNodes=%d", n)
|
||||
if n != 360 {
|
||||
b.Fatalf("want 360, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterSelection(b *testing.B) {
|
||||
@@ -124,7 +136,9 @@ func BenchmarkFilterSelection(b *testing.B) {
|
||||
sel.FilterSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("FilterSelection=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNotSelection(b *testing.B) {
|
||||
@@ -141,7 +155,9 @@ func BenchmarkNotSelection(b *testing.B) {
|
||||
sel.NotSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("NotSelection=%d", n)
|
||||
if n != 360 {
|
||||
b.Fatalf("want 360, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHas(b *testing.B) {
|
||||
@@ -157,7 +173,9 @@ func BenchmarkHas(b *testing.B) {
|
||||
sel.Has(".editsection")
|
||||
}
|
||||
}
|
||||
b.Logf("Has=%d", n)
|
||||
if n != 13 {
|
||||
b.Fatalf("want 13, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHasNodes(b *testing.B) {
|
||||
@@ -175,7 +193,9 @@ func BenchmarkHasNodes(b *testing.B) {
|
||||
sel.HasNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("HasNodes=%d", n)
|
||||
if n != 15 {
|
||||
b.Fatalf("want 15, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHasSelection(b *testing.B) {
|
||||
@@ -192,7 +212,9 @@ func BenchmarkHasSelection(b *testing.B) {
|
||||
sel.HasSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("HasSelection=%d", n)
|
||||
if n != 15 {
|
||||
b.Fatalf("want 15, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEnd(b *testing.B) {
|
||||
@@ -208,5 +230,7 @@ func BenchmarkEnd(b *testing.B) {
|
||||
sel.End()
|
||||
}
|
||||
}
|
||||
b.Logf("End=%d", n)
|
||||
if n != 373 {
|
||||
b.Fatalf("want 373, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-4
@@ -1,6 +1,7 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -19,7 +20,9 @@ func BenchmarkEach(b *testing.B) {
|
||||
n = tmp
|
||||
}
|
||||
}
|
||||
b.Logf("Each=%d", n)
|
||||
if n != 59 {
|
||||
b.Fatalf("want 59, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMap(b *testing.B) {
|
||||
@@ -29,7 +32,7 @@ func BenchmarkMap(b *testing.B) {
|
||||
sel := DocW().Find("td")
|
||||
f := func(i int, s *Selection) string {
|
||||
tmp++
|
||||
return string(tmp)
|
||||
return strconv.Itoa(tmp)
|
||||
}
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
@@ -38,7 +41,9 @@ func BenchmarkMap(b *testing.B) {
|
||||
n = tmp
|
||||
}
|
||||
}
|
||||
b.Logf("Map=%d", n)
|
||||
if n != 59 {
|
||||
b.Fatalf("want 59, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkEachWithBreak(b *testing.B) {
|
||||
@@ -58,5 +63,7 @@ func BenchmarkEachWithBreak(b *testing.B) {
|
||||
n = tmp
|
||||
}
|
||||
}
|
||||
b.Logf("Each=%d", n)
|
||||
if n != 10 {
|
||||
b.Fatalf("want 10, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ func BenchmarkAttr(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
s, _ = sel.Attr("id")
|
||||
}
|
||||
b.Logf("Attr=%s", s)
|
||||
if s != "firstHeading" {
|
||||
b.Fatalf("want firstHeading, got %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkText(b *testing.B) {
|
||||
@@ -34,7 +36,9 @@ func BenchmarkLength(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
n = sel.Length()
|
||||
}
|
||||
b.Logf("Length=%d", n)
|
||||
if n != 14 {
|
||||
b.Fatalf("want 14, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHtml(b *testing.B) {
|
||||
|
||||
+21
-7
@@ -13,7 +13,9 @@ func BenchmarkIs(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
y = sel.Is(".toclevel-2")
|
||||
}
|
||||
b.Logf("Is=%v", y)
|
||||
if !y {
|
||||
b.Fatal("want true")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsPositional(b *testing.B) {
|
||||
@@ -25,7 +27,9 @@ func BenchmarkIsPositional(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
y = sel.Is("li:nth-child(2)")
|
||||
}
|
||||
b.Logf("IsPositional=%v", y)
|
||||
if !y {
|
||||
b.Fatal("want true")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsFunction(b *testing.B) {
|
||||
@@ -40,7 +44,9 @@ func BenchmarkIsFunction(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
y = sel.IsFunction(f)
|
||||
}
|
||||
b.Logf("IsFunction=%v", y)
|
||||
if !y {
|
||||
b.Fatal("want true")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsSelection(b *testing.B) {
|
||||
@@ -53,7 +59,9 @@ func BenchmarkIsSelection(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
y = sel.IsSelection(sel2)
|
||||
}
|
||||
b.Logf("IsSelection=%v", y)
|
||||
if !y {
|
||||
b.Fatal("want true")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkIsNodes(b *testing.B) {
|
||||
@@ -67,7 +75,9 @@ func BenchmarkIsNodes(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
y = sel.IsNodes(nodes...)
|
||||
}
|
||||
b.Logf("IsNodes=%v", y)
|
||||
if !y {
|
||||
b.Fatal("want true")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkHasClass(b *testing.B) {
|
||||
@@ -79,7 +89,9 @@ func BenchmarkHasClass(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
y = sel.HasClass("official")
|
||||
}
|
||||
b.Logf("HasClass=%v", y)
|
||||
if !y {
|
||||
b.Fatal("want true")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkContains(b *testing.B) {
|
||||
@@ -93,5 +105,7 @@ func BenchmarkContains(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
y = sel.Contains(node)
|
||||
}
|
||||
b.Logf("Contains=%v", y)
|
||||
if !y {
|
||||
b.Fatal("want true")
|
||||
}
|
||||
}
|
||||
|
||||
+149
-43
@@ -2,6 +2,8 @@ package goquery
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/andybalholm/cascadia"
|
||||
)
|
||||
|
||||
func BenchmarkFind(b *testing.B) {
|
||||
@@ -15,7 +17,9 @@ func BenchmarkFind(b *testing.B) {
|
||||
DocB().Find("dd")
|
||||
}
|
||||
}
|
||||
b.Logf("Find=%d", n)
|
||||
if n != 41 {
|
||||
b.Fatalf("want 41, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFindWithinSelection(b *testing.B) {
|
||||
@@ -31,7 +35,9 @@ func BenchmarkFindWithinSelection(b *testing.B) {
|
||||
sel.Find("a[class]")
|
||||
}
|
||||
}
|
||||
b.Logf("FindWithinSelection=%d", n)
|
||||
if n != 39 {
|
||||
b.Fatalf("want 39, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFindSelection(b *testing.B) {
|
||||
@@ -48,7 +54,9 @@ func BenchmarkFindSelection(b *testing.B) {
|
||||
sel.FindSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("FindSelection=%d", n)
|
||||
if n != 73 {
|
||||
b.Fatalf("want 73, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFindNodes(b *testing.B) {
|
||||
@@ -66,7 +74,9 @@ func BenchmarkFindNodes(b *testing.B) {
|
||||
sel.FindNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("FindNodes=%d", n)
|
||||
if n != 73 {
|
||||
b.Fatalf("want 73, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkContents(b *testing.B) {
|
||||
@@ -82,7 +92,9 @@ func BenchmarkContents(b *testing.B) {
|
||||
sel.Contents()
|
||||
}
|
||||
}
|
||||
b.Logf("Contents=%d", n)
|
||||
if n != 16 {
|
||||
b.Fatalf("want 16, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkContentsFiltered(b *testing.B) {
|
||||
@@ -98,7 +110,9 @@ func BenchmarkContentsFiltered(b *testing.B) {
|
||||
sel.ContentsFiltered("a[href=\"#Examples\"]")
|
||||
}
|
||||
}
|
||||
b.Logf("ContentsFiltered=%d", n)
|
||||
if n != 1 {
|
||||
b.Fatalf("want 1, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChildren(b *testing.B) {
|
||||
@@ -114,7 +128,9 @@ func BenchmarkChildren(b *testing.B) {
|
||||
sel.Children()
|
||||
}
|
||||
}
|
||||
b.Logf("Children=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkChildrenFiltered(b *testing.B) {
|
||||
@@ -130,7 +146,9 @@ func BenchmarkChildrenFiltered(b *testing.B) {
|
||||
sel.ChildrenFiltered(".editsection")
|
||||
}
|
||||
}
|
||||
b.Logf("ChildrenFiltered=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParent(b *testing.B) {
|
||||
@@ -146,7 +164,9 @@ func BenchmarkParent(b *testing.B) {
|
||||
sel.Parent()
|
||||
}
|
||||
}
|
||||
b.Logf("Parent=%d", n)
|
||||
if n != 55 {
|
||||
b.Fatalf("want 55, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParentFiltered(b *testing.B) {
|
||||
@@ -162,7 +182,9 @@ func BenchmarkParentFiltered(b *testing.B) {
|
||||
sel.ParentFiltered("ul[id]")
|
||||
}
|
||||
}
|
||||
b.Logf("ParentFiltered=%d", n)
|
||||
if n != 4 {
|
||||
b.Fatalf("want 4, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParents(b *testing.B) {
|
||||
@@ -178,7 +200,9 @@ func BenchmarkParents(b *testing.B) {
|
||||
sel.Parents()
|
||||
}
|
||||
}
|
||||
b.Logf("Parents=%d", n)
|
||||
if n != 73 {
|
||||
b.Fatalf("want 73, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParentsFiltered(b *testing.B) {
|
||||
@@ -194,7 +218,9 @@ func BenchmarkParentsFiltered(b *testing.B) {
|
||||
sel.ParentsFiltered("tr")
|
||||
}
|
||||
}
|
||||
b.Logf("ParentsFiltered=%d", n)
|
||||
if n != 18 {
|
||||
b.Fatalf("want 18, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParentsUntil(b *testing.B) {
|
||||
@@ -210,7 +236,9 @@ func BenchmarkParentsUntil(b *testing.B) {
|
||||
sel.ParentsUntil("table")
|
||||
}
|
||||
}
|
||||
b.Logf("ParentsUntil=%d", n)
|
||||
if n != 52 {
|
||||
b.Fatalf("want 52, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParentsUntilSelection(b *testing.B) {
|
||||
@@ -227,7 +255,9 @@ func BenchmarkParentsUntilSelection(b *testing.B) {
|
||||
sel.ParentsUntilSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("ParentsUntilSelection=%d", n)
|
||||
if n != 70 {
|
||||
b.Fatalf("want 70, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParentsUntilNodes(b *testing.B) {
|
||||
@@ -245,7 +275,9 @@ func BenchmarkParentsUntilNodes(b *testing.B) {
|
||||
sel.ParentsUntilNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("ParentsUntilNodes=%d", n)
|
||||
if n != 70 {
|
||||
b.Fatalf("want 70, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParentsFilteredUntil(b *testing.B) {
|
||||
@@ -261,7 +293,9 @@ func BenchmarkParentsFilteredUntil(b *testing.B) {
|
||||
sel.ParentsFilteredUntil(":nth-child(1)", "ul")
|
||||
}
|
||||
}
|
||||
b.Logf("ParentsFilteredUntil=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParentsFilteredUntilSelection(b *testing.B) {
|
||||
@@ -278,7 +312,9 @@ func BenchmarkParentsFilteredUntilSelection(b *testing.B) {
|
||||
sel.ParentsFilteredUntilSelection(":nth-child(1)", sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("ParentsFilteredUntilSelection=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkParentsFilteredUntilNodes(b *testing.B) {
|
||||
@@ -296,7 +332,9 @@ func BenchmarkParentsFilteredUntilNodes(b *testing.B) {
|
||||
sel.ParentsFilteredUntilNodes(":nth-child(1)", nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("ParentsFilteredUntilNodes=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSiblings(b *testing.B) {
|
||||
@@ -312,7 +350,9 @@ func BenchmarkSiblings(b *testing.B) {
|
||||
sel.Siblings()
|
||||
}
|
||||
}
|
||||
b.Logf("Siblings=%d", n)
|
||||
if n != 293 {
|
||||
b.Fatalf("want 293, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSiblingsFiltered(b *testing.B) {
|
||||
@@ -328,7 +368,9 @@ func BenchmarkSiblingsFiltered(b *testing.B) {
|
||||
sel.SiblingsFiltered("[class]")
|
||||
}
|
||||
}
|
||||
b.Logf("SiblingsFiltered=%d", n)
|
||||
if n != 46 {
|
||||
b.Fatalf("want 46, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNext(b *testing.B) {
|
||||
@@ -344,7 +386,9 @@ func BenchmarkNext(b *testing.B) {
|
||||
sel.Next()
|
||||
}
|
||||
}
|
||||
b.Logf("Next=%d", n)
|
||||
if n != 49 {
|
||||
b.Fatalf("want 49, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextFiltered(b *testing.B) {
|
||||
@@ -360,7 +404,9 @@ func BenchmarkNextFiltered(b *testing.B) {
|
||||
sel.NextFiltered("[class]")
|
||||
}
|
||||
}
|
||||
b.Logf("NextFiltered=%d", n)
|
||||
if n != 6 {
|
||||
b.Fatalf("want 6, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextAll(b *testing.B) {
|
||||
@@ -376,7 +422,9 @@ func BenchmarkNextAll(b *testing.B) {
|
||||
sel.NextAll()
|
||||
}
|
||||
}
|
||||
b.Logf("NextAll=%d", n)
|
||||
if n != 234 {
|
||||
b.Fatalf("want 234, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextAllFiltered(b *testing.B) {
|
||||
@@ -392,7 +440,9 @@ func BenchmarkNextAllFiltered(b *testing.B) {
|
||||
sel.NextAllFiltered("[class]")
|
||||
}
|
||||
}
|
||||
b.Logf("NextAllFiltered=%d", n)
|
||||
if n != 33 {
|
||||
b.Fatalf("want 33, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrev(b *testing.B) {
|
||||
@@ -408,7 +458,9 @@ func BenchmarkPrev(b *testing.B) {
|
||||
sel.Prev()
|
||||
}
|
||||
}
|
||||
b.Logf("Prev=%d", n)
|
||||
if n != 49 {
|
||||
b.Fatalf("want 49, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevFiltered(b *testing.B) {
|
||||
@@ -426,7 +478,9 @@ func BenchmarkPrevFiltered(b *testing.B) {
|
||||
}
|
||||
// There is one more Prev li with a class, compared to Next li with a class
|
||||
// (confirmed by looking at the HTML, this is ok)
|
||||
b.Logf("PrevFiltered=%d", n)
|
||||
if n != 7 {
|
||||
b.Fatalf("want 7, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevAll(b *testing.B) {
|
||||
@@ -442,7 +496,9 @@ func BenchmarkPrevAll(b *testing.B) {
|
||||
sel.PrevAll()
|
||||
}
|
||||
}
|
||||
b.Logf("PrevAll=%d", n)
|
||||
if n != 78 {
|
||||
b.Fatalf("want 78, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevAllFiltered(b *testing.B) {
|
||||
@@ -458,7 +514,9 @@ func BenchmarkPrevAllFiltered(b *testing.B) {
|
||||
sel.PrevAllFiltered("[class]")
|
||||
}
|
||||
}
|
||||
b.Logf("PrevAllFiltered=%d", n)
|
||||
if n != 6 {
|
||||
b.Fatalf("want 6, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextUntil(b *testing.B) {
|
||||
@@ -474,7 +532,9 @@ func BenchmarkNextUntil(b *testing.B) {
|
||||
sel.NextUntil(":nth-child(4)")
|
||||
}
|
||||
}
|
||||
b.Logf("NextUntil=%d", n)
|
||||
if n != 84 {
|
||||
b.Fatalf("want 84, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextUntilSelection(b *testing.B) {
|
||||
@@ -491,7 +551,9 @@ func BenchmarkNextUntilSelection(b *testing.B) {
|
||||
sel.NextUntilSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("NextUntilSelection=%d", n)
|
||||
if n != 42 {
|
||||
b.Fatalf("want 42, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextUntilNodes(b *testing.B) {
|
||||
@@ -509,7 +571,9 @@ func BenchmarkNextUntilNodes(b *testing.B) {
|
||||
sel.NextUntilNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("NextUntilNodes=%d", n)
|
||||
if n != 12 {
|
||||
b.Fatalf("want 12, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevUntil(b *testing.B) {
|
||||
@@ -525,7 +589,9 @@ func BenchmarkPrevUntil(b *testing.B) {
|
||||
sel.PrevUntil(":nth-child(4)")
|
||||
}
|
||||
}
|
||||
b.Logf("PrevUntil=%d", n)
|
||||
if n != 238 {
|
||||
b.Fatalf("want 238, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevUntilSelection(b *testing.B) {
|
||||
@@ -542,7 +608,9 @@ func BenchmarkPrevUntilSelection(b *testing.B) {
|
||||
sel.PrevUntilSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("PrevUntilSelection=%d", n)
|
||||
if n != 49 {
|
||||
b.Fatalf("want 49, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevUntilNodes(b *testing.B) {
|
||||
@@ -560,7 +628,9 @@ func BenchmarkPrevUntilNodes(b *testing.B) {
|
||||
sel.PrevUntilNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("PrevUntilNodes=%d", n)
|
||||
if n != 11 {
|
||||
b.Fatalf("want 11, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextFilteredUntil(b *testing.B) {
|
||||
@@ -576,7 +646,9 @@ func BenchmarkNextFilteredUntil(b *testing.B) {
|
||||
sel.NextFilteredUntil("p", "div")
|
||||
}
|
||||
}
|
||||
b.Logf("NextFilteredUntil=%d", n)
|
||||
if n != 22 {
|
||||
b.Fatalf("want 22, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextFilteredUntilSelection(b *testing.B) {
|
||||
@@ -593,7 +665,9 @@ func BenchmarkNextFilteredUntilSelection(b *testing.B) {
|
||||
sel.NextFilteredUntilSelection("p", sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("NextFilteredUntilSelection=%d", n)
|
||||
if n != 22 {
|
||||
b.Fatalf("want 22, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNextFilteredUntilNodes(b *testing.B) {
|
||||
@@ -611,7 +685,9 @@ func BenchmarkNextFilteredUntilNodes(b *testing.B) {
|
||||
sel.NextFilteredUntilNodes("p", nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("NextFilteredUntilNodes=%d", n)
|
||||
if n != 22 {
|
||||
b.Fatalf("want 22, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevFilteredUntil(b *testing.B) {
|
||||
@@ -627,7 +703,9 @@ func BenchmarkPrevFilteredUntil(b *testing.B) {
|
||||
sel.PrevFilteredUntil("p", "div")
|
||||
}
|
||||
}
|
||||
b.Logf("PrevFilteredUntil=%d", n)
|
||||
if n != 20 {
|
||||
b.Fatalf("want 20, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevFilteredUntilSelection(b *testing.B) {
|
||||
@@ -644,7 +722,9 @@ func BenchmarkPrevFilteredUntilSelection(b *testing.B) {
|
||||
sel.PrevFilteredUntilSelection("p", sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("PrevFilteredUntilSelection=%d", n)
|
||||
if n != 20 {
|
||||
b.Fatalf("want 20, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkPrevFilteredUntilNodes(b *testing.B) {
|
||||
@@ -662,7 +742,9 @@ func BenchmarkPrevFilteredUntilNodes(b *testing.B) {
|
||||
sel.PrevFilteredUntilNodes("p", nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("PrevFilteredUntilNodes=%d", n)
|
||||
if n != 20 {
|
||||
b.Fatalf("want 20, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClosest(b *testing.B) {
|
||||
@@ -678,7 +760,9 @@ func BenchmarkClosest(b *testing.B) {
|
||||
sel.Closest(".pvk-content")
|
||||
}
|
||||
}
|
||||
b.Logf("Closest=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClosestSelection(b *testing.B) {
|
||||
@@ -695,7 +779,9 @@ func BenchmarkClosestSelection(b *testing.B) {
|
||||
sel.ClosestSelection(sel2)
|
||||
}
|
||||
}
|
||||
b.Logf("ClosestSelection=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkClosestNodes(b *testing.B) {
|
||||
@@ -712,5 +798,25 @@ func BenchmarkClosestNodes(b *testing.B) {
|
||||
sel.ClosestNodes(nodes...)
|
||||
}
|
||||
}
|
||||
b.Logf("ClosestNodes=%d", n)
|
||||
if n != 2 {
|
||||
b.Fatalf("want 2, got %d", n)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkSingleMatcher(b *testing.B) {
|
||||
doc := Doc()
|
||||
multi := cascadia.MustCompile(`div`)
|
||||
single := SingleMatcher(multi)
|
||||
b.ResetTimer()
|
||||
|
||||
b.Run("multi", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = doc.FindMatcher(multi)
|
||||
}
|
||||
})
|
||||
b.Run("single", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = doc.FindMatcher(single)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) 2012-2014, Martin Angers & Contributors
|
||||
// Copyright (c) 2012-2016, Martin Angers & Contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -25,22 +25,19 @@
|
||||
Package goquery implements features similar to jQuery, including the chainable
|
||||
syntax, to manipulate and query an HTML document.
|
||||
|
||||
GoQuery brings a syntax and a set of features similar to jQuery to the Go language.
|
||||
It is based on Go's net/html package and the CSS Selector library cascadia. Since
|
||||
the net/html parser returns tokens (nodes), and not a full-featured DOM object,
|
||||
jQuery's manipulation and modification functions have been left off (no point in
|
||||
modifying data in the parsed tree of the HTML, it has no effect).
|
||||
It brings a syntax and a set of features similar to jQuery to the Go language.
|
||||
It is based on Go's net/html package and the CSS Selector library cascadia.
|
||||
Since the net/html parser returns nodes, and not a full-featured DOM
|
||||
tree, jQuery's stateful manipulation functions (like height(), css(), detach())
|
||||
have been left off.
|
||||
|
||||
Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is
|
||||
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.
|
||||
|
||||
Supported functions are query-oriented features (`hasClass()`, `attr()` and the likes),
|
||||
as well as traversing functions that make sense given what we have to work with.
|
||||
This makes GoQuery a great library for scraping web pages.
|
||||
|
||||
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, I felt that writing a similar HTML-manipulating
|
||||
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
|
||||
Go's fmt package), even though some of its methods are less than intuitive (looking
|
||||
at you, index()...).
|
||||
@@ -50,7 +47,8 @@ file: https://github.com/puerkitobio/goquery
|
||||
|
||||
Please note that because of the net/html dependency, goquery requires Go1.1+.
|
||||
|
||||
The various methods are split into files based on the category of behavior:
|
||||
The various methods are split into files based on the category of behavior.
|
||||
The three dots (...) indicate that various "overloads" are available.
|
||||
|
||||
* array.go : array-like positional manipulation of the selection.
|
||||
- Eq()
|
||||
@@ -77,8 +75,23 @@ The various methods are split into files based on the category of behavior:
|
||||
- EachWithBreak()
|
||||
- Map()
|
||||
|
||||
* manipulation.go : methods for modifying the document
|
||||
- After...()
|
||||
- Append...()
|
||||
- Before...()
|
||||
- Clone()
|
||||
- Empty()
|
||||
- Prepend...()
|
||||
- Remove...()
|
||||
- ReplaceWith...()
|
||||
- Unwrap()
|
||||
- Wrap...()
|
||||
- WrapAll...()
|
||||
- WrapInner...()
|
||||
|
||||
* property.go : methods that inspect and get the node's properties values.
|
||||
- Attr()
|
||||
- Attr*(), RemoveAttr(), SetAttr()
|
||||
- AddClass(), HasClass(), RemoveClass(), ToggleClass()
|
||||
- Html()
|
||||
- Length()
|
||||
- Size(), which is an alias for Length()
|
||||
@@ -86,7 +99,6 @@ The various methods are split into files based on the category of behavior:
|
||||
|
||||
* query.go : methods that query, or reflect, a node's identity.
|
||||
- Contains()
|
||||
- HasClass()
|
||||
- Is...()
|
||||
|
||||
* traversal.go : methods to traverse the HTML document tree.
|
||||
@@ -98,8 +110,14 @@ The various methods are split into files based on the category of behavior:
|
||||
- Prev...()
|
||||
- Siblings...()
|
||||
|
||||
* type.go : definition of the types exposed by GoQuery.
|
||||
* type.go : definition of the types exposed by goquery.
|
||||
- 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
|
||||
|
||||
+5
-5
@@ -4,16 +4,16 @@
|
||||
|
||||
The `go.net/html` package used by `goquery` requires that the html document is UTF-8 encoded. When you know the encoding of the html page is not UTF-8, you can use the `iconv` package to convert it to UTF-8 (there are various implementation of the `iconv` API, see [godoc.org][iconv] for other options):
|
||||
|
||||
```
|
||||
```bash
|
||||
$ go get -u github.com/djimenez/iconv-go
|
||||
```
|
||||
|
||||
and then:
|
||||
|
||||
```
|
||||
```golang
|
||||
// Load the URL
|
||||
res, err := http.Get(url)
|
||||
if e != nil {
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
defer res.Body.Close()
|
||||
@@ -51,7 +51,7 @@ You can find a code example using `otto` [in this gist][exotto]. Thanks to githu
|
||||
|
||||
If all you need is a normal `for` loop over all nodes in the current selection, where `Map/Each`-style iteration is not necessary, you can use the following:
|
||||
|
||||
```
|
||||
```golang
|
||||
sel := Doc().Find(".selector")
|
||||
for i := range sel.Nodes {
|
||||
single := sel.Eq(i)
|
||||
@@ -65,4 +65,4 @@ Thanks to github user @jmoiron.
|
||||
[otto]: https://github.com/robertkrimen/otto
|
||||
[exotto]: https://gist.github.com/cryptix/87127f76a94183747b53
|
||||
[iconv]: http://godoc.org/?q=iconv
|
||||
[text]: http://godoc.org/code.google.com/p/go.text/encoding
|
||||
[text]: https://godoc.org/golang.org/x/text/encoding
|
||||
|
||||
+88
-10
@@ -1,26 +1,37 @@
|
||||
package goquery
|
||||
package goquery_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
// In real use, this import would be required (not in this example, since it
|
||||
// is part of the goquery package)
|
||||
//"github.com/PuerkitoBio/goquery"
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
// This example scrapes the reviews shown on the home page of metalsucks.net.
|
||||
func ExampleScrape_MetalSucks() {
|
||||
// Load the HTML document (in real use, the type would be *goquery.Document)
|
||||
doc, err := NewDocument("http://metalsucks.net")
|
||||
func Example() {
|
||||
// Request the HTML page.
|
||||
res, err := http.Get("http://metalsucks.net")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
|
||||
}
|
||||
|
||||
// Load the HTML document
|
||||
doc, err := goquery.NewDocumentFromReader(res.Body)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Find the review items (the type of the Selection would be *goquery.Selection)
|
||||
doc.Find(".reviews-wrap article .review-rhs").Each(func(i int, s *Selection) {
|
||||
// Find the review items
|
||||
doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) {
|
||||
// For each item found, get the band and title
|
||||
band := s.Find("h3").Text()
|
||||
band := s.Find("a").Text()
|
||||
title := s.Find("i").Text()
|
||||
fmt.Printf("Review %d: %s - %s\n", i, band, title)
|
||||
})
|
||||
@@ -30,3 +41,70 @@ func ExampleScrape_MetalSucks() {
|
||||
|
||||
// xOutput: voluntarily fail the Example output.
|
||||
}
|
||||
|
||||
// This example shows how to use NewDocumentFromReader from a file.
|
||||
func ExampleNewDocumentFromReader_file() {
|
||||
// create from a file
|
||||
f, err := os.Open("some/file.html")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
doc, err := goquery.NewDocumentFromReader(f)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// use the goquery document...
|
||||
_ = doc.Find("h1")
|
||||
}
|
||||
|
||||
// This example shows how to use NewDocumentFromReader from a string.
|
||||
func ExampleNewDocumentFromReader_string() {
|
||||
// create from a string
|
||||
data := `
|
||||
<html>
|
||||
<head>
|
||||
<title>My document</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Header</h1>
|
||||
</body>
|
||||
</html>`
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
header := doc.Find("h1").Text()
|
||||
fmt.Println(header)
|
||||
|
||||
// Output: Header
|
||||
}
|
||||
|
||||
func ExampleSingle() {
|
||||
html := `
|
||||
<html>
|
||||
<body>
|
||||
<div>1</div>
|
||||
<div>2</div>
|
||||
<div>3</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
doc, err := goquery.NewDocumentFromReader(strings.NewReader(html))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// By default, the selector string selects all matching nodes
|
||||
multiSel := doc.Find("div")
|
||||
fmt.Println(multiSel.Text())
|
||||
|
||||
// Using goquery.Single, only the first match is selected
|
||||
singleSel := doc.FindMatcher(goquery.Single("div"))
|
||||
fmt.Println(singleSel.Text())
|
||||
|
||||
// Output:
|
||||
// 123
|
||||
// 1
|
||||
}
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// Add adds the selector string's matching nodes to those in the current
|
||||
// selection and returns a new Selection object.
|
||||
// The selector string is run in the context of the document of the current
|
||||
// Selection object.
|
||||
func (s *Selection) Add(selector string) *Selection {
|
||||
return s.AddNodes(findWithSelector([]*html.Node{s.document.rootNode}, selector)...)
|
||||
return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, compileMatcher(selector))...)
|
||||
}
|
||||
|
||||
// AddMatcher adds the matcher's matching nodes to those in the current
|
||||
// selection and returns a new Selection object.
|
||||
// The matcher is run in the context of the document of the current
|
||||
// Selection object.
|
||||
func (s *Selection) AddMatcher(m Matcher) *Selection {
|
||||
return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, m)...)
|
||||
}
|
||||
|
||||
// AddSelection adds the specified Selection object's nodes to those in the
|
||||
@@ -29,12 +35,36 @@ func (s *Selection) Union(sel *Selection) *Selection {
|
||||
// AddNodes adds the specified nodes to those in the
|
||||
// current selection and returns a new Selection object.
|
||||
func (s *Selection) AddNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, appendWithoutDuplicates(s.Nodes, nodes))
|
||||
return pushStack(s, appendWithoutDuplicates(s.Nodes, nodes, nil))
|
||||
}
|
||||
|
||||
// AndSelf adds the previous set of elements on the stack to the current set.
|
||||
// It returns a new Selection object containing the current Selection combined
|
||||
// with the previous one.
|
||||
// Deprecated: This function has been deprecated and is now an alias for AddBack().
|
||||
func (s *Selection) AndSelf() *Selection {
|
||||
return s.AddBack()
|
||||
}
|
||||
|
||||
// AddBack adds the previous set of elements on the stack to the current set.
|
||||
// It returns a new Selection object containing the current Selection combined
|
||||
// with the previous one.
|
||||
func (s *Selection) AddBack() *Selection {
|
||||
return s.AddSelection(s.prevSel)
|
||||
}
|
||||
|
||||
// AddBackFiltered reduces the previous set of elements on the stack to those that
|
||||
// match the selector string, and adds them to the current set.
|
||||
// It returns a new Selection object containing the current Selection combined
|
||||
// with the filtered previous one
|
||||
func (s *Selection) AddBackFiltered(selector string) *Selection {
|
||||
return s.AddSelection(s.prevSel.Filter(selector))
|
||||
}
|
||||
|
||||
// AddBackMatcher reduces the previous set of elements on the stack to those that match
|
||||
// the mateher, and adds them to the curernt set.
|
||||
// It returns a new Selection object containing the current Selection combined
|
||||
// with the filtered previous one
|
||||
func (s *Selection) AddBackMatcher(m Matcher) *Selection {
|
||||
return s.AddSelection(s.prevSel.FilterMatcher(m))
|
||||
}
|
||||
|
||||
+61
-11
@@ -6,63 +6,113 @@ import (
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
sel := Doc().Find("div.row-fluid").Add("a")
|
||||
AssertLength(t, sel.Nodes, 19)
|
||||
assertLength(t, sel.Nodes, 19)
|
||||
}
|
||||
|
||||
func TestAddInvalid(t *testing.T) {
|
||||
sel1 := Doc().Find("div.row-fluid")
|
||||
sel2 := sel1.Add("")
|
||||
assertLength(t, sel1.Nodes, 9)
|
||||
assertLength(t, sel2.Nodes, 9)
|
||||
if sel1 == sel2 {
|
||||
t.Errorf("selections should not be the same")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Add("a").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestAddSelection(t *testing.T) {
|
||||
sel := Doc().Find("div.row-fluid")
|
||||
sel2 := Doc().Find("a")
|
||||
sel = sel.AddSelection(sel2)
|
||||
AssertLength(t, sel.Nodes, 19)
|
||||
assertLength(t, sel.Nodes, 19)
|
||||
}
|
||||
|
||||
func TestAddSelectionNil(t *testing.T) {
|
||||
sel := Doc().Find("div.row-fluid")
|
||||
AssertLength(t, sel.Nodes, 9)
|
||||
assertLength(t, sel.Nodes, 9)
|
||||
|
||||
sel = sel.AddSelection(nil)
|
||||
AssertLength(t, sel.Nodes, 9)
|
||||
assertLength(t, sel.Nodes, 9)
|
||||
}
|
||||
|
||||
func TestAddSelectionRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Find("a")
|
||||
sel2 = sel.AddSelection(sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestAddNodes(t *testing.T) {
|
||||
sel := Doc().Find("div.pvk-gutter")
|
||||
sel2 := Doc().Find(".pvk-content")
|
||||
sel = sel.AddNodes(sel2.Nodes...)
|
||||
AssertLength(t, sel.Nodes, 9)
|
||||
assertLength(t, sel.Nodes, 9)
|
||||
}
|
||||
|
||||
func TestAddNodesNone(t *testing.T) {
|
||||
sel := Doc().Find("div.pvk-gutter").AddNodes()
|
||||
AssertLength(t, sel.Nodes, 6)
|
||||
assertLength(t, sel.Nodes, 6)
|
||||
}
|
||||
|
||||
func TestAddNodesRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Find("a")
|
||||
sel2 = sel.AddNodes(sel2.Nodes...).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestAddNodesBig(t *testing.T) {
|
||||
doc := DocW()
|
||||
sel := doc.Find("li")
|
||||
assertLength(t, sel.Nodes, 373)
|
||||
sel2 := doc.Find("xyz")
|
||||
assertLength(t, sel2.Nodes, 0)
|
||||
|
||||
nodes := sel.Nodes
|
||||
sel2 = sel2.AddNodes(nodes...)
|
||||
assertLength(t, sel2.Nodes, 373)
|
||||
nodes2 := append(nodes, nodes...)
|
||||
sel2 = sel2.End().AddNodes(nodes2...)
|
||||
assertLength(t, sel2.Nodes, 373)
|
||||
nodes3 := append(nodes2, nodes...)
|
||||
sel2 = sel2.End().AddNodes(nodes3...)
|
||||
assertLength(t, sel2.Nodes, 373)
|
||||
}
|
||||
|
||||
func TestAndSelf(t *testing.T) {
|
||||
sel := Doc().Find(".span12").Last().AndSelf()
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestAndSelfRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Find("a").AndSelf().End().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestAddBack(t *testing.T) {
|
||||
sel := Doc().Find(".span12").Last().AddBack()
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestAddBackRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Find("a").AddBack().End().End()
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestAddBackFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".span12, .footer").Find("h1").AddBackFiltered(".footer")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestAddBackFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".span12, .footer")
|
||||
sel2 := sel.Find("h1").AddBackFiltered(".footer").End().End()
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
@@ -1,20 +1,30 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"code.google.com/p/cascadia"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// Filter reduces the set of matched elements to those that match the selector string.
|
||||
// It returns a new Selection object for this subset of matching elements.
|
||||
func (s *Selection) Filter(selector string) *Selection {
|
||||
return pushStack(s, winnow(s, selector, true))
|
||||
return s.FilterMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// FilterMatcher reduces the set of matched elements to those that match
|
||||
// the given matcher. It returns a new Selection object for this subset
|
||||
// of matching elements.
|
||||
func (s *Selection) FilterMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, winnow(s, m, true))
|
||||
}
|
||||
|
||||
// Not removes elements from the Selection that match the selector string.
|
||||
// It returns a new Selection object with the matching elements removed.
|
||||
func (s *Selection) Not(selector string) *Selection {
|
||||
return pushStack(s, winnow(s, selector, false))
|
||||
return s.NotMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// NotMatcher removes elements from the Selection that match the given matcher.
|
||||
// It returns a new Selection object with the matching elements removed.
|
||||
func (s *Selection) NotMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, winnow(s, m, false))
|
||||
}
|
||||
|
||||
// FilterFunction reduces the set of matched elements to those that pass the function's test.
|
||||
@@ -72,6 +82,13 @@ func (s *Selection) Has(selector string) *Selection {
|
||||
return s.HasSelection(s.document.Find(selector))
|
||||
}
|
||||
|
||||
// HasMatcher reduces the set of matched elements to those that have a descendant
|
||||
// that matches the matcher.
|
||||
// It returns a new Selection object with the matching elements.
|
||||
func (s *Selection) HasMatcher(m Matcher) *Selection {
|
||||
return s.HasSelection(s.document.FindMatcher(m))
|
||||
}
|
||||
|
||||
// HasNodes reduces the set of matched elements to those that have a
|
||||
// descendant that matches one of the nodes.
|
||||
// It returns a new Selection object with the matching elements.
|
||||
@@ -106,26 +123,34 @@ func (s *Selection) End() *Selection {
|
||||
return newEmptySelection(s.document)
|
||||
}
|
||||
|
||||
// Filter based on a selector string, and the indicator to keep (Filter) or
|
||||
// Filter based on the matcher, and the indicator to keep (Filter) or
|
||||
// to get rid of (Not) the matching elements.
|
||||
func winnow(sel *Selection, selector string, keep bool) []*html.Node {
|
||||
cs := cascadia.MustCompile(selector)
|
||||
|
||||
func winnow(sel *Selection, m Matcher, keep bool) []*html.Node {
|
||||
// Optimize if keep is requested
|
||||
if keep {
|
||||
return cs.Filter(sel.Nodes)
|
||||
return m.Filter(sel.Nodes)
|
||||
}
|
||||
// Use grep
|
||||
return grep(sel, func(i int, s *Selection) bool {
|
||||
return !cs.Match(s.Get(0))
|
||||
return !m.Match(s.Get(0))
|
||||
})
|
||||
}
|
||||
|
||||
// Filter based on an array of nodes, and the indicator to keep (Filter) or
|
||||
// to get rid of (Not) the matching elements.
|
||||
func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node {
|
||||
if len(nodes)+len(sel.Nodes) < minNodesForSet {
|
||||
return grep(sel, func(i int, s *Selection) bool {
|
||||
return isInSlice(nodes, s.Get(0)) == keep
|
||||
})
|
||||
}
|
||||
|
||||
set := make(map[*html.Node]bool)
|
||||
for _, n := range nodes {
|
||||
set[n] = true
|
||||
}
|
||||
return grep(sel, func(i int, s *Selection) bool {
|
||||
return isInSlice(nodes, s.Get(0)) == keep
|
||||
return set[s.Get(0)] == keep
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+44
-29
@@ -6,25 +6,30 @@ import (
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
sel := Doc().Find(".span12").Filter(".alert")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestFilterNone(t *testing.T) {
|
||||
sel := Doc().Find(".span12").Filter(".zzalert")
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestFilterInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".span12").Filter("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestFilterRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Filter(".alert").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestFilterFunction(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").FilterFunction(func(i int, s *Selection) bool {
|
||||
return i > 0
|
||||
})
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestFilterFunctionRollback(t *testing.T) {
|
||||
@@ -32,33 +37,33 @@ func TestFilterFunctionRollback(t *testing.T) {
|
||||
sel2 := sel.FilterFunction(func(i int, s *Selection) bool {
|
||||
return i > 0
|
||||
}).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestFilterNode(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.FilterNodes(sel.Nodes[2])
|
||||
AssertLength(t, sel2.Nodes, 1)
|
||||
assertLength(t, sel2.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestFilterNodeRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.FilterNodes(sel.Nodes[2]).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestFilterSelection(t *testing.T) {
|
||||
sel := Doc().Find(".link")
|
||||
sel2 := Doc().Find("a[ng-click]")
|
||||
sel3 := sel.FilterSelection(sel2)
|
||||
AssertLength(t, sel3.Nodes, 1)
|
||||
assertLength(t, sel3.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestFilterSelectionRollback(t *testing.T) {
|
||||
sel := Doc().Find(".link")
|
||||
sel2 := Doc().Find("a[ng-click]")
|
||||
sel2 = sel.FilterSelection(sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestFilterSelectionNil(t *testing.T) {
|
||||
@@ -66,30 +71,35 @@ func TestFilterSelectionNil(t *testing.T) {
|
||||
|
||||
sel := Doc().Find(".link")
|
||||
sel3 := sel.FilterSelection(sel2)
|
||||
AssertLength(t, sel3.Nodes, 0)
|
||||
assertLength(t, sel3.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestNot(t *testing.T) {
|
||||
sel := Doc().Find(".span12").Not(".alert")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestNotInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".span12").Not("")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestNotRollback(t *testing.T) {
|
||||
sel := Doc().Find(".span12")
|
||||
sel2 := sel.Not(".alert").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNotNone(t *testing.T) {
|
||||
sel := Doc().Find(".span12").Not(".zzalert")
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestNotFunction(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").NotFunction(func(i int, s *Selection) bool {
|
||||
return i > 0
|
||||
})
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestNotFunctionRollback(t *testing.T) {
|
||||
@@ -97,65 +107,70 @@ func TestNotFunctionRollback(t *testing.T) {
|
||||
sel2 := sel.NotFunction(func(i int, s *Selection) bool {
|
||||
return i > 0
|
||||
}).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNotNode(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.NotNodes(sel.Nodes[2])
|
||||
AssertLength(t, sel2.Nodes, 2)
|
||||
assertLength(t, sel2.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestNotNodeRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.NotNodes(sel.Nodes[2]).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNotSelection(t *testing.T) {
|
||||
sel := Doc().Find(".link")
|
||||
sel2 := Doc().Find("a[ng-click]")
|
||||
sel3 := sel.NotSelection(sel2)
|
||||
AssertLength(t, sel3.Nodes, 6)
|
||||
assertLength(t, sel3.Nodes, 6)
|
||||
}
|
||||
|
||||
func TestNotSelectionRollback(t *testing.T) {
|
||||
sel := Doc().Find(".link")
|
||||
sel2 := Doc().Find("a[ng-click]")
|
||||
sel2 = sel.NotSelection(sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestIntersection(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter")
|
||||
sel2 := Doc().Find("div").Intersection(sel)
|
||||
AssertLength(t, sel2.Nodes, 6)
|
||||
assertLength(t, sel2.Nodes, 6)
|
||||
}
|
||||
|
||||
func TestIntersectionRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter")
|
||||
sel2 := Doc().Find("div")
|
||||
sel2 = sel.Intersection(sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestHas(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").Has(".center-content")
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
// Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content
|
||||
}
|
||||
|
||||
func TestHasInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").Has("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestHasRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.Has(".center-content").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestHasNodes(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".center-content")
|
||||
sel = sel.HasNodes(sel2.Nodes...)
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
// Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content
|
||||
}
|
||||
|
||||
@@ -163,29 +178,29 @@ func TestHasNodesRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".center-content")
|
||||
sel2 = sel.HasNodes(sel2.Nodes...).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestHasSelection(t *testing.T) {
|
||||
sel := Doc().Find("p")
|
||||
sel2 := Doc().Find("small")
|
||||
sel = sel.HasSelection(sel2)
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestHasSelectionRollback(t *testing.T) {
|
||||
sel := Doc().Find("p")
|
||||
sel2 := Doc().Find("small")
|
||||
sel2 = sel.HasSelection(sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestEnd(t *testing.T) {
|
||||
sel := Doc().Find("p").Has("small").End()
|
||||
AssertLength(t, sel.Nodes, 4)
|
||||
assertLength(t, sel.Nodes, 4)
|
||||
}
|
||||
|
||||
func TestEndToTop(t *testing.T) {
|
||||
sel := Doc().Find("p").Has("small").End().End().End()
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
module github.com/PuerkitoBio/goquery
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.1.0
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
|
||||
)
|
||||
|
||||
go 1.13
|
||||
@@ -0,0 +1,8 @@
|
||||
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
+8
-2
@@ -1,7 +1,10 @@
|
||||
package goquery
|
||||
|
||||
// Each iterates over a Selection object, executing a function for each
|
||||
// matched element. It returns the current Selection object.
|
||||
// matched element. It returns the current Selection object. The function
|
||||
// f is called for each element in the selection with the index of the
|
||||
// element in that selection starting at 0, and a *Selection that contains
|
||||
// only that element.
|
||||
func (s *Selection) Each(f func(int, *Selection)) *Selection {
|
||||
for i, n := range s.Nodes {
|
||||
f(i, newSingleSelection(n, s.document))
|
||||
@@ -23,7 +26,10 @@ func (s *Selection) EachWithBreak(f func(int, *Selection) bool) *Selection {
|
||||
}
|
||||
|
||||
// Map passes each element in the current matched set through a function,
|
||||
// producing a slice of string holding the returned values.
|
||||
// producing a slice of string holding the returned values. The function
|
||||
// f is called for each element in the selection with the index of the
|
||||
// element in that selection starting at 0, and a *Selection that contains
|
||||
// only that element.
|
||||
func (s *Selection) Map(f func(int, *Selection) string) (result []string) {
|
||||
for i, n := range s.Nodes {
|
||||
result = append(result, f(i, newSingleSelection(n, s.document)))
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ func TestEach(t *testing.T) {
|
||||
if cnt != 4 {
|
||||
t.Errorf("Expected Each() to call function 4 times, got %v times.", cnt)
|
||||
}
|
||||
AssertLength(t, sel.Nodes, 6)
|
||||
assertLength(t, sel.Nodes, 6)
|
||||
}
|
||||
|
||||
func TestEachWithBreak(t *testing.T) {
|
||||
@@ -32,7 +32,7 @@ func TestEachWithBreak(t *testing.T) {
|
||||
if cnt != 1 {
|
||||
t.Errorf("Expected Each() to call function 1 time, got %v times.", cnt)
|
||||
}
|
||||
AssertLength(t, sel.Nodes, 6)
|
||||
assertLength(t, sel.Nodes, 6)
|
||||
}
|
||||
|
||||
func TestEachEmptySelection(t *testing.T) {
|
||||
@@ -46,7 +46,7 @@ func TestEachEmptySelection(t *testing.T) {
|
||||
t.Error("Expected Each() to not be called on empty Selection.")
|
||||
}
|
||||
sel2 := sel.Find("div")
|
||||
AssertLength(t, sel2.Nodes, 0)
|
||||
assertLength(t, sel2.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestMap(t *testing.T) {
|
||||
|
||||
+679
@@ -0,0 +1,679 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// After applies the selector from the root document and inserts the matched elements
|
||||
// after the elements in the set of matched elements.
|
||||
//
|
||||
// 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 as Selection.Append.
|
||||
func (s *Selection) After(selector string) *Selection {
|
||||
return s.AfterMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// AfterMatcher applies the matcher from the root document and inserts the matched elements
|
||||
// after the elements in the set of matched elements.
|
||||
//
|
||||
// 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 as Selection.Append.
|
||||
func (s *Selection) AfterMatcher(m Matcher) *Selection {
|
||||
return s.AfterNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// AfterSelection inserts the elements in the selection after each element in the set of matched
|
||||
// elements.
|
||||
//
|
||||
// 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 as Selection.Append.
|
||||
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.
|
||||
//
|
||||
// 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 {
|
||||
sn.Parent.InsertBefore(n, sn.NextSibling)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Append appends the elements specified by the selector to the end of each element
|
||||
// in the set of matched elements, following those rules:
|
||||
//
|
||||
// 1) The selector is applied to the root document.
|
||||
//
|
||||
// 2) Elements that are part of the document 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 one, which will be moved
|
||||
// as noted in (2).
|
||||
func (s *Selection) Append(selector string) *Selection {
|
||||
return s.AppendMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// AppendMatcher appends the elements specified by the matcher to the end of each element
|
||||
// in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) AppendMatcher(m Matcher) *Selection {
|
||||
return s.AppendNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// AppendSelection appends 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...)
|
||||
}
|
||||
|
||||
// AppendHtml parses the html and appends it to the set of matched elements.
|
||||
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.
|
||||
//
|
||||
// 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)
|
||||
})
|
||||
}
|
||||
|
||||
// Before inserts the matched elements before each element in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) Before(selector string) *Selection {
|
||||
return s.BeforeMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// BeforeMatcher inserts the matched elements before each element in the set of matched elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) BeforeMatcher(m Matcher) *Selection {
|
||||
return s.BeforeNodes(m.MatchAll(s.document.rootNode)...)
|
||||
}
|
||||
|
||||
// BeforeSelection inserts the elements in the selection before each element in the set of matched
|
||||
// elements.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) BeforeSelection(sel *Selection) *Selection {
|
||||
return s.BeforeNodes(sel.Nodes...)
|
||||
}
|
||||
|
||||
// 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(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.
|
||||
//
|
||||
// This follows the same rules as Selection.Append.
|
||||
func (s *Selection) BeforeNodes(ns ...*html.Node) *Selection {
|
||||
return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) {
|
||||
if sn.Parent != nil {
|
||||
sn.Parent.InsertBefore(n, sn)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Clone creates 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
|
||||
}
|
||||
|
||||
// Empty removes all children nodes from the set of matched elements.
|
||||
// It returns the children nodes in a new Selection.
|
||||
func (s *Selection) Empty() *Selection {
|
||||
var nodes []*html.Node
|
||||
|
||||
for _, n := range s.Nodes {
|
||||
for c := n.FirstChild; c != nil; c = n.FirstChild {
|
||||
n.RemoveChild(c)
|
||||
nodes = append(nodes, c)
|
||||
}
|
||||
}
|
||||
|
||||
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(compileMatcher(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(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
|
||||
// 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 {
|
||||
for _, n := range s.Nodes {
|
||||
if n.Parent != nil {
|
||||
n.Parent.RemoveChild(n)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// RemoveFiltered removes from the current set of matched elements those that
|
||||
// match the selector filter. It returns the Selection of removed nodes.
|
||||
//
|
||||
// For example if the selection s contains "<h1>", "<h2>" and "<h3>"
|
||||
// and s.RemoveFiltered("h2") is called, only the "<h2>" node is removed
|
||||
// (and returned), while "<h1>" and "<h3>" are kept in the document.
|
||||
func (s *Selection) RemoveFiltered(selector string) *Selection {
|
||||
return s.RemoveMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// RemoveMatcher removes from the current set of matched elements those that
|
||||
// match the Matcher filter. It returns the Selection of removed nodes.
|
||||
// See RemoveFiltered for additional information.
|
||||
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(compileMatcher(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(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
|
||||
// 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()
|
||||
}
|
||||
|
||||
// SetHtml sets the html content of each element in the selection to
|
||||
// specified html string.
|
||||
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.
|
||||
// The provided text string is escaped.
|
||||
func (s *Selection) SetText(text string) *Selection {
|
||||
return s.SetHtml(html.EscapeString(text))
|
||||
}
|
||||
|
||||
// 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(compileMatcher(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(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-
|
||||
// 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(compileMatcher(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(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 {
|
||||
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(compileMatcher(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(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
|
||||
// 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("goquery: failed to parse HTML: " + err.Error())
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if n.Type != html.ElementNode {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,739 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"log"
|
||||
"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")
|
||||
|
||||
assertLength(t, doc.Find("#main #nf6").Nodes, 0)
|
||||
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
|
||||
assertLength(t, doc.Find("#main + #nf6").Nodes, 1)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAfterMany(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find(".one").After("#nf6")
|
||||
|
||||
assertLength(t, doc.Find("#foot #nf6").Nodes, 1)
|
||||
assertLength(t, doc.Find("#main #nf6").Nodes, 1)
|
||||
assertLength(t, doc.Find(".one + #nf6").Nodes, 2)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAfterWithRemoved(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
s := doc.Find("#main").Remove()
|
||||
s.After("#nf6")
|
||||
|
||||
assertLength(t, s.Find("#nf6").Nodes, 0)
|
||||
assertLength(t, doc.Find("#nf6").Nodes, 0)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAfterSelection(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").AfterSelection(doc.Find("#nf1, #nf2"))
|
||||
|
||||
assertLength(t, doc.Find("#main #nf1, #main #nf2").Nodes, 0)
|
||||
assertLength(t, doc.Find("#foot #nf1, #foot #nf2").Nodes, 0)
|
||||
assertLength(t, doc.Find("#main + #nf1, #nf1 + #nf2").Nodes, 2)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAfterHtml(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").AfterHtml("<strong>new node</strong>")
|
||||
|
||||
assertLength(t, doc.Find("#main + strong").Nodes, 1)
|
||||
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")
|
||||
|
||||
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
|
||||
assertLength(t, doc.Find("#main #nf6").Nodes, 1)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
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)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
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)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAppendSelectionExisting(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").AppendSelection(doc.Find("#n1, #n2"))
|
||||
|
||||
assertClass(t, doc.Find("#main :nth-child(1)"), "three")
|
||||
assertClass(t, doc.Find("#main :nth-child(5)"), "one")
|
||||
assertClass(t, doc.Find("#main :nth-child(6)"), "two")
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
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)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestAppendHtml(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("div").AppendHtml("<strong>new node</strong>")
|
||||
|
||||
assertLength(t, doc.Find("strong").Nodes, 14)
|
||||
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")
|
||||
|
||||
assertLength(t, doc.Find("#main #nf6").Nodes, 0)
|
||||
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
|
||||
assertLength(t, doc.Find("body > #nf6:first-child").Nodes, 1)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestBeforeWithRemoved(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
s := doc.Find("#main").Remove()
|
||||
s.Before("#nf6")
|
||||
|
||||
assertLength(t, s.Find("#nf6").Nodes, 0)
|
||||
assertLength(t, doc.Find("#nf6").Nodes, 0)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestBeforeSelection(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").BeforeSelection(doc.Find("#nf1, #nf2"))
|
||||
|
||||
assertLength(t, doc.Find("#main #nf1, #main #nf2").Nodes, 0)
|
||||
assertLength(t, doc.Find("#foot #nf1, #foot #nf2").Nodes, 0)
|
||||
assertLength(t, doc.Find("body > #nf1:first-child, #nf1 + #nf2").Nodes, 2)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestBeforeHtml(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("#main").BeforeHtml("<strong>new node</strong>")
|
||||
|
||||
assertLength(t, doc.Find("body > strong:first-child").Nodes, 1)
|
||||
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()
|
||||
|
||||
assertLength(t, doc.Find("#main").Children().Nodes, 0)
|
||||
assertLength(t, s.Filter("div").Nodes, 6)
|
||||
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 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()
|
||||
|
||||
assertLength(t, doc.Find("#foot #nf1").Nodes, 0)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestRemoveAll(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("*").Remove()
|
||||
|
||||
assertLength(t, doc.Find("*").Nodes, 0)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestRemoveRoot(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
doc.Find("html").Remove()
|
||||
|
||||
assertLength(t, doc.Find("html").Nodes, 0)
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestRemoveFiltered(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
nf6 := doc.Find("#nf6")
|
||||
s := doc.Find("div").RemoveFiltered("#nf6")
|
||||
|
||||
assertLength(t, doc.Find("#nf6").Nodes, 0)
|
||||
assertLength(t, s.Nodes, 1)
|
||||
if nf6.Nodes[0] != s.Nodes[0] {
|
||||
t.Error("Removed node does not match original")
|
||||
}
|
||||
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 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")
|
||||
q.SetHtml(`<div id="replace">test</div>`)
|
||||
|
||||
assertLength(t, doc.Find("#replace").Nodes, 2)
|
||||
assertLength(t, doc.Find("#main, #foot").Nodes, 2)
|
||||
|
||||
if q.Text() != "testtest" {
|
||||
t.Errorf("Expected text to be %v, found %v", "testtest", q.Text())
|
||||
}
|
||||
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestSetHtmlNoMatch(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
q := doc.Find("#notthere")
|
||||
q.SetHtml(`<div id="replace">test</div>`)
|
||||
|
||||
assertLength(t, doc.Find("#replace").Nodes, 0)
|
||||
|
||||
printSel(t, doc.Selection)
|
||||
}
|
||||
|
||||
func TestSetHtmlEmpty(t *testing.T) {
|
||||
doc := Doc2Clone()
|
||||
q := doc.Find("#main")
|
||||
q.SetHtml(``)
|
||||
|
||||
assertLength(t, doc.Find("#main").Nodes, 1)
|
||||
assertLength(t, doc.Find("#main").Children().Nodes, 0)
|
||||
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")
|
||||
repl := "<div id=\"replace\">test</div>"
|
||||
q.SetText(repl)
|
||||
|
||||
assertLength(t, doc.Find("#replace").Nodes, 0)
|
||||
assertLength(t, doc.Find("#main, #foot").Nodes, 2)
|
||||
|
||||
if q.Text() != (repl + repl) {
|
||||
t.Errorf("Expected text to be %v, found %v", (repl + repl), q.Text())
|
||||
}
|
||||
|
||||
h, err := q.Html()
|
||||
if err != nil {
|
||||
t.Errorf("Error: %v", err)
|
||||
}
|
||||
esc := "<div id="replace">test</div>"
|
||||
if h != esc {
|
||||
t.Errorf("Expected html to be %v, found %v", esc, h)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHtmlWithNonElementNode(t *testing.T) {
|
||||
const data = `
|
||||
<html>
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
This is <span>some</span><b>text</b>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
cases := map[string]func(*Selection, string) *Selection{
|
||||
"AfterHtml": (*Selection).AfterHtml,
|
||||
"AppendHtml": (*Selection).AppendHtml,
|
||||
"BeforeHtml": (*Selection).BeforeHtml,
|
||||
"PrependHtml": (*Selection).PrependHtml,
|
||||
"ReplaceWithHtml": (*Selection).ReplaceWithHtml,
|
||||
"SetHtml": (*Selection).SetHtml,
|
||||
}
|
||||
for nm, fn := range cases {
|
||||
// this test is only to make sure that the HTML parsing/manipulation
|
||||
// methods do not raise panics when executed over Selections that contain
|
||||
// non-Element nodes.
|
||||
t.Run(nm, func(t *testing.T) {
|
||||
doc := loadString(t, data)
|
||||
sel := doc.Find("p").Contents()
|
||||
func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
fn(sel, "<div></div>")
|
||||
}()
|
||||
|
||||
// print the resulting document in verbose mode
|
||||
h, err := OuterHtml(doc.Selection)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
t.Log(h)
|
||||
})
|
||||
}
|
||||
}
|
||||
+27
-27
@@ -1,37 +1,37 @@
|
||||
#!/bin/sh
|
||||
# Copyright 2012 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# git gofmt pre-commit hook
|
||||
#
|
||||
# To use, store as .git/hooks/pre-commit inside your repository and make sure
|
||||
# it has execute permissions.
|
||||
#
|
||||
# This script does not handle file names that contain spaces.
|
||||
echo ">>> golint"
|
||||
for dir in $(go list ./... | grep -v /vendor/)
|
||||
do
|
||||
golint "${dir}"
|
||||
done
|
||||
echo "<<< golint"
|
||||
echo
|
||||
|
||||
# golint is purely informational, it doesn't fail with exit code != 0 if it finds something,
|
||||
# because it may find a lot of false positives. Just print out its result for information.
|
||||
echo "lint result (informational only):"
|
||||
golint .
|
||||
echo ">>> go vet"
|
||||
go vet $(go list ./... | grep -v /vendor/)
|
||||
echo "<<< go vet"
|
||||
echo
|
||||
|
||||
# go vet returns 1 if an error was found. Exit the hook with this exit code.
|
||||
go vet ./...
|
||||
vetres=$?
|
||||
echo ">>> gosimple"
|
||||
gosimple $(go list ./... | grep -v /vendor/)
|
||||
echo "<<< gosimple"
|
||||
echo
|
||||
|
||||
# Check for gofmt problems and report if any.
|
||||
gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$')
|
||||
gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$' | grep -v /vendor/)
|
||||
[ -z "$gofiles" ] && echo "EXIT $vetres" && exit $vetres
|
||||
|
||||
unformatted=$(gofmt -l $gofiles)
|
||||
[ -z "$unformatted" ] && echo "EXIT $vetres" && exit $vetres
|
||||
if [ -n "$gofiles" ]; then
|
||||
unformatted=$(gofmt -l $gofiles)
|
||||
|
||||
# Some files are not gofmt'd. Print message and fail.
|
||||
if [ -n "$unformatted" ]; then
|
||||
# Some files are not gofmt'd.
|
||||
echo >&2 "Go files must be formatted with gofmt. Please run:"
|
||||
for fn in $unformatted; do
|
||||
echo >&2 " gofmt -w $PWD/$fn"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
echo
|
||||
|
||||
echo >&2 "Go files must be formatted with gofmt. Please run:"
|
||||
for fn in $unformatted; do
|
||||
echo >&2 " gofmt -w $PWD/$fn"
|
||||
done
|
||||
|
||||
echo "EXIT 1"
|
||||
exit 1
|
||||
|
||||
+208
-24
@@ -2,10 +2,14 @@ package goquery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var rxClassTrim = regexp.MustCompile("[\t\r\n]")
|
||||
|
||||
// Attr gets the specified attribute's value for the first element in the
|
||||
// Selection. To get the value for each element individually, use a looping
|
||||
// construct such as Each or Map method.
|
||||
@@ -16,15 +20,65 @@ 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 {
|
||||
removeAttr(n, attrName)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// 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 {
|
||||
attr := getAttributePtr(attrName, n)
|
||||
if attr == nil {
|
||||
n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val})
|
||||
} else {
|
||||
attr.Val = val
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// Text gets the combined text contents of each element in the set of matched
|
||||
// elements, including their descendants.
|
||||
func (s *Selection) Text() string {
|
||||
var buf bytes.Buffer
|
||||
|
||||
// Slightly optimized vs calling Each: no single selection object created
|
||||
for _, n := range s.Nodes {
|
||||
buf.WriteString(getNodeText(n))
|
||||
var f func(*html.Node)
|
||||
f = func(n *html.Node) {
|
||||
if n.Type == html.TextNode {
|
||||
// Keep newlines and spaces, like jQuery
|
||||
buf.WriteString(n.Data)
|
||||
}
|
||||
if n.FirstChild != nil {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
f(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, n := range s.Nodes {
|
||||
f(n)
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
@@ -58,34 +112,164 @@ func (s *Selection) Html() (ret string, e error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Get the specified node's text content.
|
||||
func getNodeText(node *html.Node) string {
|
||||
if node.Type == html.TextNode {
|
||||
// Keep newlines and spaces, like jQuery
|
||||
return node.Data
|
||||
} else if node.FirstChild != nil {
|
||||
var buf bytes.Buffer
|
||||
for c := node.FirstChild; c != nil; c = c.NextSibling {
|
||||
buf.WriteString(getNodeText(c))
|
||||
}
|
||||
return buf.String()
|
||||
// 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 or via multiple arguments.
|
||||
func (s *Selection) AddClass(class ...string) *Selection {
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
|
||||
if classStr == "" {
|
||||
return s
|
||||
}
|
||||
|
||||
return ""
|
||||
tcls := getClassesSlice(classStr)
|
||||
for _, n := range s.Nodes {
|
||||
curClasses, attr := getClassesAndAttr(n, true)
|
||||
for _, newClass := range tcls {
|
||||
if !strings.Contains(curClasses, " "+newClass+" ") {
|
||||
curClasses += newClass + " "
|
||||
}
|
||||
}
|
||||
|
||||
setClasses(n, attr, curClasses)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// HasClass determines whether any of the matched elements are assigned the
|
||||
// given class.
|
||||
func (s *Selection) HasClass(class string) bool {
|
||||
class = " " + class + " "
|
||||
for _, n := range s.Nodes {
|
||||
classes, _ := getClassesAndAttr(n, false)
|
||||
if strings.Contains(classes, class) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RemoveClass removes the given class(es) from each element in the set of matched elements.
|
||||
// Multiple class names can be specified, separated by a space or via multiple arguments.
|
||||
// If no class name is provided, all classes are removed.
|
||||
func (s *Selection) RemoveClass(class ...string) *Selection {
|
||||
var rclasses []string
|
||||
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
remove := classStr == ""
|
||||
|
||||
if !remove {
|
||||
rclasses = getClassesSlice(classStr)
|
||||
}
|
||||
|
||||
for _, n := range s.Nodes {
|
||||
if remove {
|
||||
removeAttr(n, "class")
|
||||
} else {
|
||||
classes, attr := getClassesAndAttr(n, true)
|
||||
for _, rcl := range rclasses {
|
||||
classes = strings.Replace(classes, " "+rcl+" ", " ", -1)
|
||||
}
|
||||
|
||||
setClasses(n, attr, classes)
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// ToggleClass adds or removes the given class(es) for each element in the set of matched elements.
|
||||
// Multiple class names can be specified, separated by a space or via multiple arguments.
|
||||
func (s *Selection) ToggleClass(class ...string) *Selection {
|
||||
classStr := strings.TrimSpace(strings.Join(class, " "))
|
||||
|
||||
if classStr == "" {
|
||||
return s
|
||||
}
|
||||
|
||||
tcls := getClassesSlice(classStr)
|
||||
|
||||
for _, n := range s.Nodes {
|
||||
classes, attr := getClassesAndAttr(n, true)
|
||||
for _, tcl := range tcls {
|
||||
if strings.Contains(classes, " "+tcl+" ") {
|
||||
classes = strings.Replace(classes, " "+tcl+" ", " ", -1)
|
||||
} else {
|
||||
classes += tcl + " "
|
||||
}
|
||||
}
|
||||
|
||||
setClasses(n, attr, classes)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func getAttributePtr(attrName string, n *html.Node) *html.Attribute {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i, a := range n.Attr {
|
||||
if a.Key == attrName {
|
||||
return &n.Attr[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Private function to get the specified attribute's value from a node.
|
||||
func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) {
|
||||
if n == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, a := range n.Attr {
|
||||
if a.Key == attrName {
|
||||
val = a.Val
|
||||
exists = true
|
||||
return
|
||||
}
|
||||
if a := getAttributePtr(attrName, n); a != nil {
|
||||
val = a.Val
|
||||
exists = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Get and normalize the "class" attribute from the node.
|
||||
func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) {
|
||||
// Applies only to element nodes
|
||||
if n.Type == html.ElementNode {
|
||||
attr = getAttributePtr("class", n)
|
||||
if attr == nil && create {
|
||||
n.Attr = append(n.Attr, html.Attribute{
|
||||
Key: "class",
|
||||
Val: "",
|
||||
})
|
||||
attr = &n.Attr[len(n.Attr)-1]
|
||||
}
|
||||
}
|
||||
|
||||
if attr == nil {
|
||||
classes = " "
|
||||
} else {
|
||||
classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getClassesSlice(classes string) []string {
|
||||
return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ")
|
||||
}
|
||||
|
||||
func removeAttr(n *html.Node, attrName string) {
|
||||
for i, a := range n.Attr {
|
||||
if a.Key == attrName {
|
||||
n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr =
|
||||
n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1]
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setClasses(n *html.Node, attr *html.Attribute, classes string) {
|
||||
classes = strings.TrimSpace(classes)
|
||||
if classes == "" {
|
||||
removeAttr(n, "class")
|
||||
return
|
||||
}
|
||||
|
||||
attr.Val = classes
|
||||
}
|
||||
|
||||
@@ -14,12 +14,66 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveAttr(t *testing.T) {
|
||||
sel := Doc2Clone().Find("div")
|
||||
|
||||
sel.RemoveAttr("id")
|
||||
|
||||
_, ok := sel.Attr("id")
|
||||
if ok {
|
||||
t.Error("Expected there to be no id attributes set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetAttr(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#main")
|
||||
|
||||
sel.SetAttr("id", "not-main")
|
||||
|
||||
val, ok := sel.Attr("id")
|
||||
if !ok {
|
||||
t.Error("Expected an id attribute on main")
|
||||
}
|
||||
|
||||
if val != "not-main" {
|
||||
t.Errorf("Expected an attribute id to be not-main, got %s", val)
|
||||
}
|
||||
}
|
||||
|
||||
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" {
|
||||
@@ -83,3 +137,116 @@ func TestNbsp(t *testing.T) {
|
||||
t.Errorf("Html: expected a non-breaking space at index 4, got %d", ix)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddClass(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#main")
|
||||
sel.AddClass("main main main")
|
||||
|
||||
// Make sure that class was only added once
|
||||
if a, ok := sel.Attr("class"); !ok || a != "main" {
|
||||
t.Error("Expected #main to have class main")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddClassSimilar(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#nf5")
|
||||
sel.AddClass("odd")
|
||||
|
||||
assertClass(t, sel, "odd")
|
||||
assertClass(t, sel, "odder")
|
||||
printSel(t, sel.Parent())
|
||||
}
|
||||
|
||||
func TestAddEmptyClass(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#main")
|
||||
sel.AddClass("")
|
||||
|
||||
// Make sure that class was only added once
|
||||
if a, ok := sel.Attr("class"); ok {
|
||||
t.Errorf("Expected #main to not to have a class, have: %s", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddClasses(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#main")
|
||||
sel.AddClass("a b")
|
||||
|
||||
// Make sure that class was only added once
|
||||
if !sel.HasClass("a") || !sel.HasClass("b") {
|
||||
t.Errorf("#main does not have classes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClass(t *testing.T) {
|
||||
sel := Doc().Find("div")
|
||||
if !sel.HasClass("span12") {
|
||||
t.Error("Expected at least one div to have class span12.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClassNone(t *testing.T) {
|
||||
sel := Doc().Find("h2")
|
||||
if sel.HasClass("toto") {
|
||||
t.Error("Expected h1 to have no class.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClassNotFirst(t *testing.T) {
|
||||
sel := Doc().Find(".alert")
|
||||
if !sel.HasClass("alert-error") {
|
||||
t.Error("Expected .alert to also have class .alert-error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveClass(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#nf1")
|
||||
sel.RemoveClass("one row")
|
||||
|
||||
if !sel.HasClass("even") || sel.HasClass("one") || sel.HasClass("row") {
|
||||
classes, _ := sel.Attr("class")
|
||||
t.Error("Expected #nf1 to have class even, has ", classes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveClassSimilar(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#nf5, #nf6")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
|
||||
sel.RemoveClass("odd")
|
||||
assertClass(t, sel.Eq(0), "odder")
|
||||
printSel(t, sel)
|
||||
}
|
||||
|
||||
func TestRemoveAllClasses(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#nf1")
|
||||
sel.RemoveClass()
|
||||
|
||||
if a, ok := sel.Attr("class"); ok {
|
||||
t.Error("All classes were not removed, has ", a)
|
||||
}
|
||||
|
||||
sel = Doc2Clone().Find("#main")
|
||||
sel.RemoveClass()
|
||||
if a, ok := sel.Attr("class"); ok {
|
||||
t.Error("All classes were not removed, has ", a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToggleClass(t *testing.T) {
|
||||
sel := Doc2Clone().Find("#nf1")
|
||||
|
||||
sel.ToggleClass("one")
|
||||
if sel.HasClass("one") {
|
||||
t.Error("Expected #nf1 to not have class one")
|
||||
}
|
||||
|
||||
sel.ToggleClass("one")
|
||||
if !sel.HasClass("one") {
|
||||
t.Error("Expected #nf1 to have class one")
|
||||
}
|
||||
|
||||
sel.ToggleClass("one even row")
|
||||
if a, ok := sel.Attr("class"); ok {
|
||||
t.Errorf("Expected #nf1 to have no classes, have %q", a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"code.google.com/p/cascadia"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
var rxClassTrim = regexp.MustCompile("[\t\r\n]")
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
// Is checks the current matched set of elements against a selector and
|
||||
// returns true if at least one of these elements matches.
|
||||
func (s *Selection) Is(selector string) bool {
|
||||
return s.IsMatcher(compileMatcher(selector))
|
||||
}
|
||||
|
||||
// IsMatcher checks the current matched set of elements against a matcher and
|
||||
// returns true if at least one of these elements matches.
|
||||
func (s *Selection) IsMatcher(m Matcher) bool {
|
||||
if len(s.Nodes) > 0 {
|
||||
// Attempt a match with the selector
|
||||
cs := cascadia.MustCompile(selector)
|
||||
if len(s.Nodes) == 1 {
|
||||
return cs.Match(s.Nodes[0])
|
||||
return m.Match(s.Nodes[0])
|
||||
}
|
||||
return len(cs.Filter(s.Nodes)) > 0
|
||||
return len(m.Filter(s.Nodes)) > 0
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -43,24 +39,6 @@ func (s *Selection) IsNodes(nodes ...*html.Node) bool {
|
||||
return s.FilterNodes(nodes...).Length() > 0
|
||||
}
|
||||
|
||||
// HasClass determines whether any of the matched elements are assigned the
|
||||
// given class.
|
||||
func (s *Selection) HasClass(class string) bool {
|
||||
class = " " + class + " "
|
||||
for _, n := range s.Nodes {
|
||||
// Applies only to element nodes
|
||||
if n.Type == html.ElementNode {
|
||||
if elClass, ok := getAttributeValue("class", n); ok {
|
||||
elClass = rxClassTrim.ReplaceAllString(" "+elClass+" ", " ")
|
||||
if strings.Index(elClass, class) > -1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Contains returns true if the specified Node is within,
|
||||
// at any depth, one of the nodes in the Selection object.
|
||||
// It is NOT inclusive, to behave like jQuery's implementation, and
|
||||
|
||||
+7
-21
@@ -11,6 +11,13 @@ func TestIs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".footer p:nth-child(1)")
|
||||
if sel.Is("") {
|
||||
t.Error("Is should not succeed with invalid selector string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPositional(t *testing.T) {
|
||||
sel := Doc().Find(".footer p:nth-child(2)")
|
||||
if !sel.Is("p:nth-child(2)") {
|
||||
@@ -72,27 +79,6 @@ func TestIsNodes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClass(t *testing.T) {
|
||||
sel := Doc().Find("div")
|
||||
if !sel.HasClass("span12") {
|
||||
t.Error("Expected at least one div to have class span12.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClassNone(t *testing.T) {
|
||||
sel := Doc().Find("h2")
|
||||
if sel.HasClass("toto") {
|
||||
t.Error("Expected h1 to have no class.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasClassNotFirst(t *testing.T) {
|
||||
sel := Doc().Find(".alert")
|
||||
if !sel.HasClass("alert-error") {
|
||||
t.Error("Expected .alert to also have class .alert-error.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDocContains(t *testing.T) {
|
||||
sel := Doc().Find("h1")
|
||||
if !Doc().Contains(sel.Nodes[0]) {
|
||||
|
||||
Vendored
+3
-3
@@ -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>
|
||||
@@ -17,8 +17,8 @@
|
||||
<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"></div>
|
||||
<div id="nf5" class="five even row odder"></div>
|
||||
<div id="nf6" class="six odd row"></div>
|
||||
</div>
|
||||
</body>
|
||||
</BODY>
|
||||
</html>
|
||||
|
||||
Vendored
+24
@@ -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>
|
||||
+223
-60
@@ -1,9 +1,6 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"code.google.com/p/cascadia"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
import "golang.org/x/net/html"
|
||||
|
||||
type siblingType int
|
||||
|
||||
@@ -24,7 +21,14 @@ const (
|
||||
// elements, filtered by a selector. It returns a new Selection object
|
||||
// containing these matched elements.
|
||||
func (s *Selection) Find(selector string) *Selection {
|
||||
return pushStack(s, findWithSelector(s.Nodes, selector))
|
||||
return pushStack(s, findWithMatcher(s.Nodes, compileMatcher(selector)))
|
||||
}
|
||||
|
||||
// FindMatcher gets the descendants of each element in the current set of matched
|
||||
// elements, filtered by the matcher. It returns a new Selection object
|
||||
// containing these matched elements.
|
||||
func (s *Selection) FindMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, findWithMatcher(s.Nodes, m))
|
||||
}
|
||||
|
||||
// FindSelection gets the descendants of each element in the current
|
||||
@@ -68,6 +72,14 @@ func (s *Selection) ContentsFiltered(selector string) *Selection {
|
||||
return s.Contents()
|
||||
}
|
||||
|
||||
// ContentsMatcher gets the children of each element in the Selection,
|
||||
// filtered by the specified matcher. It returns a new Selection
|
||||
// object containing these elements. Since matchers only act on Element nodes,
|
||||
// this function is an alias to ChildrenMatcher.
|
||||
func (s *Selection) ContentsMatcher(m Matcher) *Selection {
|
||||
return s.ChildrenMatcher(m)
|
||||
}
|
||||
|
||||
// Children gets the child elements of each element in the Selection.
|
||||
// It returns a new Selection object containing these elements.
|
||||
func (s *Selection) Children() *Selection {
|
||||
@@ -78,7 +90,14 @@ func (s *Selection) Children() *Selection {
|
||||
// filtered by the specified selector. It returns a new
|
||||
// Selection object containing these elements.
|
||||
func (s *Selection) ChildrenFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), selector)
|
||||
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// ChildrenMatcher gets the child elements of each element in the Selection,
|
||||
// filtered by the specified matcher. It returns a new
|
||||
// Selection object containing these elements.
|
||||
func (s *Selection) ChildrenMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), m)
|
||||
}
|
||||
|
||||
// Parent gets the parent of each element in the Selection. It returns a
|
||||
@@ -90,19 +109,30 @@ func (s *Selection) Parent() *Selection {
|
||||
// ParentFiltered gets the parent of each element in the Selection filtered by a
|
||||
// selector. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getParentNodes(s.Nodes), selector)
|
||||
return filterAndPush(s, getParentNodes(s.Nodes), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// ParentMatcher gets the parent of each element in the Selection filtered by a
|
||||
// matcher. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getParentNodes(s.Nodes), m)
|
||||
}
|
||||
|
||||
// Closest gets the first element that matches the selector by testing the
|
||||
// element itself and traversing up through its ancestors in the DOM tree.
|
||||
func (s *Selection) Closest(selector string) *Selection {
|
||||
cs := cascadia.MustCompile(selector)
|
||||
cs := compileMatcher(selector)
|
||||
return s.ClosestMatcher(cs)
|
||||
}
|
||||
|
||||
// ClosestMatcher gets the first element that matches the matcher by testing the
|
||||
// element itself and traversing up through its ancestors in the DOM tree.
|
||||
func (s *Selection) ClosestMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
|
||||
// For each node in the selection, test the node itself, then each parent
|
||||
// until a match is found.
|
||||
for ; n != nil; n = n.Parent {
|
||||
if cs.Match(n) {
|
||||
if m.Match(n) {
|
||||
return []*html.Node{n}
|
||||
}
|
||||
}
|
||||
@@ -113,11 +143,15 @@ func (s *Selection) Closest(selector string) *Selection {
|
||||
// ClosestNodes gets the first element that matches one of the nodes by testing the
|
||||
// element itself and traversing up through its ancestors in the DOM tree.
|
||||
func (s *Selection) ClosestNodes(nodes ...*html.Node) *Selection {
|
||||
set := make(map[*html.Node]bool)
|
||||
for _, n := range nodes {
|
||||
set[n] = true
|
||||
}
|
||||
return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
|
||||
// For each node in the selection, test the node itself, then each parent
|
||||
// until a match is found.
|
||||
for ; n != nil; n = n.Parent {
|
||||
if isInSlice(nodes, n) {
|
||||
if set[n] {
|
||||
return []*html.Node{n}
|
||||
}
|
||||
}
|
||||
@@ -138,20 +172,33 @@ func (s *Selection) ClosestSelection(sel *Selection) *Selection {
|
||||
// Parents gets the ancestors of each element in the current Selection. It
|
||||
// returns a new Selection object with the matched elements.
|
||||
func (s *Selection) Parents() *Selection {
|
||||
return pushStack(s, getParentsNodes(s.Nodes, "", nil))
|
||||
return pushStack(s, getParentsNodes(s.Nodes, nil, nil))
|
||||
}
|
||||
|
||||
// ParentsFiltered gets the ancestors of each element in the current
|
||||
// Selection. It returns a new Selection object with the matched elements.
|
||||
func (s *Selection) ParentsFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, "", nil), selector)
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// ParentsMatcher gets the ancestors of each element in the current
|
||||
// Selection. It returns a new Selection object with the matched elements.
|
||||
func (s *Selection) ParentsMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), m)
|
||||
}
|
||||
|
||||
// ParentsUntil gets the ancestors of each element in the Selection, up to but
|
||||
// not including the element matched by the selector. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) ParentsUntil(selector string) *Selection {
|
||||
return pushStack(s, getParentsNodes(s.Nodes, selector, nil))
|
||||
return pushStack(s, getParentsNodes(s.Nodes, compileMatcher(selector), nil))
|
||||
}
|
||||
|
||||
// ParentsUntilMatcher gets the ancestors of each element in the Selection, up to but
|
||||
// not including the element matched by the matcher. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) ParentsUntilMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, getParentsNodes(s.Nodes, m, nil))
|
||||
}
|
||||
|
||||
// ParentsUntilSelection gets the ancestors of each element in the Selection,
|
||||
@@ -168,96 +215,151 @@ func (s *Selection) ParentsUntilSelection(sel *Selection) *Selection {
|
||||
// up to but not including the specified nodes. It returns a
|
||||
// new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsUntilNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, getParentsNodes(s.Nodes, "", nodes))
|
||||
return pushStack(s, getParentsNodes(s.Nodes, nil, nodes))
|
||||
}
|
||||
|
||||
// ParentsFilteredUntil is like ParentsUntil, with the option to filter the
|
||||
// results based on a selector string. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) ParentsFilteredUntil(filterSelector string, untilSelector string) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, untilSelector, nil), filterSelector)
|
||||
func (s *Selection) ParentsFilteredUntil(filterSelector, untilSelector string) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// ParentsFilteredUntilMatcher is like ParentsUntilMatcher, with the option to filter the
|
||||
// results based on a matcher. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsFilteredUntilMatcher(filter, until Matcher) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, until, nil), filter)
|
||||
}
|
||||
|
||||
// ParentsFilteredUntilSelection is like ParentsUntilSelection, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
||||
return s.ParentsMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
||||
}
|
||||
|
||||
// ParentsMatcherUntilSelection is like ParentsUntilSelection, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.ParentsFiltered(filterSelector)
|
||||
return s.ParentsMatcher(filter)
|
||||
}
|
||||
return s.ParentsFilteredUntilNodes(filterSelector, sel.Nodes...)
|
||||
return s.ParentsMatcherUntilNodes(filter, sel.Nodes...)
|
||||
}
|
||||
|
||||
// ParentsFilteredUntilNodes is like ParentsUntilNodes, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, "", nodes), filterSelector)
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// ParentsMatcherUntilNodes is like ParentsUntilNodes, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) ParentsMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), filter)
|
||||
}
|
||||
|
||||
// Siblings gets the siblings of each element in the Selection. It returns
|
||||
// a new Selection object containing the matched elements.
|
||||
func (s *Selection) Siblings() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingAll, "", nil))
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil))
|
||||
}
|
||||
|
||||
// SiblingsFiltered gets the siblings of each element in the Selection
|
||||
// filtered by a selector. It returns a new Selection object containing the
|
||||
// matched elements.
|
||||
func (s *Selection) SiblingsFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, "", nil), selector)
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// SiblingsMatcher gets the siblings of each element in the Selection
|
||||
// filtered by a matcher. It returns a new Selection object containing the
|
||||
// matched elements.
|
||||
func (s *Selection) SiblingsMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), m)
|
||||
}
|
||||
|
||||
// Next gets the immediately following sibling of each element in the
|
||||
// Selection. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) Next() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNext, "", nil))
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil))
|
||||
}
|
||||
|
||||
// NextFiltered gets the immediately following sibling of each element in the
|
||||
// Selection filtered by a selector. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) NextFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, "", nil), selector)
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// NextMatcher gets the immediately following sibling of each element in the
|
||||
// Selection filtered by a matcher. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) NextMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), m)
|
||||
}
|
||||
|
||||
// NextAll gets all the following siblings of each element in the
|
||||
// Selection. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) NextAll() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextAll, "", nil))
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil))
|
||||
}
|
||||
|
||||
// NextAllFiltered gets all the following siblings of each element in the
|
||||
// Selection filtered by a selector. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) NextAllFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, "", nil), selector)
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// NextAllMatcher gets all the following siblings of each element in the
|
||||
// Selection filtered by a matcher. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) NextAllMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), m)
|
||||
}
|
||||
|
||||
// Prev gets the immediately preceding sibling of each element in the
|
||||
// Selection. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) Prev() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrev, "", nil))
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil))
|
||||
}
|
||||
|
||||
// PrevFiltered gets the immediately preceding sibling of each element in the
|
||||
// Selection filtered by a selector. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) PrevFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, "", nil), selector)
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// PrevMatcher gets the immediately preceding sibling of each element in the
|
||||
// Selection filtered by a matcher. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) PrevMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), m)
|
||||
}
|
||||
|
||||
// PrevAll gets all the preceding siblings of each element in the
|
||||
// Selection. It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) PrevAll() *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevAll, "", nil))
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil))
|
||||
}
|
||||
|
||||
// PrevAllFiltered gets all the preceding siblings of each element in the
|
||||
// Selection filtered by a selector. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) PrevAllFiltered(selector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, "", nil), selector)
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), compileMatcher(selector))
|
||||
}
|
||||
|
||||
// PrevAllMatcher gets all the preceding siblings of each element in the
|
||||
// Selection filtered by a matcher. It returns a new Selection object
|
||||
// containing the matched elements.
|
||||
func (s *Selection) PrevAllMatcher(m Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), m)
|
||||
}
|
||||
|
||||
// NextUntil gets all following siblings of each element up to but not
|
||||
@@ -265,7 +367,15 @@ func (s *Selection) PrevAllFiltered(selector string) *Selection {
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) NextUntil(selector string) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
selector, nil))
|
||||
compileMatcher(selector), nil))
|
||||
}
|
||||
|
||||
// NextUntilMatcher gets all following siblings of each element up to but not
|
||||
// including the element matched by the matcher. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) NextUntilMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
m, nil))
|
||||
}
|
||||
|
||||
// NextUntilSelection gets all following siblings of each element up to but not
|
||||
@@ -283,7 +393,7 @@ func (s *Selection) NextUntilSelection(sel *Selection) *Selection {
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) NextUntilNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
"", nodes))
|
||||
nil, nodes))
|
||||
}
|
||||
|
||||
// PrevUntil gets all preceding siblings of each element up to but not
|
||||
@@ -291,7 +401,15 @@ func (s *Selection) NextUntilNodes(nodes ...*html.Node) *Selection {
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) PrevUntil(selector string) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
selector, nil))
|
||||
compileMatcher(selector), nil))
|
||||
}
|
||||
|
||||
// PrevUntilMatcher gets all preceding siblings of each element up to but not
|
||||
// including the element matched by the matcher. It returns a new Selection
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) PrevUntilMatcher(m Matcher) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
m, nil))
|
||||
}
|
||||
|
||||
// PrevUntilSelection gets all preceding siblings of each element up to but not
|
||||
@@ -309,25 +427,40 @@ func (s *Selection) PrevUntilSelection(sel *Selection) *Selection {
|
||||
// object containing the matched elements.
|
||||
func (s *Selection) PrevUntilNodes(nodes ...*html.Node) *Selection {
|
||||
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
"", nodes))
|
||||
nil, nodes))
|
||||
}
|
||||
|
||||
// NextFilteredUntil is like NextUntil, with the option to filter
|
||||
// the results based on a selector string.
|
||||
// It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) NextFilteredUntil(filterSelector string, untilSelector string) *Selection {
|
||||
func (s *Selection) NextFilteredUntil(filterSelector, untilSelector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
untilSelector, nil), filterSelector)
|
||||
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// NextFilteredUntilMatcher is like NextUntilMatcher, with the option to filter
|
||||
// the results based on a matcher.
|
||||
// It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) NextFilteredUntilMatcher(filter, until Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
until, nil), filter)
|
||||
}
|
||||
|
||||
// NextFilteredUntilSelection is like NextUntilSelection, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) NextFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
||||
return s.NextMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
||||
}
|
||||
|
||||
// NextMatcherUntilSelection is like NextUntilSelection, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) NextMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.NextFiltered(filterSelector)
|
||||
return s.NextMatcher(filter)
|
||||
}
|
||||
return s.NextFilteredUntilNodes(filterSelector, sel.Nodes...)
|
||||
return s.NextMatcherUntilNodes(filter, sel.Nodes...)
|
||||
}
|
||||
|
||||
// NextFilteredUntilNodes is like NextUntilNodes, with the
|
||||
@@ -335,25 +468,48 @@ func (s *Selection) NextFilteredUntilSelection(filterSelector string, sel *Selec
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) NextFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
"", nodes), filterSelector)
|
||||
nil, nodes), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// NextMatcherUntilNodes is like NextUntilNodes, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) NextMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
|
||||
nil, nodes), filter)
|
||||
}
|
||||
|
||||
// PrevFilteredUntil is like PrevUntil, with the option to filter
|
||||
// the results based on a selector string.
|
||||
// It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) PrevFilteredUntil(filterSelector string, untilSelector string) *Selection {
|
||||
func (s *Selection) PrevFilteredUntil(filterSelector, untilSelector string) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
untilSelector, nil), filterSelector)
|
||||
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// PrevFilteredUntilMatcher is like PrevUntilMatcher, with the option to filter
|
||||
// the results based on a matcher.
|
||||
// It returns a new Selection object containing the matched elements.
|
||||
func (s *Selection) PrevFilteredUntilMatcher(filter, until Matcher) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
until, nil), filter)
|
||||
}
|
||||
|
||||
// PrevFilteredUntilSelection is like PrevUntilSelection, with the
|
||||
// option to filter the results based on a selector string. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) PrevFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
|
||||
return s.PrevMatcherUntilSelection(compileMatcher(filterSelector), sel)
|
||||
}
|
||||
|
||||
// PrevMatcherUntilSelection is like PrevUntilSelection, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) PrevMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
|
||||
if sel == nil {
|
||||
return s.PrevFiltered(filterSelector)
|
||||
return s.PrevMatcher(filter)
|
||||
}
|
||||
return s.PrevFilteredUntilNodes(filterSelector, sel.Nodes...)
|
||||
return s.PrevMatcherUntilNodes(filter, sel.Nodes...)
|
||||
}
|
||||
|
||||
// PrevFilteredUntilNodes is like PrevUntilNodes, with the
|
||||
@@ -361,28 +517,34 @@ func (s *Selection) PrevFilteredUntilSelection(filterSelector string, sel *Selec
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) PrevFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
"", nodes), filterSelector)
|
||||
nil, nodes), compileMatcher(filterSelector))
|
||||
}
|
||||
|
||||
// Filter and push filters the nodes based on a selector, and pushes the results
|
||||
// PrevMatcherUntilNodes is like PrevUntilNodes, with the
|
||||
// option to filter the results based on a matcher. It returns a new
|
||||
// Selection object containing the matched elements.
|
||||
func (s *Selection) PrevMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
|
||||
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
|
||||
nil, nodes), filter)
|
||||
}
|
||||
|
||||
// Filter and push filters the nodes based on a matcher, and pushes the results
|
||||
// on the stack, with the srcSel as previous selection.
|
||||
func filterAndPush(srcSel *Selection, nodes []*html.Node, selector string) *Selection {
|
||||
func filterAndPush(srcSel *Selection, nodes []*html.Node, m Matcher) *Selection {
|
||||
// Create a temporary Selection with the specified nodes to filter using winnow
|
||||
sel := &Selection{nodes, srcSel.document, nil}
|
||||
// Filter based on selector and push on stack
|
||||
return pushStack(srcSel, winnow(sel, selector, true))
|
||||
// Filter based on matcher and push on stack
|
||||
return pushStack(srcSel, winnow(sel, m, true))
|
||||
}
|
||||
|
||||
// Internal implementation of Find that return raw nodes.
|
||||
func findWithSelector(nodes []*html.Node, selector string) []*html.Node {
|
||||
// Compile the selector once
|
||||
sel := cascadia.MustCompile(selector)
|
||||
func findWithMatcher(nodes []*html.Node, m Matcher) []*html.Node {
|
||||
// Map nodes to find the matches within the children of each node
|
||||
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
|
||||
// Go down one level, becausejQuery's Find selects only within descendants
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
if c.Type == html.ElementNode {
|
||||
result = append(result, sel.MatchAll(c)...)
|
||||
result = append(result, m.MatchAll(c)...)
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -391,12 +553,12 @@ func findWithSelector(nodes []*html.Node, selector string) []*html.Node {
|
||||
|
||||
// Internal implementation to get all parent nodes, stopping at the specified
|
||||
// node (or nil if no stop).
|
||||
func getParentsNodes(nodes []*html.Node, stopSelector string, stopNodes []*html.Node) []*html.Node {
|
||||
func getParentsNodes(nodes []*html.Node, stopm Matcher, stopNodes []*html.Node) []*html.Node {
|
||||
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
|
||||
for p := n.Parent; p != nil; p = p.Parent {
|
||||
sel := newSingleSelection(p, nil)
|
||||
if stopSelector != "" {
|
||||
if sel.Is(stopSelector) {
|
||||
if stopm != nil {
|
||||
if sel.IsMatcher(stopm) {
|
||||
break
|
||||
}
|
||||
} else if len(stopNodes) > 0 {
|
||||
@@ -413,17 +575,17 @@ func getParentsNodes(nodes []*html.Node, stopSelector string, stopNodes []*html.
|
||||
}
|
||||
|
||||
// Internal implementation of sibling nodes that return a raw slice of matches.
|
||||
func getSiblingNodes(nodes []*html.Node, st siblingType, untilSelector string, untilNodes []*html.Node) []*html.Node {
|
||||
func getSiblingNodes(nodes []*html.Node, st siblingType, untilm Matcher, untilNodes []*html.Node) []*html.Node {
|
||||
var f func(*html.Node) bool
|
||||
|
||||
// If the requested siblings are ...Until, create the test function to
|
||||
// determine if the until condition is reached (returns true if it is)
|
||||
if st == siblingNextUntil || st == siblingPrevUntil {
|
||||
f = func(n *html.Node) bool {
|
||||
if untilSelector != "" {
|
||||
// Selector-based condition
|
||||
if untilm != nil {
|
||||
// Matcher-based condition
|
||||
sel := newSingleSelection(n, nil)
|
||||
return sel.Is(untilSelector)
|
||||
return sel.IsMatcher(untilm)
|
||||
} else if len(untilNodes) > 0 {
|
||||
// Nodes-based condition
|
||||
sel := newSingleSelection(n, nil)
|
||||
@@ -526,10 +688,11 @@ func getParentNodes(nodes []*html.Node) []*html.Node {
|
||||
// Returns an array of nodes mapped by calling the callback function once for
|
||||
// each node in the source nodes.
|
||||
func mapNodes(nodes []*html.Node, f func(int, *html.Node) []*html.Node) (result []*html.Node) {
|
||||
set := make(map[*html.Node]bool)
|
||||
for i, n := range nodes {
|
||||
if vals := f(i, n); len(vals) > 0 {
|
||||
result = appendWithoutDuplicates(result, vals)
|
||||
result = appendWithoutDuplicates(result, vals, set)
|
||||
}
|
||||
}
|
||||
return
|
||||
return result
|
||||
}
|
||||
|
||||
+237
-141
@@ -7,672 +7,768 @@ import (
|
||||
|
||||
func TestFind(t *testing.T) {
|
||||
sel := Doc().Find("div.row-fluid")
|
||||
AssertLength(t, sel.Nodes, 9)
|
||||
assertLength(t, sel.Nodes, 9)
|
||||
}
|
||||
|
||||
func TestFindRollback(t *testing.T) {
|
||||
sel := Doc().Find("div.row-fluid")
|
||||
sel2 := sel.Find("a").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestFindNotSelf(t *testing.T) {
|
||||
sel := Doc().Find("h1").Find("h1")
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestFindInvalidSelector(t *testing.T) {
|
||||
defer AssertPanic(t)
|
||||
Doc().Find(":+ ^")
|
||||
func TestFindInvalid(t *testing.T) {
|
||||
sel := Doc().Find(":+ ^")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestFindBig(t *testing.T) {
|
||||
doc := DocW()
|
||||
sel := doc.Find("li")
|
||||
assertLength(t, sel.Nodes, 373)
|
||||
sel2 := doc.Find("span")
|
||||
assertLength(t, sel2.Nodes, 448)
|
||||
sel3 := sel.FindSelection(sel2)
|
||||
assertLength(t, sel3.Nodes, 248)
|
||||
}
|
||||
|
||||
func TestChainedFind(t *testing.T) {
|
||||
sel := Doc().Find("div.hero-unit").Find(".row-fluid")
|
||||
AssertLength(t, sel.Nodes, 4)
|
||||
assertLength(t, sel.Nodes, 4)
|
||||
}
|
||||
|
||||
func TestChainedFindInvalid(t *testing.T) {
|
||||
sel := Doc().Find("div.hero-unit").Find("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestChildren(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Children()
|
||||
AssertLength(t, sel.Nodes, 5)
|
||||
assertLength(t, sel.Nodes, 5)
|
||||
}
|
||||
|
||||
func TestChildrenRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Children().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestContents(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").Contents()
|
||||
AssertLength(t, sel.Nodes, 13)
|
||||
assertLength(t, sel.Nodes, 13)
|
||||
}
|
||||
|
||||
func TestContentsRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.Contents().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestChildrenFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").ChildrenFiltered(".hero-unit")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestChildrenFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").ChildrenFiltered("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestChildrenFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.ChildrenFiltered(".hero-unit").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestContentsFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").ContentsFiltered(".hero-unit")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestContentsFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").ContentsFiltered("~")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestContentsFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content")
|
||||
sel2 := sel.ContentsFiltered(".hero-unit").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestChildrenFilteredNone(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-content").ChildrenFiltered("a.btn")
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestParent(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").Parent()
|
||||
AssertLength(t, sel.Nodes, 3)
|
||||
assertLength(t, sel.Nodes, 3)
|
||||
}
|
||||
|
||||
func TestParentRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.Parent().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParentBody(t *testing.T) {
|
||||
sel := Doc().Find("body").Parent()
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestParentFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").ParentFiltered(".hero-unit")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
AssertClass(t, sel, "hero-unit")
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
assertClass(t, sel, "hero-unit")
|
||||
}
|
||||
|
||||
func TestParentFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").ParentFiltered("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestParentFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.ParentFiltered(".hero-unit").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParents(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").Parents()
|
||||
AssertLength(t, sel.Nodes, 8)
|
||||
assertLength(t, sel.Nodes, 8)
|
||||
}
|
||||
|
||||
func TestParentsOrder(t *testing.T) {
|
||||
sel := Doc().Find("#cf2").Parents()
|
||||
AssertLength(t, sel.Nodes, 6)
|
||||
AssertSelectionIs(t, sel, ".hero-unit", ".pvk-content", "div.row-fluid", "#cf1", "body", "html")
|
||||
assertLength(t, sel.Nodes, 6)
|
||||
assertSelectionIs(t, sel, ".hero-unit", ".pvk-content", "div.row-fluid", "#cf1", "body", "html")
|
||||
}
|
||||
|
||||
func TestParentsRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.Parents().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParentsFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").ParentsFiltered("body")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestParentsFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").ParentsFiltered("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestParentsFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.ParentsFiltered("body").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParentsUntil(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").ParentsUntil("body")
|
||||
AssertLength(t, sel.Nodes, 6)
|
||||
assertLength(t, sel.Nodes, 6)
|
||||
}
|
||||
|
||||
func TestParentsUntilInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").ParentsUntil("")
|
||||
assertLength(t, sel.Nodes, 8)
|
||||
}
|
||||
|
||||
func TestParentsUntilRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.ParentsUntil("body").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParentsUntilSelection(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".pvk-content")
|
||||
sel = sel.ParentsUntilSelection(sel2)
|
||||
AssertLength(t, sel.Nodes, 3)
|
||||
assertLength(t, sel.Nodes, 3)
|
||||
}
|
||||
|
||||
func TestParentsUntilSelectionRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".pvk-content")
|
||||
sel2 = sel.ParentsUntilSelection(sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParentsUntilNodes(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".pvk-content, .hero-unit")
|
||||
sel = sel.ParentsUntilNodes(sel2.Nodes...)
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestParentsUntilNodesRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".pvk-content, .hero-unit")
|
||||
sel2 = sel.ParentsUntilNodes(sel2.Nodes...).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParentsFilteredUntil(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").ParentsFilteredUntil(".pvk-content", "body")
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestParentsFilteredUntilInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").ParentsFilteredUntil("", "")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestParentsFilteredUntilRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.ParentsFilteredUntil(".pvk-content", "body").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParentsFilteredUntilSelection(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".row-fluid")
|
||||
sel = sel.ParentsFilteredUntilSelection("div", sel2)
|
||||
AssertLength(t, sel.Nodes, 3)
|
||||
assertLength(t, sel.Nodes, 3)
|
||||
}
|
||||
|
||||
func TestParentsFilteredUntilSelectionRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".row-fluid")
|
||||
sel2 = sel.ParentsFilteredUntilSelection("div", sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestParentsFilteredUntilNodes(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".row-fluid")
|
||||
sel = sel.ParentsFilteredUntilNodes("body", sel2.Nodes...)
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestParentsFilteredUntilNodesRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := Doc().Find(".row-fluid")
|
||||
sel2 = sel.ParentsFilteredUntilNodes("body", sel2.Nodes...).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestSiblings(t *testing.T) {
|
||||
sel := Doc().Find("h1").Siblings()
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestSiblingsRollback(t *testing.T) {
|
||||
sel := Doc().Find("h1")
|
||||
sel2 := sel.Siblings().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestSiblings2(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter").Siblings()
|
||||
AssertLength(t, sel.Nodes, 9)
|
||||
assertLength(t, sel.Nodes, 9)
|
||||
}
|
||||
|
||||
func TestSiblings3(t *testing.T) {
|
||||
sel := Doc().Find("body>.container-fluid").Siblings()
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestSiblingsFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter").SiblingsFiltered(".pvk-content")
|
||||
AssertLength(t, sel.Nodes, 3)
|
||||
assertLength(t, sel.Nodes, 3)
|
||||
}
|
||||
|
||||
func TestSiblingsFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter").SiblingsFiltered("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestSiblingsFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter")
|
||||
sel2 := sel.SiblingsFiltered(".pvk-content").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNext(t *testing.T) {
|
||||
sel := Doc().Find("h1").Next()
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestNextRollback(t *testing.T) {
|
||||
sel := Doc().Find("h1")
|
||||
sel2 := sel.Next().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNext2(t *testing.T) {
|
||||
sel := Doc().Find(".close").Next()
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestNextNone(t *testing.T) {
|
||||
sel := Doc().Find("small").Next()
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestNextFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").NextFiltered("div")
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestNextFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").NextFiltered("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestNextFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.NextFiltered("div").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextFiltered2(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid").NextFiltered("[ng-view]")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestPrev(t *testing.T) {
|
||||
sel := Doc().Find(".red").Prev()
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
AssertClass(t, sel, "green")
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
assertClass(t, sel, "green")
|
||||
}
|
||||
|
||||
func TestPrevRollback(t *testing.T) {
|
||||
sel := Doc().Find(".red")
|
||||
sel2 := sel.Prev().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestPrev2(t *testing.T) {
|
||||
sel := Doc().Find(".row-fluid").Prev()
|
||||
AssertLength(t, sel.Nodes, 5)
|
||||
assertLength(t, sel.Nodes, 5)
|
||||
}
|
||||
|
||||
func TestPrevNone(t *testing.T) {
|
||||
sel := Doc().Find("h2").Prev()
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestPrevFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".row-fluid").PrevFiltered(".row-fluid")
|
||||
AssertLength(t, sel.Nodes, 5)
|
||||
assertLength(t, sel.Nodes, 5)
|
||||
}
|
||||
|
||||
func TestPrevFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".row-fluid").PrevFiltered("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestPrevFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".row-fluid")
|
||||
sel2 := sel.PrevFiltered(".row-fluid").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextAll(t *testing.T) {
|
||||
sel := Doc().Find("#cf2 div:nth-child(1)").NextAll()
|
||||
AssertLength(t, sel.Nodes, 3)
|
||||
assertLength(t, sel.Nodes, 3)
|
||||
}
|
||||
|
||||
func TestNextAllRollback(t *testing.T) {
|
||||
sel := Doc().Find("#cf2 div:nth-child(1)")
|
||||
sel2 := sel.NextAll().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextAll2(t *testing.T) {
|
||||
sel := Doc().Find("div[ng-cloak]").NextAll()
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestNextAllNone(t *testing.T) {
|
||||
sel := Doc().Find(".footer").NextAll()
|
||||
AssertLength(t, sel.Nodes, 0)
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestNextAllFiltered(t *testing.T) {
|
||||
sel := Doc().Find("#cf2 .row-fluid").NextAllFiltered("[ng-cloak]")
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestNextAllFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find("#cf2 .row-fluid").NextAllFiltered("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestNextAllFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find("#cf2 .row-fluid")
|
||||
sel2 := sel.NextAllFiltered("[ng-cloak]").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextAllFiltered2(t *testing.T) {
|
||||
sel := Doc().Find(".close").NextAllFiltered("h4")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
}
|
||||
|
||||
func TestPrevAll(t *testing.T) {
|
||||
sel := Doc().Find("[ng-view]").PrevAll()
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestPrevAllOrder(t *testing.T) {
|
||||
sel := Doc().Find("[ng-view]").PrevAll()
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
AssertSelectionIs(t, sel, "#cf4", "#cf3")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel, "#cf4", "#cf3")
|
||||
}
|
||||
|
||||
func TestPrevAllRollback(t *testing.T) {
|
||||
sel := Doc().Find("[ng-view]")
|
||||
sel2 := sel.PrevAll().End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestPrevAll2(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter").PrevAll()
|
||||
AssertLength(t, sel.Nodes, 6)
|
||||
assertLength(t, sel.Nodes, 6)
|
||||
}
|
||||
|
||||
func TestPrevAllFiltered(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter").PrevAllFiltered(".pvk-content")
|
||||
AssertLength(t, sel.Nodes, 3)
|
||||
assertLength(t, sel.Nodes, 3)
|
||||
}
|
||||
|
||||
func TestPrevAllFilteredInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter").PrevAllFiltered("")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestPrevAllFilteredRollback(t *testing.T) {
|
||||
sel := Doc().Find(".pvk-gutter")
|
||||
sel2 := sel.PrevAllFiltered(".pvk-content").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextUntil(t *testing.T) {
|
||||
sel := Doc().Find(".alert a").NextUntil("p")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
AssertSelectionIs(t, sel, "h4")
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
assertSelectionIs(t, sel, "h4")
|
||||
}
|
||||
|
||||
func TestNextUntilInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".alert a").NextUntil("")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestNextUntil2(t *testing.T) {
|
||||
sel := Doc().Find("#cf2-1").NextUntil("[ng-cloak]")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
AssertSelectionIs(t, sel, "#cf2-2")
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
assertSelectionIs(t, sel, "#cf2-2")
|
||||
}
|
||||
|
||||
func TestNextUntilOrder(t *testing.T) {
|
||||
sel := Doc().Find("#cf2-1").NextUntil("#cf2-4")
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
AssertSelectionIs(t, sel, "#cf2-2", "#cf2-3")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel, "#cf2-2", "#cf2-3")
|
||||
}
|
||||
|
||||
func TestNextUntilRollback(t *testing.T) {
|
||||
sel := Doc().Find("#cf2-1")
|
||||
sel2 := sel.PrevUntil("#cf2-4").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextUntilSelection(t *testing.T) {
|
||||
sel := Doc2().Find("#n2")
|
||||
sel2 := Doc2().Find("#n4")
|
||||
sel2 = sel.NextUntilSelection(sel2)
|
||||
AssertLength(t, sel2.Nodes, 1)
|
||||
AssertSelectionIs(t, sel2, "#n3")
|
||||
assertLength(t, sel2.Nodes, 1)
|
||||
assertSelectionIs(t, sel2, "#n3")
|
||||
}
|
||||
|
||||
func TestNextUntilSelectionRollback(t *testing.T) {
|
||||
sel := Doc2().Find("#n2")
|
||||
sel2 := Doc2().Find("#n4")
|
||||
sel2 = sel.NextUntilSelection(sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextUntilNodes(t *testing.T) {
|
||||
sel := Doc2().Find("#n2")
|
||||
sel2 := Doc2().Find("#n5")
|
||||
sel2 = sel.NextUntilNodes(sel2.Nodes...)
|
||||
AssertLength(t, sel2.Nodes, 2)
|
||||
AssertSelectionIs(t, sel2, "#n3", "#n4")
|
||||
assertLength(t, sel2.Nodes, 2)
|
||||
assertSelectionIs(t, sel2, "#n3", "#n4")
|
||||
}
|
||||
|
||||
func TestNextUntilNodesRollback(t *testing.T) {
|
||||
sel := Doc2().Find("#n2")
|
||||
sel2 := Doc2().Find("#n5")
|
||||
sel2 = sel.NextUntilNodes(sel2.Nodes...).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestPrevUntil(t *testing.T) {
|
||||
sel := Doc().Find(".alert p").PrevUntil("a")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
AssertSelectionIs(t, sel, "h4")
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
assertSelectionIs(t, sel, "h4")
|
||||
}
|
||||
|
||||
func TestPrevUntilInvalid(t *testing.T) {
|
||||
sel := Doc().Find(".alert p").PrevUntil("")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
}
|
||||
|
||||
func TestPrevUntil2(t *testing.T) {
|
||||
sel := Doc().Find("[ng-cloak]").PrevUntil(":not([ng-cloak])")
|
||||
AssertLength(t, sel.Nodes, 1)
|
||||
AssertSelectionIs(t, sel, "[ng-cloak]")
|
||||
assertLength(t, sel.Nodes, 1)
|
||||
assertSelectionIs(t, sel, "[ng-cloak]")
|
||||
}
|
||||
|
||||
func TestPrevUntilOrder(t *testing.T) {
|
||||
sel := Doc().Find("#cf2-4").PrevUntil("#cf2-1")
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
AssertSelectionIs(t, sel, "#cf2-3", "#cf2-2")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel, "#cf2-3", "#cf2-2")
|
||||
}
|
||||
|
||||
func TestPrevUntilRollback(t *testing.T) {
|
||||
sel := Doc().Find("#cf2-4")
|
||||
sel2 := sel.PrevUntil("#cf2-1").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestPrevUntilSelection(t *testing.T) {
|
||||
sel := Doc2().Find("#n4")
|
||||
sel2 := Doc2().Find("#n2")
|
||||
sel2 = sel.PrevUntilSelection(sel2)
|
||||
AssertLength(t, sel2.Nodes, 1)
|
||||
AssertSelectionIs(t, sel2, "#n3")
|
||||
assertLength(t, sel2.Nodes, 1)
|
||||
assertSelectionIs(t, sel2, "#n3")
|
||||
}
|
||||
|
||||
func TestPrevUntilSelectionRollback(t *testing.T) {
|
||||
sel := Doc2().Find("#n4")
|
||||
sel2 := Doc2().Find("#n2")
|
||||
sel2 = sel.PrevUntilSelection(sel2).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestPrevUntilNodes(t *testing.T) {
|
||||
sel := Doc2().Find("#n5")
|
||||
sel2 := Doc2().Find("#n2")
|
||||
sel2 = sel.PrevUntilNodes(sel2.Nodes...)
|
||||
AssertLength(t, sel2.Nodes, 2)
|
||||
AssertSelectionIs(t, sel2, "#n4", "#n3")
|
||||
assertLength(t, sel2.Nodes, 2)
|
||||
assertSelectionIs(t, sel2, "#n4", "#n3")
|
||||
}
|
||||
|
||||
func TestPrevUntilNodesRollback(t *testing.T) {
|
||||
sel := Doc2().Find("#n5")
|
||||
sel2 := Doc2().Find("#n2")
|
||||
sel2 = sel.PrevUntilNodes(sel2.Nodes...).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextFilteredUntil(t *testing.T) {
|
||||
sel := Doc2().Find(".two").NextFilteredUntil(".even", ".six")
|
||||
AssertLength(t, sel.Nodes, 4)
|
||||
AssertSelectionIs(t, sel, "#n3", "#n5", "#nf3", "#nf5")
|
||||
assertLength(t, sel.Nodes, 4)
|
||||
assertSelectionIs(t, sel, "#n3", "#n5", "#nf3", "#nf5")
|
||||
}
|
||||
|
||||
func TestNextFilteredUntilInvalid(t *testing.T) {
|
||||
sel := Doc2().Find(".two").NextFilteredUntil("", "")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestNextFilteredUntilRollback(t *testing.T) {
|
||||
sel := Doc2().Find(".two")
|
||||
sel2 := sel.NextFilteredUntil(".even", ".six").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestNextFilteredUntilSelection(t *testing.T) {
|
||||
sel := Doc2().Find(".even")
|
||||
sel2 := Doc2().Find(".five")
|
||||
sel = sel.NextFilteredUntilSelection(".even", sel2)
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
AssertSelectionIs(t, sel, "#n3", "#nf3")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel, "#n3", "#nf3")
|
||||
}
|
||||
|
||||
func TestNextFilteredUntilSelectionRollback(t *testing.T) {
|
||||
sel := Doc2().Find(".even")
|
||||
sel2 := Doc2().Find(".five")
|
||||
sel3 := sel.NextFilteredUntilSelection(".even", sel2).End()
|
||||
AssertEqual(t, sel, sel3)
|
||||
assertEqual(t, sel, sel3)
|
||||
}
|
||||
|
||||
func TestNextFilteredUntilNodes(t *testing.T) {
|
||||
sel := Doc2().Find(".even")
|
||||
sel2 := Doc2().Find(".four")
|
||||
sel = sel.NextFilteredUntilNodes(".odd", sel2.Nodes...)
|
||||
AssertLength(t, sel.Nodes, 4)
|
||||
AssertSelectionIs(t, sel, "#n2", "#n6", "#nf2", "#nf6")
|
||||
assertLength(t, sel.Nodes, 4)
|
||||
assertSelectionIs(t, sel, "#n2", "#n6", "#nf2", "#nf6")
|
||||
}
|
||||
|
||||
func TestNextFilteredUntilNodesRollback(t *testing.T) {
|
||||
sel := Doc2().Find(".even")
|
||||
sel2 := Doc2().Find(".four")
|
||||
sel3 := sel.NextFilteredUntilNodes(".odd", sel2.Nodes...).End()
|
||||
AssertEqual(t, sel, sel3)
|
||||
assertEqual(t, sel, sel3)
|
||||
}
|
||||
|
||||
func TestPrevFilteredUntil(t *testing.T) {
|
||||
sel := Doc2().Find(".five").PrevFilteredUntil(".odd", ".one")
|
||||
AssertLength(t, sel.Nodes, 4)
|
||||
AssertSelectionIs(t, sel, "#n4", "#n2", "#nf4", "#nf2")
|
||||
assertLength(t, sel.Nodes, 4)
|
||||
assertSelectionIs(t, sel, "#n4", "#n2", "#nf4", "#nf2")
|
||||
}
|
||||
|
||||
func TestPrevFilteredUntilInvalid(t *testing.T) {
|
||||
sel := Doc2().Find(".five").PrevFilteredUntil("", "")
|
||||
assertLength(t, sel.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestPrevFilteredUntilRollback(t *testing.T) {
|
||||
sel := Doc2().Find(".four")
|
||||
sel2 := sel.PrevFilteredUntil(".odd", ".one").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestPrevFilteredUntilSelection(t *testing.T) {
|
||||
sel := Doc2().Find(".odd")
|
||||
sel2 := Doc2().Find(".two")
|
||||
sel = sel.PrevFilteredUntilSelection(".odd", sel2)
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
AssertSelectionIs(t, sel, "#n4", "#nf4")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel, "#n4", "#nf4")
|
||||
}
|
||||
|
||||
func TestPrevFilteredUntilSelectionRollback(t *testing.T) {
|
||||
sel := Doc2().Find(".even")
|
||||
sel2 := Doc2().Find(".five")
|
||||
sel3 := sel.PrevFilteredUntilSelection(".even", sel2).End()
|
||||
AssertEqual(t, sel, sel3)
|
||||
assertEqual(t, sel, sel3)
|
||||
}
|
||||
|
||||
func TestPrevFilteredUntilNodes(t *testing.T) {
|
||||
sel := Doc2().Find(".even")
|
||||
sel2 := Doc2().Find(".four")
|
||||
sel = sel.PrevFilteredUntilNodes(".odd", sel2.Nodes...)
|
||||
AssertLength(t, sel.Nodes, 2)
|
||||
AssertSelectionIs(t, sel, "#n2", "#nf2")
|
||||
assertLength(t, sel.Nodes, 2)
|
||||
assertSelectionIs(t, sel, "#n2", "#nf2")
|
||||
}
|
||||
|
||||
func TestPrevFilteredUntilNodesRollback(t *testing.T) {
|
||||
sel := Doc2().Find(".even")
|
||||
sel2 := Doc2().Find(".four")
|
||||
sel3 := sel.PrevFilteredUntilNodes(".odd", sel2.Nodes...).End()
|
||||
AssertEqual(t, sel, sel3)
|
||||
assertEqual(t, sel, sel3)
|
||||
}
|
||||
|
||||
func TestClosestItself(t *testing.T) {
|
||||
sel := Doc2().Find(".three")
|
||||
sel2 := sel.Closest(".row")
|
||||
AssertLength(t, sel2.Nodes, sel.Length())
|
||||
AssertSelectionIs(t, sel2, "#n3", "#nf3")
|
||||
assertLength(t, sel2.Nodes, sel.Length())
|
||||
assertSelectionIs(t, sel2, "#n3", "#nf3")
|
||||
}
|
||||
|
||||
func TestClosestNoDupes(t *testing.T) {
|
||||
sel := Doc().Find(".span12")
|
||||
sel2 := sel.Closest(".pvk-content")
|
||||
AssertLength(t, sel2.Nodes, 1)
|
||||
AssertClass(t, sel2, "pvk-content")
|
||||
assertLength(t, sel2.Nodes, 1)
|
||||
assertClass(t, sel2, "pvk-content")
|
||||
}
|
||||
|
||||
func TestClosestNone(t *testing.T) {
|
||||
sel := Doc().Find("h4")
|
||||
sel2 := sel.Closest("a")
|
||||
AssertLength(t, sel2.Nodes, 0)
|
||||
assertLength(t, sel2.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestClosestInvalid(t *testing.T) {
|
||||
sel := Doc().Find("h4")
|
||||
sel2 := sel.Closest("")
|
||||
assertLength(t, sel2.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestClosestMany(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.Closest(".pvk-content")
|
||||
AssertLength(t, sel2.Nodes, 2)
|
||||
AssertSelectionIs(t, sel2, "#pc1", "#pc2")
|
||||
assertLength(t, sel2.Nodes, 2)
|
||||
assertSelectionIs(t, sel2, "#pc1", "#pc2")
|
||||
}
|
||||
|
||||
func TestClosestRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.Closest(".pvk-content").End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestClosestSelectionItself(t *testing.T) {
|
||||
sel := Doc2().Find(".three")
|
||||
sel2 := sel.ClosestSelection(Doc2().Find(".row"))
|
||||
AssertLength(t, sel2.Nodes, sel.Length())
|
||||
assertLength(t, sel2.Nodes, sel.Length())
|
||||
}
|
||||
|
||||
func TestClosestSelectionNoDupes(t *testing.T) {
|
||||
sel := Doc().Find(".span12")
|
||||
sel2 := sel.ClosestSelection(Doc().Find(".pvk-content"))
|
||||
AssertLength(t, sel2.Nodes, 1)
|
||||
AssertClass(t, sel2, "pvk-content")
|
||||
assertLength(t, sel2.Nodes, 1)
|
||||
assertClass(t, sel2, "pvk-content")
|
||||
}
|
||||
|
||||
func TestClosestSelectionNone(t *testing.T) {
|
||||
sel := Doc().Find("h4")
|
||||
sel2 := sel.ClosestSelection(Doc().Find("a"))
|
||||
AssertLength(t, sel2.Nodes, 0)
|
||||
assertLength(t, sel2.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestClosestSelectionMany(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.ClosestSelection(Doc().Find(".pvk-content"))
|
||||
AssertLength(t, sel2.Nodes, 2)
|
||||
AssertSelectionIs(t, sel2, "#pc1", "#pc2")
|
||||
assertLength(t, sel2.Nodes, 2)
|
||||
assertSelectionIs(t, sel2, "#pc1", "#pc2")
|
||||
}
|
||||
|
||||
func TestClosestSelectionRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.ClosestSelection(Doc().Find(".pvk-content")).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestClosestNodesItself(t *testing.T) {
|
||||
sel := Doc2().Find(".three")
|
||||
sel2 := sel.ClosestNodes(Doc2().Find(".row").Nodes...)
|
||||
AssertLength(t, sel2.Nodes, sel.Length())
|
||||
assertLength(t, sel2.Nodes, sel.Length())
|
||||
}
|
||||
|
||||
func TestClosestNodesNoDupes(t *testing.T) {
|
||||
sel := Doc().Find(".span12")
|
||||
sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...)
|
||||
AssertLength(t, sel2.Nodes, 1)
|
||||
AssertClass(t, sel2, "pvk-content")
|
||||
assertLength(t, sel2.Nodes, 1)
|
||||
assertClass(t, sel2, "pvk-content")
|
||||
}
|
||||
|
||||
func TestClosestNodesNone(t *testing.T) {
|
||||
sel := Doc().Find("h4")
|
||||
sel2 := sel.ClosestNodes(Doc().Find("a").Nodes...)
|
||||
AssertLength(t, sel2.Nodes, 0)
|
||||
assertLength(t, sel2.Nodes, 0)
|
||||
}
|
||||
|
||||
func TestClosestNodesMany(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...)
|
||||
AssertLength(t, sel2.Nodes, 2)
|
||||
AssertSelectionIs(t, sel2, "#pc1", "#pc2")
|
||||
assertLength(t, sel2.Nodes, 2)
|
||||
assertSelectionIs(t, sel2, "#pc1", "#pc2")
|
||||
}
|
||||
|
||||
func TestClosestNodesRollback(t *testing.T) {
|
||||
sel := Doc().Find(".container-fluid")
|
||||
sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...).End()
|
||||
AssertEqual(t, sel, sel2)
|
||||
assertEqual(t, sel, sel2)
|
||||
}
|
||||
|
||||
func TestIssue26(t *testing.T) {
|
||||
@@ -692,6 +788,6 @@ func TestIssue26(t *testing.T) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sel := doc.Find("img[src]")
|
||||
AssertLength(t, sel.Nodes, c.l)
|
||||
assertLength(t, sel.Nodes, c.l)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/andybalholm/cascadia"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
@@ -29,6 +30,10 @@ func NewDocumentFromNode(root *html.Node) *Document {
|
||||
// NewDocument is a Document constructor that takes a string URL as argument.
|
||||
// It loads the specified document, parses it, and stores the root Document
|
||||
// node, ready to be manipulated.
|
||||
//
|
||||
// Deprecated: Use the net/http standard library package to make the request
|
||||
// and validate the response before calling goquery.NewDocumentFromReader
|
||||
// with the response's body.
|
||||
func NewDocument(url string) (*Document, error) {
|
||||
// Load the URL
|
||||
res, e := http.Get(url)
|
||||
@@ -38,10 +43,10 @@ func NewDocument(url string) (*Document, error) {
|
||||
return NewDocumentFromResponse(res)
|
||||
}
|
||||
|
||||
// NewDocumentFromReader returns a Document from a generic reader.
|
||||
// NewDocumentFromReader returns a Document from an io.Reader.
|
||||
// It returns an error as second value if the reader's data cannot be parsed
|
||||
// as html. It does *not* check if the reader is also an io.Closer, so the
|
||||
// provided reader is never closed by this call, it is the responsibility
|
||||
// as html. It does not check if the reader is also an io.Closer, the
|
||||
// provided reader is never closed by this call. It is the responsibility
|
||||
// of the caller to close it if required.
|
||||
func NewDocumentFromReader(r io.Reader) (*Document, error) {
|
||||
root, e := html.Parse(r)
|
||||
@@ -51,15 +56,19 @@ 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.
|
||||
//
|
||||
// Deprecated: Use goquery.NewDocumentFromReader with the response's body.
|
||||
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)
|
||||
@@ -71,6 +80,11 @@ func NewDocumentFromResponse(res *http.Response) (*Document, error) {
|
||||
return newDocument(root, res.Request.URL), nil
|
||||
}
|
||||
|
||||
// CloneDocument creates a deep-clone of a document.
|
||||
func CloneDocument(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
|
||||
@@ -97,3 +111,93 @@ func newEmptySelection(doc *Document) *Selection {
|
||||
func newSingleSelection(node *html.Node, doc *Document) *Selection {
|
||||
return &Selection{[]*html.Node{node}, doc, nil}
|
||||
}
|
||||
|
||||
// Matcher is an interface that defines the methods to match
|
||||
// HTML nodes against a compiled selector string. Cascadia's
|
||||
// Selector implements this interface.
|
||||
type Matcher interface {
|
||||
Match(*html.Node) bool
|
||||
MatchAll(*html.Node) []*html.Node
|
||||
Filter([]*html.Node) []*html.Node
|
||||
}
|
||||
|
||||
// Single compiles a selector string to a Matcher that stops after the first
|
||||
// match is found.
|
||||
//
|
||||
// By default, Selection.Find and other functions that accept a selector string
|
||||
// to select nodes will use all matches corresponding to that selector. By
|
||||
// using the Matcher returned by Single, at most the first match will be
|
||||
// selected.
|
||||
//
|
||||
// For example, those two statements are semantically equivalent:
|
||||
//
|
||||
// sel1 := doc.Find("a").First()
|
||||
// sel2 := doc.FindMatcher(goquery.Single("a"))
|
||||
//
|
||||
// The one using Single is optimized to be potentially much faster on large
|
||||
// documents.
|
||||
//
|
||||
// Only the behaviour of the MatchAll method of the Matcher interface is
|
||||
// altered compared to standard Matchers. This means that the single-selection
|
||||
// property of the Matcher only applies for Selection methods where the Matcher
|
||||
// is used to select nodes, not to filter or check if a node matches the
|
||||
// Matcher - in those cases, the behaviour of the Matcher is unchanged (e.g.
|
||||
// FilterMatcher(Single("div")) will still result in a Selection with multiple
|
||||
// "div"s if there were many "div"s in the Selection to begin with).
|
||||
func Single(selector string) Matcher {
|
||||
return singleMatcher{compileMatcher(selector)}
|
||||
}
|
||||
|
||||
// SingleMatcher returns a Matcher matches the same nodes as m, but that stops
|
||||
// after the first match is found.
|
||||
//
|
||||
// See the documentation of function Single for more details.
|
||||
func SingleMatcher(m Matcher) Matcher {
|
||||
if _, ok := m.(singleMatcher); ok {
|
||||
// m is already a singleMatcher
|
||||
return m
|
||||
}
|
||||
return singleMatcher{m}
|
||||
}
|
||||
|
||||
// compileMatcher compiles the selector string s and returns
|
||||
// the corresponding Matcher. If s is an invalid selector string,
|
||||
// it returns a Matcher that fails all matches.
|
||||
func compileMatcher(s string) Matcher {
|
||||
cs, err := cascadia.Compile(s)
|
||||
if err != nil {
|
||||
return invalidMatcher{}
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
type singleMatcher struct {
|
||||
Matcher
|
||||
}
|
||||
|
||||
func (m singleMatcher) MatchAll(n *html.Node) []*html.Node {
|
||||
// Optimized version - stops finding at the first match (cascadia-compiled
|
||||
// matchers all use this code path).
|
||||
if mm, ok := m.Matcher.(interface{ MatchFirst(*html.Node) *html.Node }); ok {
|
||||
node := mm.MatchFirst(n)
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return []*html.Node{node}
|
||||
}
|
||||
|
||||
// Fallback version, for e.g. test mocks that don't provide the MatchFirst
|
||||
// method.
|
||||
nodes := m.Matcher.MatchAll(n)
|
||||
if len(nodes) > 0 {
|
||||
return nodes[:1:1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// invalidMatcher is a Matcher that always fails to match.
|
||||
type invalidMatcher struct{}
|
||||
|
||||
func (invalidMatcher) Match(n *html.Node) bool { return false }
|
||||
func (invalidMatcher) MatchAll(n *html.Node) []*html.Node { return nil }
|
||||
func (invalidMatcher) Filter(ns []*html.Node) []*html.Node { return nil }
|
||||
|
||||
+105
-10
@@ -4,43 +4,64 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/andybalholm/cascadia"
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
// Test helper functions and members
|
||||
var doc *Document
|
||||
var doc2 *Document
|
||||
var doc3 *Document
|
||||
var docB *Document
|
||||
var docW *Document
|
||||
|
||||
func Doc() *Document {
|
||||
if doc == nil {
|
||||
doc = LoadDoc("page.html")
|
||||
doc = loadDoc("page.html")
|
||||
}
|
||||
return doc
|
||||
}
|
||||
|
||||
func Doc2() *Document {
|
||||
if doc2 == nil {
|
||||
doc2 = LoadDoc("page2.html")
|
||||
doc2 = loadDoc("page2.html")
|
||||
}
|
||||
return doc2
|
||||
}
|
||||
|
||||
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")
|
||||
docB = loadDoc("gotesting.html")
|
||||
}
|
||||
return docB
|
||||
}
|
||||
|
||||
func DocW() *Document {
|
||||
if docW == nil {
|
||||
docW = LoadDoc("gowiki.html")
|
||||
docW = loadDoc("gowiki.html")
|
||||
}
|
||||
return docW
|
||||
}
|
||||
|
||||
func AssertLength(t *testing.T, nodes []*html.Node, length int) {
|
||||
func assertLength(t *testing.T, nodes []*html.Node, length int) {
|
||||
if len(nodes) != length {
|
||||
t.Errorf("Expected %d nodes, found %d.", length, len(nodes))
|
||||
for i, n := range nodes {
|
||||
@@ -49,25 +70,25 @@ func AssertLength(t *testing.T, nodes []*html.Node, length int) {
|
||||
}
|
||||
}
|
||||
|
||||
func AssertClass(t *testing.T, sel *Selection, class string) {
|
||||
func assertClass(t *testing.T, sel *Selection, class string) {
|
||||
if !sel.HasClass(class) {
|
||||
t.Errorf("Expected node to have class %s, found %+v.", class, sel.Get(0))
|
||||
}
|
||||
}
|
||||
|
||||
func AssertPanic(t *testing.T) {
|
||||
func assertPanic(t *testing.T) {
|
||||
if e := recover(); e == nil {
|
||||
t.Error("Expected a panic.")
|
||||
}
|
||||
}
|
||||
|
||||
func AssertEqual(t *testing.T, s1 *Selection, s2 *Selection) {
|
||||
func assertEqual(t *testing.T, s1 *Selection, s2 *Selection) {
|
||||
if s1 != s2 {
|
||||
t.Error("Expected selection objects to be the same.")
|
||||
}
|
||||
}
|
||||
|
||||
func AssertSelectionIs(t *testing.T, sel *Selection, is ...string) {
|
||||
func assertSelectionIs(t *testing.T, sel *Selection, is ...string) {
|
||||
for i := 0; i < sel.Length(); i++ {
|
||||
if !sel.Eq(i).Is(is[i]) {
|
||||
t.Errorf("Expected node %d to be %s, found %+v", i, is[i], sel.Get(i))
|
||||
@@ -75,7 +96,17 @@ func AssertSelectionIs(t *testing.T, sel *Selection, is ...string) {
|
||||
}
|
||||
}
|
||||
|
||||
func LoadDoc(page string) *Document {
|
||||
func printSel(t *testing.T, sel *Selection) {
|
||||
if testing.Verbose() {
|
||||
h, err := sel.Html()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log(h)
|
||||
}
|
||||
}
|
||||
|
||||
func loadDoc(page string) *Document {
|
||||
var f *os.File
|
||||
var e error
|
||||
|
||||
@@ -91,6 +122,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())
|
||||
@@ -158,3 +197,59 @@ func TestNewDocumentFromResponseNil(t *testing.T) {
|
||||
t.Error("Expected error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue103(t *testing.T) {
|
||||
d, err := NewDocumentFromReader(strings.NewReader("<html><title>Scientists Stored These Images in DNA—Then Flawlessly Retrieved Them</title></html>"))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
text := d.Find("title").Text()
|
||||
for i, r := range text {
|
||||
t.Logf("%d: %d - %q\n", i, r, string(r))
|
||||
}
|
||||
t.Log(text)
|
||||
}
|
||||
|
||||
func TestSingle(t *testing.T) {
|
||||
data := `
|
||||
<html>
|
||||
<body>
|
||||
<div class="b">1</div>
|
||||
<div class="a">2</div>
|
||||
<div class="a">3</div>
|
||||
<p class="b">4</p>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
doc, err := NewDocumentFromReader(strings.NewReader(data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
text := doc.FindMatcher(Single("div")).Text()
|
||||
if text != "1" {
|
||||
t.Fatalf("want %q, got %q", "1", text)
|
||||
}
|
||||
|
||||
// Verify semantic equivalence
|
||||
sel1 := doc.Find("div").First()
|
||||
sel2 := doc.FindMatcher(Single("div"))
|
||||
if sel1.Text() != sel2.Text() {
|
||||
t.Fatalf("want sel1 to equal sel2")
|
||||
}
|
||||
|
||||
// Here, the Single has no effect as the selector is used to filter
|
||||
// from the existing selection, not to find nodes in the document.
|
||||
divs := doc.Find("div")
|
||||
text = divs.FilterMatcher(Single(".a")).Text()
|
||||
if text != "23" {
|
||||
t.Fatalf("want %q, got %q", "23", text)
|
||||
}
|
||||
|
||||
classA := cascadia.MustCompile(".a")
|
||||
classB := cascadia.MustCompile(".b")
|
||||
text = doc.FindMatcher(classB).AddMatcher(SingleMatcher(classA)).Text()
|
||||
if text != "142" {
|
||||
t.Fatalf("want %q, got %q", "142", text)
|
||||
}
|
||||
}
|
||||
|
||||
+94
-7
@@ -1,14 +1,80 @@
|
||||
package goquery
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"golang.org/x/net/html"
|
||||
)
|
||||
|
||||
func getChildren(n *html.Node) (result []*html.Node) {
|
||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
||||
result = append(result, c)
|
||||
// used to determine if a set (map[*html.Node]bool) should be used
|
||||
// instead of iterating over a slice. The set uses more memory and
|
||||
// is slower than slice iteration for small N.
|
||||
const minNodesForSet = 1000
|
||||
|
||||
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 ""
|
||||
}
|
||||
return result
|
||||
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 node.Data
|
||||
default:
|
||||
if node.Type >= 0 && int(node.Type) < len(nodeNames) {
|
||||
return nodeNames[node.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
|
||||
}
|
||||
|
||||
// Loop through all container nodes to search for the target node.
|
||||
@@ -54,11 +120,32 @@ func indexInSlice(slice []*html.Node, node *html.Node) int {
|
||||
// Appends the new nodes to the target slice, making sure no duplicate is added.
|
||||
// There is no check to the original state of the target slice, so it may still
|
||||
// contain duplicates. The target slice is returned because append() may create
|
||||
// a new underlying array.
|
||||
func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node) []*html.Node {
|
||||
// a new underlying array. If targetSet is nil, a local set is created with the
|
||||
// target if len(target) + len(nodes) is greater than minNodesForSet.
|
||||
func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node {
|
||||
// if there are not that many nodes, don't use the map, faster to just use nested loops
|
||||
// (unless a non-nil targetSet is passed, in which case the caller knows better).
|
||||
if targetSet == nil && len(target)+len(nodes) < minNodesForSet {
|
||||
for _, n := range nodes {
|
||||
if !isInSlice(target, n) {
|
||||
target = append(target, n)
|
||||
}
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
// if a targetSet is passed, then assume it is reliable, otherwise create one
|
||||
// and initialize it with the current target contents.
|
||||
if targetSet == nil {
|
||||
targetSet = make(map[*html.Node]bool, len(target))
|
||||
for _, n := range target {
|
||||
targetSet[n] = true
|
||||
}
|
||||
}
|
||||
for _, n := range nodes {
|
||||
if !isInSlice(target, n) {
|
||||
if !targetSet[n] {
|
||||
target = append(target, n)
|
||||
targetSet[n] = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.Errorf("want %v, got %v", in, out)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user