├── .gitattributes ├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── array.go ├── array_test.go ├── bench ├── v0.1.0 ├── v0.1.1 ├── v0.1.1-v0.2.1-go1.1rc1.svg ├── v0.2.0 ├── v0.2.0-v0.2.1-go1.1rc1.svg ├── v0.2.1-go1.1rc1 ├── v0.3.0 ├── v0.3.2-go1.2 ├── v0.3.2-go1.2-take2 ├── v0.3.2-go1.2rc1 ├── v1.0.0-go1.7 ├── v1.0.1a-go1.7 ├── v1.0.1b-go1.7 └── v1.0.1c-go1.7 ├── bench_array_test.go ├── bench_example_test.go ├── bench_expand_test.go ├── bench_filter_test.go ├── bench_iteration_test.go ├── bench_property_test.go ├── bench_query_test.go ├── bench_traversal_test.go ├── doc.go ├── doc └── tips.md ├── example_test.go ├── expand.go ├── expand_test.go ├── filter.go ├── filter_test.go ├── go.mod ├── go.sum ├── iteration.go ├── iteration_test.go ├── manipulation.go ├── manipulation_test.go ├── misc └── git │ └── pre-commit ├── property.go ├── property_test.go ├── query.go ├── query_test.go ├── testdata ├── gotesting.html ├── gowiki.html ├── metalreview.html ├── page.html ├── page2.html └── page3.html ├── traversal.go ├── traversal_test.go ├── type.go ├── type_test.go ├── utilities.go └── utilities_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | testdata/* linguist-vendored 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mna] 2 | custom: ["https://www.buymeacoffee.com/mna"] 3 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for Go 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | # Enable version updates for GitHub action workflows 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: "daily" 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push, pull_request] 3 | 4 | env: 5 | GOPROXY: https://proxy.golang.org,direct 6 | 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | go-version: [1.23.x, 1.24.x] 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | runs-on: ${{ matrix.os }} 14 | 15 | steps: 16 | - name: Checkout code 17 | uses: actions/checkout@v4 18 | 19 | - name: Install Go 20 | uses: actions/setup-go@v5 21 | with: 22 | go-version: ${{ matrix.go-version }} 23 | 24 | - name: Test 25 | run: go test ./... -v -cover 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # editor temporary files 2 | *.sublime-* 3 | .DS_Store 4 | *.swp 5 | #*.*# 6 | tags 7 | 8 | # direnv config 9 | .env* 10 | 11 | # test binaries 12 | *.test 13 | 14 | # coverage and profilte outputs 15 | *.out 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2021, Martin Angers & Contributors 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | * Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goquery - a little like that j-thing, only in Go 2 | 3 | [![Build Status](https://github.com/PuerkitoBio/goquery/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/PuerkitoBio/goquery/actions) 4 | [![Go Reference](https://pkg.go.dev/badge/github.com/PuerkitoBio/goquery.svg)](https://pkg.go.dev/github.com/PuerkitoBio/goquery) 5 | [![Sourcegraph Badge](https://sourcegraph.com/github.com/PuerkitoBio/goquery/-/badge.svg)](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge) 6 | 7 | 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. 8 | 9 | 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. 10 | 11 | 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]...). 12 | 13 | ## Table of Contents 14 | 15 | * [Installation](#installation) 16 | * [Changelog](#changelog) 17 | * [API](#api) 18 | * [Examples](#examples) 19 | * [Related Projects](#related-projects) 20 | * [Support](#support) 21 | * [License](#license) 22 | 23 | ## Installation 24 | 25 | Required Go version: 26 | 27 | * Starting with version `v1.10.0` of goquery, Go 1.23+ is required due to the use of function-based iterators. 28 | * For `v1.9.0` of goquery, Go 1.18+ is required due to the use of generics. 29 | * For previous goquery versions, a Go version of 1.1+ was required because of the `net/html` dependency. 30 | 31 | Ongoing goquery development is tested on the latest 2 versions of Go. 32 | 33 | $ go get github.com/PuerkitoBio/goquery 34 | 35 | (optional) To run unit tests: 36 | 37 | $ cd $GOPATH/src/github.com/PuerkitoBio/goquery 38 | $ go test 39 | 40 | (optional) To run benchmarks (warning: it runs for a few minutes): 41 | 42 | $ cd $GOPATH/src/github.com/PuerkitoBio/goquery 43 | $ go test -bench=".*" 44 | 45 | ## Changelog 46 | 47 | **Note that goquery's API is now stable, and will not break.** 48 | 49 | * **2025-04-11 (v1.10.3)** : Update `go.mod` dependencies, small optimization (thanks [@myxzlpltk](https://github.com/myxzlpltk)). 50 | * **2025-02-13 (v1.10.2)** : Update `go.mod` dependencies, add go1.24 to the test matrix. 51 | * **2024-12-26 (v1.10.1)** : Update `go.mod` dependencies. 52 | * **2024-09-06 (v1.10.0)** : Add `EachIter` which provides an iterator that can be used in `for..range` loops on the `*Selection` object. **goquery now requires Go version 1.23+** (thanks [@amikai](https://github.com/amikai)). 53 | * **2024-09-06 (v1.9.3)** : Update `go.mod` dependencies. 54 | * **2024-04-29 (v1.9.2)** : Update `go.mod` dependencies. 55 | * **2024-02-29 (v1.9.1)** : Improve allocation and performance of the `Map` function and `Selection.Map` method, better document the cascadia differences (thanks [@jwilsson](https://github.com/jwilsson)). 56 | * **2024-02-22 (v1.9.0)** : Add a generic `Map` function, **goquery now requires Go version 1.18+** (thanks [@Fesaa](https://github.com/Fesaa)). 57 | * **2023-02-18 (v1.8.1)** : Update `go.mod` dependencies, update CI workflow. 58 | * **2021-10-25 (v1.8.0)** : Add `Render` function to render a `Selection` to an `io.Writer` (thanks [@anthonygedeon](https://github.com/anthonygedeon)). 59 | * **2021-07-11 (v1.7.1)** : Update go.mod dependencies and add dependabot config (thanks [@jauderho](https://github.com/jauderho)). 60 | * **2021-06-14 (v1.7.0)** : Add `Single` and `SingleMatcher` functions to optimize first-match selection (thanks [@gdollardollar](https://github.com/gdollardollar)). 61 | * **2021-01-11 (v1.6.1)** : Fix panic when calling `{Prepend,Append,Set}Html` on a `Selection` that contains non-Element nodes. 62 | * **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. 63 | * **2020-02-04 (v1.5.1)** : Update module dependencies. 64 | * **2018-11-15 (v1.5.0)** : Go module support (thanks @Zaba505). 65 | * **2018-06-07 (v1.4.1)** : Add `NewDocumentFromReader` examples. 66 | * **2018-03-24 (v1.4.0)** : Deprecate `NewDocument(url)` and `NewDocumentFromResponse(response)`. 67 | * **2018-01-28 (v1.3.0)** : Add `ToEnd` constant to `Slice` until the end of the selection (thanks to @davidjwilkins for raising the issue). 68 | * **2018-01-11 (v1.2.0)** : Add `AddBack*` and deprecate `AndSelf` (thanks to @davidjwilkins). 69 | * **2017-02-12 (v1.1.0)** : Add `SetHtml` and `SetText` (thanks to @glebtv). 70 | * **2016-12-29 (v1.0.2)** : Optimize allocations for `Selection.Text` (thanks to @radovskyb). 71 | * **2016-08-28 (v1.0.1)** : Optimize performance for large documents. 72 | * **2016-07-27 (v1.0.0)** : Tag version 1.0.0. 73 | * **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. 74 | * **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`). 75 | * **2015-04-20** : Add `AttrOr` helper method to return the attribute's value or a default value if absent. Thanks to [piotrkowalczuk][piotr]. 76 | * **2015-02-04** : Add more manipulation functions - Prepend* - thanks again to [Andrew Stone][thatguystone]. 77 | * **2014-11-28** : Add more manipulation functions - ReplaceWith*, Wrap* and Unwrap - thanks again to [Andrew Stone][thatguystone]. 78 | * **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. 79 | * **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. 80 | * **v0.3.2** : Add `NewDocumentFromReader()` (thanks jweir) which allows creating a goquery document from an io.Reader. 81 | * **v0.3.1** : Add `NewDocumentFromResponse()` (thanks assassingj) which allows creating a goquery document from an http response. 82 | * **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. 83 | * **v0.2.1** : Make go-getable, now that [go.net/html is Go1.0-compatible][gonet] (thanks to @matrixik for pointing this out). 84 | * **v0.2.0** : Add support for negative indices in Slice(). **BREAKING CHANGE** `Document.Root` is removed, `Document` is now a `Selection` itself (a selection of one, the root element, just like `Document.Root` was before). Add jQuery's Closest() method. 85 | * **v0.1.1** : Add benchmarks to use as baseline for refactorings, refactor Next...() and Prev...() methods to use the new html package's linked list features (Next/PrevSibling, FirstChild). Good performance boost (40+% in some cases). 86 | * **v0.1.0** : Initial release. 87 | 88 | ## API 89 | 90 | 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. 91 | 92 | 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: 93 | 94 | * 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()`) 95 | * When the jQuery equivalent **requires** one argument, the same name as jQuery is used for the selector string version (e.g.: `Is()`) 96 | * The signatures accepting a jQuery object as argument are defined in goquery as `XxxSelection()` and take a `*Selection` object as argument (e.g.: `FilterSelection()`) 97 | * 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()`) 98 | * The signatures accepting a function as argument in jQuery are defined in goquery as `XxxFunction()` and take a function as argument (e.g.: `FilterFunction()`) 99 | * 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()`) 100 | 101 | 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). 102 | 103 | The complete [package reference documentation can be found here][doc]. 104 | 105 | Please note that Cascadia's selectors do not necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details. Also, the selectors work more like the DOM's `querySelectorAll`, than jQuery's matchers - they have no concept of contextual matching (for some concrete examples of what that means, see [this ticket](https://github.com/andybalholm/cascadia/issues/61)). In practice, it doesn't matter very often but it's something worth mentioning. 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): 106 | 107 | * `Find("~")` returns an empty selection because the selector string doesn't match anything. 108 | * `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). 109 | * `ParentsFiltered("~")` returns an empty selection because the selector string doesn't match anything. 110 | * `ParentsUntil("~")` returns all parents of the selection because the selector string didn't match any element to stop before the top element. 111 | 112 | ## Examples 113 | 114 | See some tips and tricks in the [wiki][]. 115 | 116 | Adapted from example_test.go: 117 | 118 | ```Go 119 | package main 120 | 121 | import ( 122 | "fmt" 123 | "log" 124 | "net/http" 125 | 126 | "github.com/PuerkitoBio/goquery" 127 | ) 128 | 129 | func ExampleScrape() { 130 | // Request the HTML page. 131 | res, err := http.Get("http://metalsucks.net") 132 | if err != nil { 133 | log.Fatal(err) 134 | } 135 | defer res.Body.Close() 136 | if res.StatusCode != 200 { 137 | log.Fatalf("status code error: %d %s", res.StatusCode, res.Status) 138 | } 139 | 140 | // Load the HTML document 141 | doc, err := goquery.NewDocumentFromReader(res.Body) 142 | if err != nil { 143 | log.Fatal(err) 144 | } 145 | 146 | // Find the review items 147 | doc.Find(".left-content article .post-title").Each(func(i int, s *goquery.Selection) { 148 | // For each item found, get the title 149 | title := s.Find("a").Text() 150 | fmt.Printf("Review %d: %s\n", i, title) 151 | }) 152 | } 153 | 154 | func main() { 155 | ExampleScrape() 156 | } 157 | ``` 158 | 159 | ## Related Projects 160 | 161 | - [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags. 162 | - [andybalholm/cascadia][cascadia], the CSS selector library used by goquery. 163 | - [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors. 164 | - [gocolly/colly](https://github.com/gocolly/colly), a lightning fast and elegant Scraping Framework 165 | - [gnulnx/goperf](https://github.com/gnulnx/goperf), a website performance test tool that also fetches static assets. 166 | - [MontFerret/ferret](https://github.com/MontFerret/ferret), declarative web scraping. 167 | - [tacusci/berrycms](https://github.com/tacusci/berrycms), a modern simple to use CMS with easy to write plugins 168 | - [Dataflow kit](https://github.com/slotix/dataflowkit), Web Scraping framework for Gophers. 169 | - [Geziyor](https://github.com/geziyor/geziyor), a fast web crawling & scraping framework for Go. Supports JS rendering. 170 | - [Pagser](https://github.com/foolin/pagser), a simple, easy, extensible, configurable HTML parser to struct based on goquery and struct tags. 171 | - [stitcherd](https://github.com/vhodges/stitcherd), A server for doing server side includes using css selectors and DOM updates. 172 | - [goskyr](https://github.com/jakopako/goskyr), an easily configurable command-line scraper written in Go. 173 | - [goGetJS](https://github.com/davemolk/goGetJS), a tool for extracting, searching, and saving JavaScript files (with optional headless browser). 174 | - [fitter](https://github.com/PxyUp/fitter), a tool for selecting values from JSON, XML, HTML and XPath formatted pages. 175 | - [seltabl](github.com/conneroisu/seltabl), an orm-like package and supporting language server for extracting values from HTML 176 | 177 | ## Support 178 | 179 | There are a number of ways you can support the project: 180 | 181 | * Use it, star it, build something with it, spread the word! 182 | - 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! 183 | * Raise issues to improve the project (note: doc typos and clarifications are issues too!) 184 | - Please search existing issues before opening a new one - it may have already been addressed. 185 | * Pull requests: please discuss new code in an issue first, unless the fix is really trivial. 186 | - Make sure new code is tested. 187 | - Be mindful of existing code - PRs that break existing code have a high probability of being declined, unless it fixes a serious issue. 188 | * Sponsor the developer 189 | - See the Github Sponsor button at the top of the repo on github 190 | - or via BuyMeACoffee.com, below 191 | 192 | Buy Me A Coffee 193 | 194 | ## License 195 | 196 | The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic]. 197 | 198 | [jquery]: https://jquery.com/ 199 | [go]: https://go.dev/ 200 | [cascadia]: https://github.com/andybalholm/cascadia 201 | [cascadiacli]: https://github.com/suntong/cascadia 202 | [bsd]: https://opensource.org/licenses/BSD-3-Clause 203 | [golic]: https://go.dev/LICENSE 204 | [caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE 205 | [doc]: https://pkg.go.dev/github.com/PuerkitoBio/goquery 206 | [index]: https://api.jquery.com/index/ 207 | [gonet]: https://github.com/golang/net/ 208 | [html]: https://pkg.go.dev/golang.org/x/net/html 209 | [wiki]: https://github.com/PuerkitoBio/goquery/wiki/Tips-and-tricks 210 | [thatguystone]: https://github.com/thatguystone 211 | [piotr]: https://github.com/piotrkowalczuk 212 | [goq]: https://github.com/andrewstuart/goq 213 | [thiemok]: https://github.com/thiemok 214 | [djw]: https://github.com/davidjwilkins 215 | -------------------------------------------------------------------------------- /array.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "golang.org/x/net/html" 5 | ) 6 | 7 | const ( 8 | maxUint = ^uint(0) 9 | maxInt = int(maxUint >> 1) 10 | 11 | // ToEnd is a special index value that can be used as end index in a call 12 | // to Slice so that all elements are selected until the end of the Selection. 13 | // It is equivalent to passing (*Selection).Length(). 14 | ToEnd = maxInt 15 | ) 16 | 17 | // First reduces the set of matched elements to the first in the set. 18 | // It returns a new Selection object, and an empty Selection object if the 19 | // the selection is empty. 20 | func (s *Selection) First() *Selection { 21 | return s.Eq(0) 22 | } 23 | 24 | // Last reduces the set of matched elements to the last in the set. 25 | // It returns a new Selection object, and an empty Selection object if 26 | // the selection is empty. 27 | func (s *Selection) Last() *Selection { 28 | return s.Eq(-1) 29 | } 30 | 31 | // Eq reduces the set of matched elements to the one at the specified index. 32 | // If a negative index is given, it counts backwards starting at the end of the 33 | // set. It returns a new Selection object, and an empty Selection object if the 34 | // index is invalid. 35 | func (s *Selection) Eq(index int) *Selection { 36 | if index < 0 { 37 | index += len(s.Nodes) 38 | } 39 | 40 | if index >= len(s.Nodes) || index < 0 { 41 | return newEmptySelection(s.document) 42 | } 43 | 44 | return s.Slice(index, index+1) 45 | } 46 | 47 | // Slice reduces the set of matched elements to a subset specified by a range 48 | // of indices. The start index is 0-based and indicates the index of the first 49 | // element to select. The end index is 0-based and indicates the index at which 50 | // the elements stop being selected (the end index is not selected). 51 | // 52 | // The indices may be negative, in which case they represent an offset from the 53 | // end of the selection. 54 | // 55 | // The special value ToEnd may be specified as end index, in which case all elements 56 | // until the end are selected. This works both for a positive and negative start 57 | // index. 58 | func (s *Selection) Slice(start, end int) *Selection { 59 | if start < 0 { 60 | start += len(s.Nodes) 61 | } 62 | if end == ToEnd { 63 | end = len(s.Nodes) 64 | } else if end < 0 { 65 | end += len(s.Nodes) 66 | } 67 | return pushStack(s, s.Nodes[start:end]) 68 | } 69 | 70 | // Get retrieves the underlying node at the specified index. 71 | // Get without parameter is not implemented, since the node array is available 72 | // on the Selection object. 73 | func (s *Selection) Get(index int) *html.Node { 74 | if index < 0 { 75 | index += len(s.Nodes) // Negative index gets from the end 76 | } 77 | return s.Nodes[index] 78 | } 79 | 80 | // Index returns the position of the first element within the Selection object 81 | // relative to its sibling elements. 82 | func (s *Selection) Index() int { 83 | if len(s.Nodes) > 0 { 84 | return newSingleSelection(s.Nodes[0], s.document).PrevAll().Length() 85 | } 86 | return -1 87 | } 88 | 89 | // IndexSelector returns the position of the first element within the 90 | // Selection object relative to the elements matched by the selector, or -1 if 91 | // not found. 92 | func (s *Selection) IndexSelector(selector string) int { 93 | if len(s.Nodes) > 0 { 94 | sel := s.document.Find(selector) 95 | return indexInSlice(sel.Nodes, s.Nodes[0]) 96 | } 97 | return -1 98 | } 99 | 100 | // IndexMatcher returns the position of the first element within the 101 | // Selection object relative to the elements matched by the matcher, or -1 if 102 | // not found. 103 | func (s *Selection) IndexMatcher(m Matcher) int { 104 | if len(s.Nodes) > 0 { 105 | sel := s.document.FindMatcher(m) 106 | return indexInSlice(sel.Nodes, s.Nodes[0]) 107 | } 108 | return -1 109 | } 110 | 111 | // IndexOfNode returns the position of the specified node within the Selection 112 | // object, or -1 if not found. 113 | func (s *Selection) IndexOfNode(node *html.Node) int { 114 | return indexInSlice(s.Nodes, node) 115 | } 116 | 117 | // IndexOfSelection returns the position of the first node in the specified 118 | // Selection object within this Selection object, or -1 if not found. 119 | func (s *Selection) IndexOfSelection(sel *Selection) int { 120 | if sel != nil && len(sel.Nodes) > 0 { 121 | return indexInSlice(s.Nodes, sel.Nodes[0]) 122 | } 123 | return -1 124 | } 125 | -------------------------------------------------------------------------------- /array_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFirst(t *testing.T) { 8 | sel := Doc().Find(".pvk-content").First() 9 | assertLength(t, sel.Nodes, 1) 10 | } 11 | 12 | func TestFirstEmpty(t *testing.T) { 13 | sel := Doc().Find(".pvk-zzcontentzz").First() 14 | assertLength(t, sel.Nodes, 0) 15 | } 16 | 17 | func TestFirstInvalid(t *testing.T) { 18 | sel := Doc().Find("").First() 19 | assertLength(t, sel.Nodes, 0) 20 | } 21 | 22 | func TestFirstRollback(t *testing.T) { 23 | sel := Doc().Find(".pvk-content") 24 | sel2 := sel.First().End() 25 | assertEqual(t, sel, sel2) 26 | } 27 | 28 | func TestLast(t *testing.T) { 29 | sel := Doc().Find(".pvk-content").Last() 30 | assertLength(t, sel.Nodes, 1) 31 | 32 | // Should contain Footer 33 | foot := Doc().Find(".footer") 34 | if !sel.Contains(foot.Nodes[0]) { 35 | t.Error("Last .pvk-content should contain .footer.") 36 | } 37 | } 38 | 39 | func TestLastEmpty(t *testing.T) { 40 | sel := Doc().Find(".pvk-zzcontentzz").Last() 41 | assertLength(t, sel.Nodes, 0) 42 | } 43 | 44 | func TestLastInvalid(t *testing.T) { 45 | sel := Doc().Find("").Last() 46 | assertLength(t, sel.Nodes, 0) 47 | } 48 | 49 | func TestLastRollback(t *testing.T) { 50 | sel := Doc().Find(".pvk-content") 51 | sel2 := sel.Last().End() 52 | assertEqual(t, sel, sel2) 53 | } 54 | 55 | func TestEq(t *testing.T) { 56 | sel := Doc().Find(".pvk-content").Eq(1) 57 | assertLength(t, sel.Nodes, 1) 58 | } 59 | 60 | func TestEqNegative(t *testing.T) { 61 | sel := Doc().Find(".pvk-content").Eq(-1) 62 | assertLength(t, sel.Nodes, 1) 63 | 64 | // Should contain Footer 65 | foot := Doc().Find(".footer") 66 | if !sel.Contains(foot.Nodes[0]) { 67 | t.Error("Index -1 of .pvk-content should contain .footer.") 68 | } 69 | } 70 | 71 | func TestEqEmpty(t *testing.T) { 72 | sel := Doc().Find("something_random_that_does_not_exists").Eq(0) 73 | assertLength(t, sel.Nodes, 0) 74 | } 75 | 76 | func TestEqInvalid(t *testing.T) { 77 | sel := Doc().Find("").Eq(0) 78 | assertLength(t, sel.Nodes, 0) 79 | } 80 | 81 | func TestEqInvalidPositive(t *testing.T) { 82 | sel := Doc().Find(".pvk-content").Eq(3) 83 | assertLength(t, sel.Nodes, 0) 84 | } 85 | 86 | func TestEqInvalidNegative(t *testing.T) { 87 | sel := Doc().Find(".pvk-content").Eq(-4) 88 | assertLength(t, sel.Nodes, 0) 89 | } 90 | 91 | func TestEqRollback(t *testing.T) { 92 | sel := Doc().Find(".pvk-content") 93 | sel2 := sel.Eq(1).End() 94 | assertEqual(t, sel, sel2) 95 | } 96 | 97 | func TestSlice(t *testing.T) { 98 | sel := Doc().Find(".pvk-content").Slice(0, 2) 99 | 100 | assertLength(t, sel.Nodes, 2) 101 | assertSelectionIs(t, sel, "#pc1", "#pc2") 102 | } 103 | 104 | func TestSliceToEnd(t *testing.T) { 105 | sel := Doc().Find(".pvk-content").Slice(1, ToEnd) 106 | 107 | assertLength(t, sel.Nodes, 2) 108 | assertSelectionIs(t, sel.Eq(0), "#pc2") 109 | if _, ok := sel.Eq(1).Attr("id"); ok { 110 | t.Error("Want no attribute ID, got one") 111 | } 112 | } 113 | 114 | func TestSliceEmpty(t *testing.T) { 115 | defer assertPanic(t) 116 | Doc().Find("x").Slice(0, 2) 117 | } 118 | 119 | func TestSliceInvalid(t *testing.T) { 120 | defer assertPanic(t) 121 | Doc().Find("").Slice(0, 2) 122 | } 123 | 124 | func TestSliceInvalidToEnd(t *testing.T) { 125 | defer assertPanic(t) 126 | Doc().Find("").Slice(2, ToEnd) 127 | } 128 | 129 | func TestSliceOutOfBounds(t *testing.T) { 130 | defer assertPanic(t) 131 | Doc().Find(".pvk-content").Slice(2, 12) 132 | } 133 | 134 | func TestNegativeSliceStart(t *testing.T) { 135 | sel := Doc().Find(".container-fluid").Slice(-2, 3) 136 | assertLength(t, sel.Nodes, 1) 137 | assertSelectionIs(t, sel.Eq(0), "#cf3") 138 | } 139 | 140 | func TestNegativeSliceEnd(t *testing.T) { 141 | sel := Doc().Find(".container-fluid").Slice(1, -1) 142 | assertLength(t, sel.Nodes, 2) 143 | assertSelectionIs(t, sel.Eq(0), "#cf2") 144 | assertSelectionIs(t, sel.Eq(1), "#cf3") 145 | } 146 | 147 | func TestNegativeSliceBoth(t *testing.T) { 148 | sel := Doc().Find(".container-fluid").Slice(-3, -1) 149 | assertLength(t, sel.Nodes, 2) 150 | assertSelectionIs(t, sel.Eq(0), "#cf2") 151 | assertSelectionIs(t, sel.Eq(1), "#cf3") 152 | } 153 | 154 | func TestNegativeSliceToEnd(t *testing.T) { 155 | sel := Doc().Find(".container-fluid").Slice(-3, ToEnd) 156 | assertLength(t, sel.Nodes, 3) 157 | assertSelectionIs(t, sel, "#cf2", "#cf3", "#cf4") 158 | } 159 | 160 | func TestNegativeSliceOutOfBounds(t *testing.T) { 161 | defer assertPanic(t) 162 | Doc().Find(".container-fluid").Slice(-12, -7) 163 | } 164 | 165 | func TestSliceRollback(t *testing.T) { 166 | sel := Doc().Find(".pvk-content") 167 | sel2 := sel.Slice(0, 2).End() 168 | assertEqual(t, sel, sel2) 169 | } 170 | 171 | func TestGet(t *testing.T) { 172 | sel := Doc().Find(".pvk-content") 173 | node := sel.Get(1) 174 | if sel.Nodes[1] != node { 175 | t.Errorf("Expected node %v to be %v.", node, sel.Nodes[1]) 176 | } 177 | } 178 | 179 | func TestGetNegative(t *testing.T) { 180 | sel := Doc().Find(".pvk-content") 181 | node := sel.Get(-3) 182 | if sel.Nodes[0] != node { 183 | t.Errorf("Expected node %v to be %v.", node, sel.Nodes[0]) 184 | } 185 | } 186 | 187 | func TestGetInvalid(t *testing.T) { 188 | defer assertPanic(t) 189 | sel := Doc().Find(".pvk-content") 190 | sel.Get(129) 191 | } 192 | 193 | func TestIndex(t *testing.T) { 194 | sel := Doc().Find(".pvk-content") 195 | if i := sel.Index(); i != 1 { 196 | t.Errorf("Expected index of 1, got %v.", i) 197 | } 198 | } 199 | 200 | func TestIndexSelector(t *testing.T) { 201 | sel := Doc().Find(".hero-unit") 202 | if i := sel.IndexSelector("div"); i != 4 { 203 | t.Errorf("Expected index of 4, got %v.", i) 204 | } 205 | } 206 | 207 | func TestIndexSelectorInvalid(t *testing.T) { 208 | sel := Doc().Find(".hero-unit") 209 | if i := sel.IndexSelector(""); i != -1 { 210 | t.Errorf("Expected index of -1, got %v.", i) 211 | } 212 | } 213 | 214 | func TestIndexOfNode(t *testing.T) { 215 | sel := Doc().Find("div.pvk-gutter") 216 | if i := sel.IndexOfNode(sel.Nodes[1]); i != 1 { 217 | t.Errorf("Expected index of 1, got %v.", i) 218 | } 219 | } 220 | 221 | func TestIndexOfNilNode(t *testing.T) { 222 | sel := Doc().Find("div.pvk-gutter") 223 | if i := sel.IndexOfNode(nil); i != -1 { 224 | t.Errorf("Expected index of -1, got %v.", i) 225 | } 226 | } 227 | 228 | func TestIndexOfSelection(t *testing.T) { 229 | sel := Doc().Find("div") 230 | sel2 := Doc().Find(".hero-unit") 231 | if i := sel.IndexOfSelection(sel2); i != 4 { 232 | t.Errorf("Expected index of 4, got %v.", i) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /bench/v0.1.0: -------------------------------------------------------------------------------- 1 | PASS 2 | BenchmarkFirst 20000000 92.9 ns/op 3 | BenchmarkLast 20000000 91.6 ns/op 4 | BenchmarkEq 20000000 90.6 ns/op 5 | BenchmarkSlice 20000000 86.7 ns/op 6 | BenchmarkGet 1000000000 2.14 ns/op 7 | BenchmarkIndex 500000 5308 ns/op 8 | --- BENCH: BenchmarkIndex 9 | bench_array_test.go:73: Index=3 10 | bench_array_test.go:73: Index=3 11 | bench_array_test.go:73: Index=3 12 | bench_array_test.go:73: Index=3 13 | BenchmarkIndexSelector 50000 54962 ns/op 14 | --- BENCH: BenchmarkIndexSelector 15 | bench_array_test.go:85: IndexSelector=4 16 | bench_array_test.go:85: IndexSelector=4 17 | bench_array_test.go:85: IndexSelector=4 18 | bench_array_test.go:85: IndexSelector=4 19 | BenchmarkIndexOfNode 100000000 11.4 ns/op 20 | --- BENCH: BenchmarkIndexOfNode 21 | bench_array_test.go:99: IndexOfNode=2 22 | bench_array_test.go:99: IndexOfNode=2 23 | bench_array_test.go:99: IndexOfNode=2 24 | bench_array_test.go:99: IndexOfNode=2 25 | bench_array_test.go:99: IndexOfNode=2 26 | BenchmarkIndexOfSelection 100000000 12.1 ns/op 27 | --- BENCH: BenchmarkIndexOfSelection 28 | bench_array_test.go:111: IndexOfSelection=2 29 | bench_array_test.go:111: IndexOfSelection=2 30 | bench_array_test.go:111: IndexOfSelection=2 31 | bench_array_test.go:111: IndexOfSelection=2 32 | bench_array_test.go:111: IndexOfSelection=2 33 | BenchmarkMetalReviewExample 5000 327144 ns/op 34 | --- BENCH: BenchmarkMetalReviewExample 35 | bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5). 36 | Review 1: Over Your Threshold - Facticity (6.0). 37 | Review 2: Nuclear Death Terror - Chaos Reigns (7.5). 38 | Review 3: Evoken - Atra Mors (9.5). 39 | 40 | bench_example_test.go:41: MetalReviewExample=10 41 | bench_example_test.go:40: Review 0: Midnight - Complete and Total Hell (8.5). 42 | Review 1: Over Your Threshold - Facticity (6.0). 43 | Review 2: Nuclear Death Terror - Chaos Reigns (7.5). 44 | Review 3: Evoken - Atra Mors (9.5). 45 | ... [output truncated] 46 | BenchmarkAdd 50000 52945 ns/op 47 | --- BENCH: BenchmarkAdd 48 | bench_expand_test.go:20: Add=43 49 | bench_expand_test.go:20: Add=43 50 | bench_expand_test.go:20: Add=43 51 | bench_expand_test.go:20: Add=43 52 | BenchmarkAddSelection 10000000 205 ns/op 53 | --- BENCH: BenchmarkAddSelection 54 | bench_expand_test.go:37: AddSelection=43 55 | bench_expand_test.go:37: AddSelection=43 56 | bench_expand_test.go:37: AddSelection=43 57 | bench_expand_test.go:37: AddSelection=43 58 | bench_expand_test.go:37: AddSelection=43 59 | BenchmarkAddNodes 10000000 203 ns/op 60 | --- BENCH: BenchmarkAddNodes 61 | bench_expand_test.go:55: AddNodes=43 62 | bench_expand_test.go:55: AddNodes=43 63 | bench_expand_test.go:55: AddNodes=43 64 | bench_expand_test.go:55: AddNodes=43 65 | bench_expand_test.go:55: AddNodes=43 66 | BenchmarkAndSelf 1000000 2639 ns/op 67 | --- BENCH: BenchmarkAndSelf 68 | bench_expand_test.go:71: AndSelf=44 69 | bench_expand_test.go:71: AndSelf=44 70 | bench_expand_test.go:71: AndSelf=44 71 | bench_expand_test.go:71: AndSelf=44 72 | BenchmarkFilter 50000 30182 ns/op 73 | --- BENCH: BenchmarkFilter 74 | bench_filter_test.go:20: Filter=13 75 | bench_filter_test.go:20: Filter=13 76 | bench_filter_test.go:20: Filter=13 77 | bench_filter_test.go:20: Filter=13 78 | BenchmarkNot 50000 34855 ns/op 79 | --- BENCH: BenchmarkNot 80 | bench_filter_test.go:36: Not=371 81 | bench_filter_test.go:36: Not=371 82 | bench_filter_test.go:36: Not=371 83 | bench_filter_test.go:36: Not=371 84 | BenchmarkFilterFunction 50000 66052 ns/op 85 | --- BENCH: BenchmarkFilterFunction 86 | bench_filter_test.go:55: FilterFunction=112 87 | bench_filter_test.go:55: FilterFunction=112 88 | bench_filter_test.go:55: FilterFunction=112 89 | bench_filter_test.go:55: FilterFunction=112 90 | BenchmarkNotFunction 50000 69721 ns/op 91 | --- BENCH: BenchmarkNotFunction 92 | bench_filter_test.go:74: NotFunction=261 93 | bench_filter_test.go:74: NotFunction=261 94 | bench_filter_test.go:74: NotFunction=261 95 | bench_filter_test.go:74: NotFunction=261 96 | BenchmarkFilterNodes 50000 66077 ns/op 97 | --- BENCH: BenchmarkFilterNodes 98 | bench_filter_test.go:92: FilterNodes=2 99 | bench_filter_test.go:92: FilterNodes=2 100 | bench_filter_test.go:92: FilterNodes=2 101 | bench_filter_test.go:92: FilterNodes=2 102 | BenchmarkNotNodes 20000 80021 ns/op 103 | --- BENCH: BenchmarkNotNodes 104 | bench_filter_test.go:110: NotNodes=360 105 | bench_filter_test.go:110: NotNodes=360 106 | bench_filter_test.go:110: NotNodes=360 107 | bench_filter_test.go:110: NotNodes=360 108 | BenchmarkFilterSelection 50000 66256 ns/op 109 | --- BENCH: BenchmarkFilterSelection 110 | bench_filter_test.go:127: FilterSelection=2 111 | bench_filter_test.go:127: FilterSelection=2 112 | bench_filter_test.go:127: FilterSelection=2 113 | bench_filter_test.go:127: FilterSelection=2 114 | BenchmarkNotSelection 20000 79568 ns/op 115 | --- BENCH: BenchmarkNotSelection 116 | bench_filter_test.go:144: NotSelection=360 117 | bench_filter_test.go:144: NotSelection=360 118 | bench_filter_test.go:144: NotSelection=360 119 | bench_filter_test.go:144: NotSelection=360 120 | BenchmarkHas 5000 569441 ns/op 121 | --- BENCH: BenchmarkHas 122 | bench_filter_test.go:160: Has=13 123 | bench_filter_test.go:160: Has=13 124 | bench_filter_test.go:160: Has=13 125 | BenchmarkHasNodes 10000 230585 ns/op 126 | --- BENCH: BenchmarkHasNodes 127 | bench_filter_test.go:178: HasNodes=15 128 | bench_filter_test.go:178: HasNodes=15 129 | bench_filter_test.go:178: HasNodes=15 130 | BenchmarkHasSelection 10000 231470 ns/op 131 | --- BENCH: BenchmarkHasSelection 132 | bench_filter_test.go:195: HasSelection=15 133 | bench_filter_test.go:195: HasSelection=15 134 | bench_filter_test.go:195: HasSelection=15 135 | BenchmarkEnd 500000000 4.65 ns/op 136 | --- BENCH: BenchmarkEnd 137 | bench_filter_test.go:211: End=373 138 | bench_filter_test.go:211: End=373 139 | bench_filter_test.go:211: End=373 140 | bench_filter_test.go:211: End=373 141 | bench_filter_test.go:211: End=373 142 | bench_filter_test.go:211: End=373 143 | BenchmarkEach 200000 9558 ns/op 144 | --- BENCH: BenchmarkEach 145 | bench_iteration_test.go:22: Each=59 146 | bench_iteration_test.go:22: Each=59 147 | bench_iteration_test.go:22: Each=59 148 | bench_iteration_test.go:22: Each=59 149 | BenchmarkMap 100000 16809 ns/op 150 | --- BENCH: BenchmarkMap 151 | bench_iteration_test.go:41: Map=59 152 | bench_iteration_test.go:41: Map=59 153 | bench_iteration_test.go:41: Map=59 154 | bench_iteration_test.go:41: Map=59 155 | BenchmarkAttr 50000000 37.5 ns/op 156 | --- BENCH: BenchmarkAttr 157 | bench_property_test.go:16: Attr=firstHeading 158 | bench_property_test.go:16: Attr=firstHeading 159 | bench_property_test.go:16: Attr=firstHeading 160 | bench_property_test.go:16: Attr=firstHeading 161 | bench_property_test.go:16: Attr=firstHeading 162 | BenchmarkText 100000 18583 ns/op 163 | BenchmarkLength 2000000000 0.80 ns/op 164 | --- BENCH: BenchmarkLength 165 | bench_property_test.go:37: Length=14 166 | bench_property_test.go:37: Length=14 167 | bench_property_test.go:37: Length=14 168 | bench_property_test.go:37: Length=14 169 | bench_property_test.go:37: Length=14 170 | bench_property_test.go:37: Length=14 171 | BenchmarkHtml 5000000 666 ns/op 172 | BenchmarkIs 50000 34328 ns/op 173 | --- BENCH: BenchmarkIs 174 | bench_query_test.go:16: Is=true 175 | bench_query_test.go:16: Is=true 176 | bench_query_test.go:16: Is=true 177 | bench_query_test.go:16: Is=true 178 | BenchmarkIsPositional 50000 32423 ns/op 179 | --- BENCH: BenchmarkIsPositional 180 | bench_query_test.go:28: IsPositional=true 181 | bench_query_test.go:28: IsPositional=true 182 | bench_query_test.go:28: IsPositional=true 183 | bench_query_test.go:28: IsPositional=true 184 | BenchmarkIsFunction 1000000 2707 ns/op 185 | --- BENCH: BenchmarkIsFunction 186 | bench_query_test.go:43: IsFunction=true 187 | bench_query_test.go:43: IsFunction=true 188 | bench_query_test.go:43: IsFunction=true 189 | bench_query_test.go:43: IsFunction=true 190 | BenchmarkIsSelection 50000 66976 ns/op 191 | --- BENCH: BenchmarkIsSelection 192 | bench_query_test.go:56: IsSelection=true 193 | bench_query_test.go:56: IsSelection=true 194 | bench_query_test.go:56: IsSelection=true 195 | bench_query_test.go:56: IsSelection=true 196 | BenchmarkIsNodes 50000 66740 ns/op 197 | --- BENCH: BenchmarkIsNodes 198 | bench_query_test.go:70: IsNodes=true 199 | bench_query_test.go:70: IsNodes=true 200 | bench_query_test.go:70: IsNodes=true 201 | bench_query_test.go:70: IsNodes=true 202 | BenchmarkHasClass 5000 701722 ns/op 203 | --- BENCH: BenchmarkHasClass 204 | bench_query_test.go:82: HasClass=true 205 | bench_query_test.go:82: HasClass=true 206 | bench_query_test.go:82: HasClass=true 207 | BenchmarkContains 100000000 11.9 ns/op 208 | --- BENCH: BenchmarkContains 209 | bench_query_test.go:96: Contains=true 210 | bench_query_test.go:96: Contains=true 211 | bench_query_test.go:96: Contains=true 212 | bench_query_test.go:96: Contains=true 213 | bench_query_test.go:96: Contains=true 214 | BenchmarkFind 50000 55444 ns/op 215 | --- BENCH: BenchmarkFind 216 | bench_traversal_test.go:18: Find=41 217 | bench_traversal_test.go:18: Find=41 218 | bench_traversal_test.go:18: Find=41 219 | bench_traversal_test.go:18: Find=41 220 | BenchmarkFindWithinSelection 10000 127984 ns/op 221 | --- BENCH: BenchmarkFindWithinSelection 222 | bench_traversal_test.go:34: FindWithinSelection=39 223 | bench_traversal_test.go:34: FindWithinSelection=39 224 | bench_traversal_test.go:34: FindWithinSelection=39 225 | BenchmarkFindSelection 5000 355944 ns/op 226 | --- BENCH: BenchmarkFindSelection 227 | bench_traversal_test.go:51: FindSelection=73 228 | bench_traversal_test.go:51: FindSelection=73 229 | bench_traversal_test.go:51: FindSelection=73 230 | BenchmarkFindNodes 5000 355596 ns/op 231 | --- BENCH: BenchmarkFindNodes 232 | bench_traversal_test.go:69: FindNodes=73 233 | bench_traversal_test.go:69: FindNodes=73 234 | bench_traversal_test.go:69: FindNodes=73 235 | BenchmarkContents 500000 5656 ns/op 236 | --- BENCH: BenchmarkContents 237 | bench_traversal_test.go:85: Contents=16 238 | bench_traversal_test.go:85: Contents=16 239 | bench_traversal_test.go:85: Contents=16 240 | bench_traversal_test.go:85: Contents=16 241 | BenchmarkContentsFiltered 200000 9007 ns/op 242 | --- BENCH: BenchmarkContentsFiltered 243 | bench_traversal_test.go:101: ContentsFiltered=1 244 | bench_traversal_test.go:101: ContentsFiltered=1 245 | bench_traversal_test.go:101: ContentsFiltered=1 246 | bench_traversal_test.go:101: ContentsFiltered=1 247 | BenchmarkChildren 1000000 1237 ns/op 248 | --- BENCH: BenchmarkChildren 249 | bench_traversal_test.go:117: Children=2 250 | bench_traversal_test.go:117: Children=2 251 | bench_traversal_test.go:117: Children=2 252 | bench_traversal_test.go:117: Children=2 253 | BenchmarkChildrenFiltered 500000 5613 ns/op 254 | --- BENCH: BenchmarkChildrenFiltered 255 | bench_traversal_test.go:133: ChildrenFiltered=2 256 | bench_traversal_test.go:133: ChildrenFiltered=2 257 | bench_traversal_test.go:133: ChildrenFiltered=2 258 | bench_traversal_test.go:133: ChildrenFiltered=2 259 | BenchmarkParent 50000 47026 ns/op 260 | --- BENCH: BenchmarkParent 261 | bench_traversal_test.go:149: Parent=55 262 | bench_traversal_test.go:149: Parent=55 263 | bench_traversal_test.go:149: Parent=55 264 | bench_traversal_test.go:149: Parent=55 265 | BenchmarkParentFiltered 50000 51438 ns/op 266 | --- BENCH: BenchmarkParentFiltered 267 | bench_traversal_test.go:165: ParentFiltered=4 268 | bench_traversal_test.go:165: ParentFiltered=4 269 | bench_traversal_test.go:165: ParentFiltered=4 270 | bench_traversal_test.go:165: ParentFiltered=4 271 | BenchmarkParents 20000 91820 ns/op 272 | --- BENCH: BenchmarkParents 273 | bench_traversal_test.go:181: Parents=73 274 | bench_traversal_test.go:181: Parents=73 275 | bench_traversal_test.go:181: Parents=73 276 | bench_traversal_test.go:181: Parents=73 277 | BenchmarkParentsFiltered 20000 95156 ns/op 278 | --- BENCH: BenchmarkParentsFiltered 279 | bench_traversal_test.go:197: ParentsFiltered=18 280 | bench_traversal_test.go:197: ParentsFiltered=18 281 | bench_traversal_test.go:197: ParentsFiltered=18 282 | bench_traversal_test.go:197: ParentsFiltered=18 283 | BenchmarkParentsUntil 10000 134383 ns/op 284 | --- BENCH: BenchmarkParentsUntil 285 | bench_traversal_test.go:213: ParentsUntil=52 286 | bench_traversal_test.go:213: ParentsUntil=52 287 | bench_traversal_test.go:213: ParentsUntil=52 288 | BenchmarkParentsUntilSelection 10000 235456 ns/op 289 | --- BENCH: BenchmarkParentsUntilSelection 290 | bench_traversal_test.go:230: ParentsUntilSelection=70 291 | bench_traversal_test.go:230: ParentsUntilSelection=70 292 | bench_traversal_test.go:230: ParentsUntilSelection=70 293 | BenchmarkParentsUntilNodes 10000 235936 ns/op 294 | --- BENCH: BenchmarkParentsUntilNodes 295 | bench_traversal_test.go:248: ParentsUntilNodes=70 296 | bench_traversal_test.go:248: ParentsUntilNodes=70 297 | bench_traversal_test.go:248: ParentsUntilNodes=70 298 | BenchmarkParentsFilteredUntil 50000 32451 ns/op 299 | --- BENCH: BenchmarkParentsFilteredUntil 300 | bench_traversal_test.go:264: ParentsFilteredUntil=2 301 | bench_traversal_test.go:264: ParentsFilteredUntil=2 302 | bench_traversal_test.go:264: ParentsFilteredUntil=2 303 | bench_traversal_test.go:264: ParentsFilteredUntil=2 304 | BenchmarkParentsFilteredUntilSelection 50000 30570 ns/op 305 | --- BENCH: BenchmarkParentsFilteredUntilSelection 306 | bench_traversal_test.go:281: ParentsFilteredUntilSelection=2 307 | bench_traversal_test.go:281: ParentsFilteredUntilSelection=2 308 | bench_traversal_test.go:281: ParentsFilteredUntilSelection=2 309 | bench_traversal_test.go:281: ParentsFilteredUntilSelection=2 310 | BenchmarkParentsFilteredUntilNodes 50000 30729 ns/op 311 | --- BENCH: BenchmarkParentsFilteredUntilNodes 312 | bench_traversal_test.go:299: ParentsFilteredUntilNodes=2 313 | bench_traversal_test.go:299: ParentsFilteredUntilNodes=2 314 | bench_traversal_test.go:299: ParentsFilteredUntilNodes=2 315 | bench_traversal_test.go:299: ParentsFilteredUntilNodes=2 316 | BenchmarkSiblings 10000 106704 ns/op 317 | --- BENCH: BenchmarkSiblings 318 | bench_traversal_test.go:315: Siblings=293 319 | bench_traversal_test.go:315: Siblings=293 320 | bench_traversal_test.go:315: Siblings=293 321 | BenchmarkSiblingsFiltered 10000 115592 ns/op 322 | --- BENCH: BenchmarkSiblingsFiltered 323 | bench_traversal_test.go:331: SiblingsFiltered=46 324 | bench_traversal_test.go:331: SiblingsFiltered=46 325 | bench_traversal_test.go:331: SiblingsFiltered=46 326 | BenchmarkNext 50000 54449 ns/op 327 | --- BENCH: BenchmarkNext 328 | bench_traversal_test.go:347: Next=49 329 | bench_traversal_test.go:347: Next=49 330 | bench_traversal_test.go:347: Next=49 331 | bench_traversal_test.go:347: Next=49 332 | BenchmarkNextFiltered 50000 58503 ns/op 333 | --- BENCH: BenchmarkNextFiltered 334 | bench_traversal_test.go:363: NextFiltered=6 335 | bench_traversal_test.go:363: NextFiltered=6 336 | bench_traversal_test.go:363: NextFiltered=6 337 | bench_traversal_test.go:363: NextFiltered=6 338 | BenchmarkNextAll 20000 77698 ns/op 339 | --- BENCH: BenchmarkNextAll 340 | bench_traversal_test.go:379: NextAll=234 341 | bench_traversal_test.go:379: NextAll=234 342 | bench_traversal_test.go:379: NextAll=234 343 | bench_traversal_test.go:379: NextAll=234 344 | BenchmarkNextAllFiltered 20000 85034 ns/op 345 | --- BENCH: BenchmarkNextAllFiltered 346 | bench_traversal_test.go:395: NextAllFiltered=33 347 | bench_traversal_test.go:395: NextAllFiltered=33 348 | bench_traversal_test.go:395: NextAllFiltered=33 349 | bench_traversal_test.go:395: NextAllFiltered=33 350 | BenchmarkPrev 50000 56458 ns/op 351 | --- BENCH: BenchmarkPrev 352 | bench_traversal_test.go:411: Prev=49 353 | bench_traversal_test.go:411: Prev=49 354 | bench_traversal_test.go:411: Prev=49 355 | bench_traversal_test.go:411: Prev=49 356 | BenchmarkPrevFiltered 50000 60163 ns/op 357 | --- BENCH: BenchmarkPrevFiltered 358 | bench_traversal_test.go:429: PrevFiltered=7 359 | bench_traversal_test.go:429: PrevFiltered=7 360 | bench_traversal_test.go:429: PrevFiltered=7 361 | bench_traversal_test.go:429: PrevFiltered=7 362 | BenchmarkPrevAll 50000 47679 ns/op 363 | --- BENCH: BenchmarkPrevAll 364 | bench_traversal_test.go:445: PrevAll=78 365 | bench_traversal_test.go:445: PrevAll=78 366 | bench_traversal_test.go:445: PrevAll=78 367 | bench_traversal_test.go:445: PrevAll=78 368 | BenchmarkPrevAllFiltered 50000 51563 ns/op 369 | --- BENCH: BenchmarkPrevAllFiltered 370 | bench_traversal_test.go:461: PrevAllFiltered=6 371 | bench_traversal_test.go:461: PrevAllFiltered=6 372 | bench_traversal_test.go:461: PrevAllFiltered=6 373 | bench_traversal_test.go:461: PrevAllFiltered=6 374 | BenchmarkNextUntil 10000 213998 ns/op 375 | --- BENCH: BenchmarkNextUntil 376 | bench_traversal_test.go:477: NextUntil=84 377 | bench_traversal_test.go:477: NextUntil=84 378 | bench_traversal_test.go:477: NextUntil=84 379 | BenchmarkNextUntilSelection 10000 140720 ns/op 380 | --- BENCH: BenchmarkNextUntilSelection 381 | bench_traversal_test.go:494: NextUntilSelection=42 382 | bench_traversal_test.go:494: NextUntilSelection=42 383 | bench_traversal_test.go:494: NextUntilSelection=42 384 | BenchmarkNextUntilNodes 20000 90702 ns/op 385 | --- BENCH: BenchmarkNextUntilNodes 386 | bench_traversal_test.go:512: NextUntilNodes=12 387 | bench_traversal_test.go:512: NextUntilNodes=12 388 | bench_traversal_test.go:512: NextUntilNodes=12 389 | bench_traversal_test.go:512: NextUntilNodes=12 390 | BenchmarkPrevUntil 5000 456039 ns/op 391 | --- BENCH: BenchmarkPrevUntil 392 | bench_traversal_test.go:528: PrevUntil=238 393 | bench_traversal_test.go:528: PrevUntil=238 394 | bench_traversal_test.go:528: PrevUntil=238 395 | BenchmarkPrevUntilSelection 10000 167944 ns/op 396 | --- BENCH: BenchmarkPrevUntilSelection 397 | bench_traversal_test.go:545: PrevUntilSelection=49 398 | bench_traversal_test.go:545: PrevUntilSelection=49 399 | bench_traversal_test.go:545: PrevUntilSelection=49 400 | BenchmarkPrevUntilNodes 20000 82059 ns/op 401 | --- BENCH: BenchmarkPrevUntilNodes 402 | bench_traversal_test.go:563: PrevUntilNodes=11 403 | bench_traversal_test.go:563: PrevUntilNodes=11 404 | bench_traversal_test.go:563: PrevUntilNodes=11 405 | bench_traversal_test.go:563: PrevUntilNodes=11 406 | BenchmarkNextFilteredUntil 10000 150883 ns/op 407 | --- BENCH: BenchmarkNextFilteredUntil 408 | bench_traversal_test.go:579: NextFilteredUntil=22 409 | bench_traversal_test.go:579: NextFilteredUntil=22 410 | bench_traversal_test.go:579: NextFilteredUntil=22 411 | BenchmarkNextFilteredUntilSelection 10000 146578 ns/op 412 | --- BENCH: BenchmarkNextFilteredUntilSelection 413 | bench_traversal_test.go:596: NextFilteredUntilSelection=22 414 | bench_traversal_test.go:596: NextFilteredUntilSelection=22 415 | bench_traversal_test.go:596: NextFilteredUntilSelection=22 416 | BenchmarkNextFilteredUntilNodes 10000 148284 ns/op 417 | --- BENCH: BenchmarkNextFilteredUntilNodes 418 | bench_traversal_test.go:614: NextFilteredUntilNodes=22 419 | bench_traversal_test.go:614: NextFilteredUntilNodes=22 420 | bench_traversal_test.go:614: NextFilteredUntilNodes=22 421 | BenchmarkPrevFilteredUntil 10000 154303 ns/op 422 | --- BENCH: BenchmarkPrevFilteredUntil 423 | bench_traversal_test.go:630: PrevFilteredUntil=20 424 | bench_traversal_test.go:630: PrevFilteredUntil=20 425 | bench_traversal_test.go:630: PrevFilteredUntil=20 426 | BenchmarkPrevFilteredUntilSelection 10000 149062 ns/op 427 | --- BENCH: BenchmarkPrevFilteredUntilSelection 428 | bench_traversal_test.go:647: PrevFilteredUntilSelection=20 429 | bench_traversal_test.go:647: PrevFilteredUntilSelection=20 430 | bench_traversal_test.go:647: PrevFilteredUntilSelection=20 431 | BenchmarkPrevFilteredUntilNodes 10000 150584 ns/op 432 | --- BENCH: BenchmarkPrevFilteredUntilNodes 433 | bench_traversal_test.go:665: PrevFilteredUntilNodes=20 434 | bench_traversal_test.go:665: PrevFilteredUntilNodes=20 435 | bench_traversal_test.go:665: PrevFilteredUntilNodes=20 436 | ok github.com/PuerkitoBio/goquery 188.326s 437 | -------------------------------------------------------------------------------- /bench/v1.0.0-go1.7: -------------------------------------------------------------------------------- 1 | BenchmarkFirst-4 30000000 50.7 ns/op 48 B/op 1 allocs/op 2 | BenchmarkLast-4 30000000 50.9 ns/op 48 B/op 1 allocs/op 3 | BenchmarkEq-4 30000000 55.7 ns/op 48 B/op 1 allocs/op 4 | BenchmarkSlice-4 500000000 3.45 ns/op 0 B/op 0 allocs/op 5 | BenchmarkGet-4 2000000000 1.68 ns/op 0 B/op 0 allocs/op 6 | BenchmarkIndex-4 3000000 541 ns/op 248 B/op 10 allocs/op 7 | BenchmarkIndexSelector-4 200000 10749 ns/op 2464 B/op 17 allocs/op 8 | BenchmarkIndexOfNode-4 200000000 6.47 ns/op 0 B/op 0 allocs/op 9 | BenchmarkIndexOfSelection-4 200000000 7.27 ns/op 0 B/op 0 allocs/op 10 | BenchmarkMetalReviewExample-4 10000 138426 ns/op 12240 B/op 319 allocs/op 11 | BenchmarkAdd-4 200000 10192 ns/op 208 B/op 9 allocs/op 12 | BenchmarkAddSelection-4 10000000 158 ns/op 48 B/op 1 allocs/op 13 | BenchmarkAddNodes-4 10000000 156 ns/op 48 B/op 1 allocs/op 14 | BenchmarkAndSelf-4 1000000 1588 ns/op 1008 B/op 5 allocs/op 15 | BenchmarkFilter-4 100000 20427 ns/op 360 B/op 8 allocs/op 16 | BenchmarkNot-4 100000 23508 ns/op 136 B/op 5 allocs/op 17 | BenchmarkFilterFunction-4 50000 34178 ns/op 22976 B/op 755 allocs/op 18 | BenchmarkNotFunction-4 50000 38173 ns/op 29120 B/op 757 allocs/op 19 | BenchmarkFilterNodes-4 50000 34001 ns/op 20960 B/op 749 allocs/op 20 | BenchmarkNotNodes-4 30000 40344 ns/op 29120 B/op 757 allocs/op 21 | BenchmarkFilterSelection-4 50000 33308 ns/op 20960 B/op 749 allocs/op 22 | BenchmarkNotSelection-4 30000 40748 ns/op 29120 B/op 757 allocs/op 23 | BenchmarkHas-4 5000 263346 ns/op 1816 B/op 48 allocs/op 24 | BenchmarkHasNodes-4 10000 160840 ns/op 21184 B/op 752 allocs/op 25 | BenchmarkHasSelection-4 10000 165410 ns/op 21184 B/op 752 allocs/op 26 | BenchmarkEnd-4 2000000000 1.01 ns/op 0 B/op 0 allocs/op 27 | BenchmarkEach-4 300000 4664 ns/op 3304 B/op 118 allocs/op 28 | BenchmarkMap-4 200000 8286 ns/op 5572 B/op 184 allocs/op 29 | BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op 30 | BenchmarkAttr-4 100000000 21.6 ns/op 0 B/op 0 allocs/op 31 | BenchmarkText-4 200000 8909 ns/op 7536 B/op 110 allocs/op 32 | BenchmarkLength-4 2000000000 0.34 ns/op 0 B/op 0 allocs/op 33 | BenchmarkHtml-4 3000000 422 ns/op 120 B/op 2 allocs/op 34 | BenchmarkIs-4 100000 22615 ns/op 88 B/op 4 allocs/op 35 | BenchmarkIsPositional-4 50000 26655 ns/op 1112 B/op 10 allocs/op 36 | BenchmarkIsFunction-4 1000000 1208 ns/op 784 B/op 28 allocs/op 37 | BenchmarkIsSelection-4 50000 33497 ns/op 20960 B/op 749 allocs/op 38 | BenchmarkIsNodes-4 50000 33572 ns/op 20960 B/op 749 allocs/op 39 | BenchmarkHasClass-4 10000 232802 ns/op 14944 B/op 976 allocs/op 40 | BenchmarkContains-4 200000000 7.33 ns/op 0 B/op 0 allocs/op 41 | BenchmarkFind-4 200000 10715 ns/op 2464 B/op 17 allocs/op 42 | BenchmarkFindWithinSelection-4 50000 35878 ns/op 2176 B/op 78 allocs/op 43 | BenchmarkFindSelection-4 10000 194356 ns/op 2672 B/op 82 allocs/op 44 | BenchmarkFindNodes-4 10000 195510 ns/op 2672 B/op 82 allocs/op 45 | BenchmarkContents-4 1000000 2252 ns/op 864 B/op 34 allocs/op 46 | BenchmarkContentsFiltered-4 500000 3015 ns/op 1016 B/op 39 allocs/op 47 | BenchmarkChildren-4 5000000 364 ns/op 152 B/op 7 allocs/op 48 | BenchmarkChildrenFiltered-4 1000000 2212 ns/op 352 B/op 15 allocs/op 49 | BenchmarkParent-4 50000 24643 ns/op 4048 B/op 381 allocs/op 50 | BenchmarkParentFiltered-4 50000 25967 ns/op 4248 B/op 388 allocs/op 51 | BenchmarkParents-4 30000 50000 ns/op 27776 B/op 830 allocs/op 52 | BenchmarkParentsFiltered-4 30000 53107 ns/op 28360 B/op 838 allocs/op 53 | BenchmarkParentsUntil-4 100000 22423 ns/op 10352 B/op 353 allocs/op 54 | BenchmarkParentsUntilSelection-4 20000 86925 ns/op 51144 B/op 1516 allocs/op 55 | BenchmarkParentsUntilNodes-4 20000 87597 ns/op 51144 B/op 1516 allocs/op 56 | BenchmarkParentsFilteredUntil-4 300000 5568 ns/op 2232 B/op 86 allocs/op 57 | BenchmarkParentsFilteredUntilSelection-4 200000 10966 ns/op 5440 B/op 190 allocs/op 58 | BenchmarkParentsFilteredUntilNodes-4 200000 10919 ns/op 5440 B/op 190 allocs/op 59 | BenchmarkSiblings-4 30000 46018 ns/op 15400 B/op 204 allocs/op 60 | BenchmarkSiblingsFiltered-4 30000 50566 ns/op 16496 B/op 213 allocs/op 61 | BenchmarkNext-4 200000 7921 ns/op 3216 B/op 112 allocs/op 62 | BenchmarkNextFiltered-4 200000 8804 ns/op 3416 B/op 118 allocs/op 63 | BenchmarkNextAll-4 50000 31098 ns/op 9912 B/op 138 allocs/op 64 | BenchmarkNextAllFiltered-4 50000 34677 ns/op 11008 B/op 147 allocs/op 65 | BenchmarkPrev-4 200000 7920 ns/op 3216 B/op 112 allocs/op 66 | BenchmarkPrevFiltered-4 200000 8913 ns/op 3416 B/op 118 allocs/op 67 | BenchmarkPrevAll-4 200000 10845 ns/op 4376 B/op 113 allocs/op 68 | BenchmarkPrevAllFiltered-4 100000 12030 ns/op 4576 B/op 119 allocs/op 69 | BenchmarkNextUntil-4 100000 19193 ns/op 5760 B/op 260 allocs/op 70 | BenchmarkNextUntilSelection-4 50000 34829 ns/op 18480 B/op 542 allocs/op 71 | BenchmarkNextUntilNodes-4 100000 14459 ns/op 7944 B/op 248 allocs/op 72 | BenchmarkPrevUntil-4 20000 66296 ns/op 12856 B/op 448 allocs/op 73 | BenchmarkPrevUntilSelection-4 30000 45037 ns/op 23432 B/op 689 allocs/op 74 | BenchmarkPrevUntilNodes-4 200000 11525 ns/op 6152 B/op 203 allocs/op 75 | BenchmarkNextFilteredUntil-4 100000 12940 ns/op 4512 B/op 173 allocs/op 76 | BenchmarkNextFilteredUntilSelection-4 50000 38924 ns/op 19160 B/op 567 allocs/op 77 | BenchmarkNextFilteredUntilNodes-4 50000 38528 ns/op 19160 B/op 567 allocs/op 78 | BenchmarkPrevFilteredUntil-4 100000 12980 ns/op 4664 B/op 175 allocs/op 79 | BenchmarkPrevFilteredUntilSelection-4 50000 39671 ns/op 19936 B/op 587 allocs/op 80 | BenchmarkPrevFilteredUntilNodes-4 50000 39484 ns/op 19936 B/op 587 allocs/op 81 | BenchmarkClosest-4 500000 3310 ns/op 160 B/op 8 allocs/op 82 | BenchmarkClosestSelection-4 5000000 361 ns/op 96 B/op 6 allocs/op 83 | BenchmarkClosestNodes-4 5000000 359 ns/op 96 B/op 6 allocs/op 84 | PASS 85 | ok github.com/PuerkitoBio/goquery 163.718s 86 | -------------------------------------------------------------------------------- /bench/v1.0.1a-go1.7: -------------------------------------------------------------------------------- 1 | BenchmarkFirst-4 30000000 50.9 ns/op 48 B/op 1 allocs/op 2 | BenchmarkLast-4 30000000 50.0 ns/op 48 B/op 1 allocs/op 3 | BenchmarkEq-4 30000000 50.5 ns/op 48 B/op 1 allocs/op 4 | BenchmarkSlice-4 500000000 3.53 ns/op 0 B/op 0 allocs/op 5 | BenchmarkGet-4 2000000000 1.66 ns/op 0 B/op 0 allocs/op 6 | BenchmarkIndex-4 2000000 832 ns/op 248 B/op 10 allocs/op 7 | BenchmarkIndexSelector-4 100000 16073 ns/op 3839 B/op 21 allocs/op 8 | BenchmarkIndexOfNode-4 200000000 6.38 ns/op 0 B/op 0 allocs/op 9 | BenchmarkIndexOfSelection-4 200000000 7.14 ns/op 0 B/op 0 allocs/op 10 | BenchmarkMetalReviewExample-4 10000 140737 ns/op 12418 B/op 320 allocs/op 11 | BenchmarkAdd-4 100000 13162 ns/op 974 B/op 10 allocs/op 12 | BenchmarkAddSelection-4 500000 3160 ns/op 814 B/op 2 allocs/op 13 | BenchmarkAddNodes-4 500000 3159 ns/op 814 B/op 2 allocs/op 14 | BenchmarkAndSelf-4 200000 7423 ns/op 2404 B/op 9 allocs/op 15 | BenchmarkFilter-4 100000 19671 ns/op 360 B/op 8 allocs/op 16 | BenchmarkNot-4 100000 22577 ns/op 136 B/op 5 allocs/op 17 | BenchmarkFilterFunction-4 50000 33960 ns/op 22976 B/op 755 allocs/op 18 | BenchmarkNotFunction-4 50000 37909 ns/op 29120 B/op 757 allocs/op 19 | BenchmarkFilterNodes-4 50000 34196 ns/op 20960 B/op 749 allocs/op 20 | BenchmarkNotNodes-4 30000 40446 ns/op 29120 B/op 757 allocs/op 21 | BenchmarkFilterSelection-4 50000 33091 ns/op 20960 B/op 749 allocs/op 22 | BenchmarkNotSelection-4 30000 40609 ns/op 29120 B/op 757 allocs/op 23 | BenchmarkHas-4 5000 262936 ns/op 2371 B/op 50 allocs/op 24 | BenchmarkHasNodes-4 10000 148631 ns/op 21184 B/op 752 allocs/op 25 | BenchmarkHasSelection-4 10000 153117 ns/op 21184 B/op 752 allocs/op 26 | BenchmarkEnd-4 2000000000 1.02 ns/op 0 B/op 0 allocs/op 27 | BenchmarkEach-4 300000 4653 ns/op 3304 B/op 118 allocs/op 28 | BenchmarkMap-4 200000 8257 ns/op 5572 B/op 184 allocs/op 29 | BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op 30 | BenchmarkAttr-4 100000000 22.0 ns/op 0 B/op 0 allocs/op 31 | BenchmarkText-4 200000 8913 ns/op 7536 B/op 110 allocs/op 32 | BenchmarkLength-4 2000000000 0.35 ns/op 0 B/op 0 allocs/op 33 | BenchmarkHtml-4 5000000 398 ns/op 120 B/op 2 allocs/op 34 | BenchmarkIs-4 100000 22392 ns/op 88 B/op 4 allocs/op 35 | BenchmarkIsPositional-4 50000 26259 ns/op 1112 B/op 10 allocs/op 36 | BenchmarkIsFunction-4 1000000 1212 ns/op 784 B/op 28 allocs/op 37 | BenchmarkIsSelection-4 50000 33222 ns/op 20960 B/op 749 allocs/op 38 | BenchmarkIsNodes-4 50000 33408 ns/op 20960 B/op 749 allocs/op 39 | BenchmarkHasClass-4 10000 233208 ns/op 14944 B/op 976 allocs/op 40 | BenchmarkContains-4 200000000 7.57 ns/op 0 B/op 0 allocs/op 41 | BenchmarkFind-4 100000 16121 ns/op 3839 B/op 21 allocs/op 42 | BenchmarkFindWithinSelection-4 20000 68019 ns/op 11521 B/op 97 allocs/op 43 | BenchmarkFindSelection-4 5000 387582 ns/op 59787 B/op 176 allocs/op 44 | BenchmarkFindNodes-4 5000 389246 ns/op 59797 B/op 176 allocs/op 45 | BenchmarkContents-4 200000 11475 ns/op 2878 B/op 42 allocs/op 46 | BenchmarkContentsFiltered-4 200000 11222 ns/op 2498 B/op 46 allocs/op 47 | BenchmarkChildren-4 2000000 650 ns/op 152 B/op 7 allocs/op 48 | BenchmarkChildrenFiltered-4 500000 2568 ns/op 352 B/op 15 allocs/op 49 | BenchmarkParent-4 2000 702513 ns/op 194478 B/op 828 allocs/op 50 | BenchmarkParentFiltered-4 2000 690778 ns/op 194658 B/op 835 allocs/op 51 | BenchmarkParents-4 10000 124855 ns/op 49869 B/op 868 allocs/op 52 | BenchmarkParentsFiltered-4 10000 128535 ns/op 50456 B/op 876 allocs/op 53 | BenchmarkParentsUntil-4 20000 72982 ns/op 23802 B/op 388 allocs/op 54 | BenchmarkParentsUntilSelection-4 10000 156099 ns/op 72453 B/op 1549 allocs/op 55 | BenchmarkParentsUntilNodes-4 10000 156610 ns/op 72455 B/op 1549 allocs/op 56 | BenchmarkParentsFilteredUntil-4 100000 15549 ns/op 4068 B/op 94 allocs/op 57 | BenchmarkParentsFilteredUntilSelection-4 100000 20564 ns/op 7276 B/op 198 allocs/op 58 | BenchmarkParentsFilteredUntilNodes-4 100000 20635 ns/op 7276 B/op 198 allocs/op 59 | BenchmarkSiblings-4 3000 565114 ns/op 205910 B/op 336 allocs/op 60 | BenchmarkSiblingsFiltered-4 3000 580264 ns/op 206993 B/op 345 allocs/op 61 | BenchmarkNext-4 20000 93177 ns/op 26810 B/op 169 allocs/op 62 | BenchmarkNextFiltered-4 20000 94171 ns/op 27013 B/op 175 allocs/op 63 | BenchmarkNextAll-4 5000 270320 ns/op 89289 B/op 237 allocs/op 64 | BenchmarkNextAllFiltered-4 5000 275283 ns/op 90375 B/op 246 allocs/op 65 | BenchmarkPrev-4 20000 92777 ns/op 26810 B/op 169 allocs/op 66 | BenchmarkPrevFiltered-4 20000 95577 ns/op 27007 B/op 175 allocs/op 67 | BenchmarkPrevAll-4 20000 86339 ns/op 27515 B/op 151 allocs/op 68 | BenchmarkPrevAllFiltered-4 20000 87759 ns/op 27715 B/op 157 allocs/op 69 | BenchmarkNextUntil-4 10000 163930 ns/op 48541 B/op 330 allocs/op 70 | BenchmarkNextUntilSelection-4 30000 56382 ns/op 23880 B/op 556 allocs/op 71 | BenchmarkNextUntilNodes-4 100000 18883 ns/op 8703 B/op 252 allocs/op 72 | BenchmarkPrevUntil-4 3000 484668 ns/op 145402 B/op 611 allocs/op 73 | BenchmarkPrevUntilSelection-4 20000 72125 ns/op 28865 B/op 705 allocs/op 74 | BenchmarkPrevUntilNodes-4 100000 14722 ns/op 6510 B/op 205 allocs/op 75 | BenchmarkNextFilteredUntil-4 50000 39006 ns/op 10990 B/op 192 allocs/op 76 | BenchmarkNextFilteredUntilSelection-4 20000 66048 ns/op 25641 B/op 586 allocs/op 77 | BenchmarkNextFilteredUntilNodes-4 20000 65314 ns/op 25640 B/op 586 allocs/op 78 | BenchmarkPrevFilteredUntil-4 50000 33312 ns/op 9709 B/op 189 allocs/op 79 | BenchmarkPrevFilteredUntilSelection-4 20000 64197 ns/op 24981 B/op 601 allocs/op 80 | BenchmarkPrevFilteredUntilNodes-4 20000 64505 ns/op 24982 B/op 601 allocs/op 81 | BenchmarkClosest-4 500000 4065 ns/op 160 B/op 8 allocs/op 82 | BenchmarkClosestSelection-4 2000000 756 ns/op 96 B/op 6 allocs/op 83 | BenchmarkClosestNodes-4 2000000 753 ns/op 96 B/op 6 allocs/op 84 | PASS 85 | ok github.com/PuerkitoBio/goquery 162.053s 86 | -------------------------------------------------------------------------------- /bench/v1.0.1b-go1.7: -------------------------------------------------------------------------------- 1 | BenchmarkFirst-4 30000000 51.8 ns/op 48 B/op 1 allocs/op 2 | BenchmarkLast-4 30000000 50.1 ns/op 48 B/op 1 allocs/op 3 | BenchmarkEq-4 30000000 51.4 ns/op 48 B/op 1 allocs/op 4 | BenchmarkSlice-4 500000000 3.52 ns/op 0 B/op 0 allocs/op 5 | BenchmarkGet-4 2000000000 1.65 ns/op 0 B/op 0 allocs/op 6 | BenchmarkIndex-4 2000000 787 ns/op 248 B/op 10 allocs/op 7 | BenchmarkIndexSelector-4 100000 16952 ns/op 3839 B/op 21 allocs/op 8 | BenchmarkIndexOfNode-4 200000000 6.42 ns/op 0 B/op 0 allocs/op 9 | BenchmarkIndexOfSelection-4 200000000 7.12 ns/op 0 B/op 0 allocs/op 10 | BenchmarkMetalReviewExample-4 10000 141994 ns/op 12418 B/op 320 allocs/op 11 | BenchmarkAdd-4 200000 10367 ns/op 208 B/op 9 allocs/op 12 | BenchmarkAddSelection-4 10000000 152 ns/op 48 B/op 1 allocs/op 13 | BenchmarkAddNodes-4 10000000 147 ns/op 48 B/op 1 allocs/op 14 | BenchmarkAndSelf-4 1000000 1647 ns/op 1008 B/op 5 allocs/op 15 | BenchmarkFilter-4 100000 19522 ns/op 360 B/op 8 allocs/op 16 | BenchmarkNot-4 100000 22546 ns/op 136 B/op 5 allocs/op 17 | BenchmarkFilterFunction-4 50000 35087 ns/op 22976 B/op 755 allocs/op 18 | BenchmarkNotFunction-4 50000 39123 ns/op 29120 B/op 757 allocs/op 19 | BenchmarkFilterNodes-4 50000 34890 ns/op 20960 B/op 749 allocs/op 20 | BenchmarkNotNodes-4 30000 41145 ns/op 29120 B/op 757 allocs/op 21 | BenchmarkFilterSelection-4 50000 33735 ns/op 20960 B/op 749 allocs/op 22 | BenchmarkNotSelection-4 30000 41334 ns/op 29120 B/op 757 allocs/op 23 | BenchmarkHas-4 5000 264058 ns/op 2370 B/op 50 allocs/op 24 | BenchmarkHasNodes-4 10000 151718 ns/op 21184 B/op 752 allocs/op 25 | BenchmarkHasSelection-4 10000 156955 ns/op 21184 B/op 752 allocs/op 26 | BenchmarkEnd-4 2000000000 1.01 ns/op 0 B/op 0 allocs/op 27 | BenchmarkEach-4 300000 4660 ns/op 3304 B/op 118 allocs/op 28 | BenchmarkMap-4 200000 8404 ns/op 5572 B/op 184 allocs/op 29 | BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op 30 | BenchmarkAttr-4 100000000 21.6 ns/op 0 B/op 0 allocs/op 31 | BenchmarkText-4 200000 8911 ns/op 7536 B/op 110 allocs/op 32 | BenchmarkLength-4 2000000000 0.34 ns/op 0 B/op 0 allocs/op 33 | BenchmarkHtml-4 3000000 405 ns/op 120 B/op 2 allocs/op 34 | BenchmarkIs-4 100000 22228 ns/op 88 B/op 4 allocs/op 35 | BenchmarkIsPositional-4 50000 26469 ns/op 1112 B/op 10 allocs/op 36 | BenchmarkIsFunction-4 1000000 1240 ns/op 784 B/op 28 allocs/op 37 | BenchmarkIsSelection-4 50000 33709 ns/op 20960 B/op 749 allocs/op 38 | BenchmarkIsNodes-4 50000 33711 ns/op 20960 B/op 749 allocs/op 39 | BenchmarkHasClass-4 10000 236005 ns/op 14944 B/op 976 allocs/op 40 | BenchmarkContains-4 200000000 7.47 ns/op 0 B/op 0 allocs/op 41 | BenchmarkFind-4 100000 16075 ns/op 3839 B/op 21 allocs/op 42 | BenchmarkFindWithinSelection-4 30000 41418 ns/op 3539 B/op 82 allocs/op 43 | BenchmarkFindSelection-4 10000 209490 ns/op 5616 B/op 89 allocs/op 44 | BenchmarkFindNodes-4 10000 208206 ns/op 5614 B/op 89 allocs/op 45 | BenchmarkContents-4 300000 4751 ns/op 1420 B/op 36 allocs/op 46 | BenchmarkContentsFiltered-4 300000 5454 ns/op 1570 B/op 41 allocs/op 47 | BenchmarkChildren-4 3000000 527 ns/op 152 B/op 7 allocs/op 48 | BenchmarkChildrenFiltered-4 1000000 2484 ns/op 352 B/op 15 allocs/op 49 | BenchmarkParent-4 50000 34724 ns/op 6940 B/op 387 allocs/op 50 | BenchmarkParentFiltered-4 50000 35596 ns/op 7141 B/op 394 allocs/op 51 | BenchmarkParents-4 20000 62094 ns/op 30720 B/op 837 allocs/op 52 | BenchmarkParentsFiltered-4 20000 63223 ns/op 31304 B/op 845 allocs/op 53 | BenchmarkParentsUntil-4 50000 30391 ns/op 11828 B/op 358 allocs/op 54 | BenchmarkParentsUntilSelection-4 20000 99962 ns/op 54075 B/op 1523 allocs/op 55 | BenchmarkParentsUntilNodes-4 20000 98763 ns/op 54073 B/op 1523 allocs/op 56 | BenchmarkParentsFilteredUntil-4 200000 7982 ns/op 2787 B/op 88 allocs/op 57 | BenchmarkParentsFilteredUntilSelection-4 100000 13618 ns/op 5995 B/op 192 allocs/op 58 | BenchmarkParentsFilteredUntilNodes-4 100000 13639 ns/op 5994 B/op 192 allocs/op 59 | BenchmarkSiblings-4 20000 75287 ns/op 28453 B/op 225 allocs/op 60 | BenchmarkSiblingsFiltered-4 20000 80139 ns/op 29543 B/op 234 allocs/op 61 | BenchmarkNext-4 100000 14270 ns/op 4659 B/op 117 allocs/op 62 | BenchmarkNextFiltered-4 100000 15352 ns/op 4860 B/op 123 allocs/op 63 | BenchmarkNextAll-4 20000 60811 ns/op 22771 B/op 157 allocs/op 64 | BenchmarkNextAllFiltered-4 20000 69079 ns/op 23871 B/op 166 allocs/op 65 | BenchmarkPrev-4 100000 14417 ns/op 4659 B/op 117 allocs/op 66 | BenchmarkPrevFiltered-4 100000 15443 ns/op 4859 B/op 123 allocs/op 67 | BenchmarkPrevAll-4 100000 22008 ns/op 7346 B/op 120 allocs/op 68 | BenchmarkPrevAllFiltered-4 100000 23212 ns/op 7544 B/op 126 allocs/op 69 | BenchmarkNextUntil-4 50000 30589 ns/op 8767 B/op 267 allocs/op 70 | BenchmarkNextUntilSelection-4 30000 40875 ns/op 19862 B/op 546 allocs/op 71 | BenchmarkNextUntilNodes-4 100000 15987 ns/op 8134 B/op 249 allocs/op 72 | BenchmarkPrevUntil-4 20000 98799 ns/op 25727 B/op 467 allocs/op 73 | BenchmarkPrevUntilSelection-4 30000 51874 ns/op 24875 B/op 694 allocs/op 74 | BenchmarkPrevUntilNodes-4 100000 12901 ns/op 6334 B/op 204 allocs/op 75 | BenchmarkNextFilteredUntil-4 100000 19869 ns/op 5909 B/op 177 allocs/op 76 | BenchmarkNextFilteredUntilSelection-4 30000 45412 ns/op 20557 B/op 571 allocs/op 77 | BenchmarkNextFilteredUntilNodes-4 30000 45363 ns/op 20557 B/op 571 allocs/op 78 | BenchmarkPrevFilteredUntil-4 100000 19357 ns/op 6033 B/op 179 allocs/op 79 | BenchmarkPrevFilteredUntilSelection-4 30000 46396 ns/op 21305 B/op 591 allocs/op 80 | BenchmarkPrevFilteredUntilNodes-4 30000 46133 ns/op 21305 B/op 591 allocs/op 81 | BenchmarkClosest-4 500000 3448 ns/op 160 B/op 8 allocs/op 82 | BenchmarkClosestSelection-4 3000000 528 ns/op 96 B/op 6 allocs/op 83 | BenchmarkClosestNodes-4 3000000 523 ns/op 96 B/op 6 allocs/op 84 | PASS 85 | ok github.com/PuerkitoBio/goquery 162.012s 86 | -------------------------------------------------------------------------------- /bench/v1.0.1c-go1.7: -------------------------------------------------------------------------------- 1 | BenchmarkFirst-4 30000000 51.7 ns/op 48 B/op 1 allocs/op 2 | BenchmarkLast-4 30000000 51.9 ns/op 48 B/op 1 allocs/op 3 | BenchmarkEq-4 30000000 50.0 ns/op 48 B/op 1 allocs/op 4 | BenchmarkSlice-4 500000000 3.47 ns/op 0 B/op 0 allocs/op 5 | BenchmarkGet-4 2000000000 1.68 ns/op 0 B/op 0 allocs/op 6 | BenchmarkIndex-4 2000000 804 ns/op 248 B/op 10 allocs/op 7 | BenchmarkIndexSelector-4 100000 16285 ns/op 3839 B/op 21 allocs/op 8 | BenchmarkIndexOfNode-4 200000000 6.50 ns/op 0 B/op 0 allocs/op 9 | BenchmarkIndexOfSelection-4 200000000 7.02 ns/op 0 B/op 0 allocs/op 10 | BenchmarkMetalReviewExample-4 10000 143160 ns/op 12417 B/op 320 allocs/op 11 | BenchmarkAdd-4 200000 10326 ns/op 208 B/op 9 allocs/op 12 | BenchmarkAddSelection-4 10000000 155 ns/op 48 B/op 1 allocs/op 13 | BenchmarkAddNodes-4 10000000 156 ns/op 48 B/op 1 allocs/op 14 | BenchmarkAddNodesBig-4 20000 94439 ns/op 21847 B/op 37 allocs/op 15 | BenchmarkAndSelf-4 1000000 1791 ns/op 1008 B/op 5 allocs/op 16 | BenchmarkFilter-4 100000 19470 ns/op 360 B/op 8 allocs/op 17 | BenchmarkNot-4 100000 22500 ns/op 136 B/op 5 allocs/op 18 | BenchmarkFilterFunction-4 50000 34578 ns/op 22976 B/op 755 allocs/op 19 | BenchmarkNotFunction-4 50000 38703 ns/op 29120 B/op 757 allocs/op 20 | BenchmarkFilterNodes-4 50000 34486 ns/op 20960 B/op 749 allocs/op 21 | BenchmarkNotNodes-4 30000 41094 ns/op 29120 B/op 757 allocs/op 22 | BenchmarkFilterSelection-4 50000 33623 ns/op 20960 B/op 749 allocs/op 23 | BenchmarkNotSelection-4 30000 41483 ns/op 29120 B/op 757 allocs/op 24 | BenchmarkHas-4 5000 266628 ns/op 2371 B/op 50 allocs/op 25 | BenchmarkHasNodes-4 10000 152617 ns/op 21184 B/op 752 allocs/op 26 | BenchmarkHasSelection-4 10000 156682 ns/op 21184 B/op 752 allocs/op 27 | BenchmarkEnd-4 2000000000 1.00 ns/op 0 B/op 0 allocs/op 28 | BenchmarkEach-4 300000 4712 ns/op 3304 B/op 118 allocs/op 29 | BenchmarkMap-4 200000 8434 ns/op 5572 B/op 184 allocs/op 30 | BenchmarkEachWithBreak-4 2000000 819 ns/op 560 B/op 20 allocs/op 31 | BenchmarkAttr-4 100000000 21.7 ns/op 0 B/op 0 allocs/op 32 | BenchmarkText-4 200000 9376 ns/op 7536 B/op 110 allocs/op 33 | BenchmarkLength-4 2000000000 0.35 ns/op 0 B/op 0 allocs/op 34 | BenchmarkHtml-4 5000000 401 ns/op 120 B/op 2 allocs/op 35 | BenchmarkIs-4 100000 22214 ns/op 88 B/op 4 allocs/op 36 | BenchmarkIsPositional-4 50000 26559 ns/op 1112 B/op 10 allocs/op 37 | BenchmarkIsFunction-4 1000000 1228 ns/op 784 B/op 28 allocs/op 38 | BenchmarkIsSelection-4 50000 33471 ns/op 20960 B/op 749 allocs/op 39 | BenchmarkIsNodes-4 50000 34461 ns/op 20960 B/op 749 allocs/op 40 | BenchmarkHasClass-4 10000 232429 ns/op 14944 B/op 976 allocs/op 41 | BenchmarkContains-4 200000000 7.62 ns/op 0 B/op 0 allocs/op 42 | BenchmarkFind-4 100000 16114 ns/op 3839 B/op 21 allocs/op 43 | BenchmarkFindWithinSelection-4 30000 42520 ns/op 3540 B/op 82 allocs/op 44 | BenchmarkFindSelection-4 10000 209801 ns/op 5615 B/op 89 allocs/op 45 | BenchmarkFindNodes-4 10000 209082 ns/op 5614 B/op 89 allocs/op 46 | BenchmarkContents-4 300000 4836 ns/op 1420 B/op 36 allocs/op 47 | BenchmarkContentsFiltered-4 200000 5495 ns/op 1570 B/op 41 allocs/op 48 | BenchmarkChildren-4 3000000 527 ns/op 152 B/op 7 allocs/op 49 | BenchmarkChildrenFiltered-4 500000 2499 ns/op 352 B/op 15 allocs/op 50 | BenchmarkParent-4 50000 34072 ns/op 6942 B/op 387 allocs/op 51 | BenchmarkParentFiltered-4 50000 36077 ns/op 7141 B/op 394 allocs/op 52 | BenchmarkParents-4 20000 64118 ns/op 30719 B/op 837 allocs/op 53 | BenchmarkParentsFiltered-4 20000 63432 ns/op 31303 B/op 845 allocs/op 54 | BenchmarkParentsUntil-4 50000 29589 ns/op 11829 B/op 358 allocs/op 55 | BenchmarkParentsUntilSelection-4 10000 101033 ns/op 54076 B/op 1523 allocs/op 56 | BenchmarkParentsUntilNodes-4 10000 100584 ns/op 54076 B/op 1523 allocs/op 57 | BenchmarkParentsFilteredUntil-4 200000 8061 ns/op 2787 B/op 88 allocs/op 58 | BenchmarkParentsFilteredUntilSelection-4 100000 13848 ns/op 5995 B/op 192 allocs/op 59 | BenchmarkParentsFilteredUntilNodes-4 100000 13766 ns/op 5995 B/op 192 allocs/op 60 | BenchmarkSiblings-4 20000 75135 ns/op 28453 B/op 225 allocs/op 61 | BenchmarkSiblingsFiltered-4 20000 80532 ns/op 29544 B/op 234 allocs/op 62 | BenchmarkNext-4 100000 14200 ns/op 4660 B/op 117 allocs/op 63 | BenchmarkNextFiltered-4 100000 15284 ns/op 4859 B/op 123 allocs/op 64 | BenchmarkNextAll-4 20000 60889 ns/op 22774 B/op 157 allocs/op 65 | BenchmarkNextAllFiltered-4 20000 65125 ns/op 23869 B/op 166 allocs/op 66 | BenchmarkPrev-4 100000 14448 ns/op 4659 B/op 117 allocs/op 67 | BenchmarkPrevFiltered-4 100000 15444 ns/op 4859 B/op 123 allocs/op 68 | BenchmarkPrevAll-4 100000 22019 ns/op 7344 B/op 120 allocs/op 69 | BenchmarkPrevAllFiltered-4 100000 23307 ns/op 7545 B/op 126 allocs/op 70 | BenchmarkNextUntil-4 50000 30287 ns/op 8766 B/op 267 allocs/op 71 | BenchmarkNextUntilSelection-4 30000 41476 ns/op 19862 B/op 546 allocs/op 72 | BenchmarkNextUntilNodes-4 100000 16106 ns/op 8133 B/op 249 allocs/op 73 | BenchmarkPrevUntil-4 20000 98951 ns/op 25728 B/op 467 allocs/op 74 | BenchmarkPrevUntilSelection-4 30000 52390 ns/op 24875 B/op 694 allocs/op 75 | BenchmarkPrevUntilNodes-4 100000 12986 ns/op 6334 B/op 204 allocs/op 76 | BenchmarkNextFilteredUntil-4 100000 19365 ns/op 5908 B/op 177 allocs/op 77 | BenchmarkNextFilteredUntilSelection-4 30000 45334 ns/op 20555 B/op 571 allocs/op 78 | BenchmarkNextFilteredUntilNodes-4 30000 45292 ns/op 20556 B/op 571 allocs/op 79 | BenchmarkPrevFilteredUntil-4 100000 19412 ns/op 6032 B/op 179 allocs/op 80 | BenchmarkPrevFilteredUntilSelection-4 30000 46286 ns/op 21304 B/op 591 allocs/op 81 | BenchmarkPrevFilteredUntilNodes-4 30000 46554 ns/op 21305 B/op 591 allocs/op 82 | BenchmarkClosest-4 500000 3480 ns/op 160 B/op 8 allocs/op 83 | BenchmarkClosestSelection-4 2000000 722 ns/op 96 B/op 6 allocs/op 84 | BenchmarkClosestNodes-4 2000000 719 ns/op 96 B/op 6 allocs/op 85 | PASS 86 | ok github.com/PuerkitoBio/goquery 160.565s 87 | -------------------------------------------------------------------------------- /bench_array_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkFirst(b *testing.B) { 8 | b.StopTimer() 9 | sel := DocB().Find("dd") 10 | b.StartTimer() 11 | for i := 0; i < b.N; i++ { 12 | sel.First() 13 | } 14 | } 15 | 16 | func BenchmarkLast(b *testing.B) { 17 | b.StopTimer() 18 | sel := DocB().Find("dd") 19 | b.StartTimer() 20 | for i := 0; i < b.N; i++ { 21 | sel.Last() 22 | } 23 | } 24 | 25 | func BenchmarkEq(b *testing.B) { 26 | b.StopTimer() 27 | sel := DocB().Find("dd") 28 | j := 0 29 | b.StartTimer() 30 | for i := 0; i < b.N; i++ { 31 | sel.Eq(j) 32 | if j++; j >= sel.Length() { 33 | j = 0 34 | } 35 | } 36 | } 37 | 38 | func BenchmarkSlice(b *testing.B) { 39 | b.StopTimer() 40 | sel := DocB().Find("dd") 41 | j := 0 42 | b.StartTimer() 43 | for i := 0; i < b.N; i++ { 44 | sel.Slice(j, j+4) 45 | if j++; j >= (sel.Length() - 4) { 46 | j = 0 47 | } 48 | } 49 | } 50 | 51 | func BenchmarkGet(b *testing.B) { 52 | b.StopTimer() 53 | sel := DocB().Find("dd") 54 | j := 0 55 | b.StartTimer() 56 | for i := 0; i < b.N; i++ { 57 | sel.Get(j) 58 | if j++; j >= sel.Length() { 59 | j = 0 60 | } 61 | } 62 | } 63 | 64 | func BenchmarkIndex(b *testing.B) { 65 | var j int 66 | 67 | b.StopTimer() 68 | sel := DocB().Find("#Main") 69 | b.StartTimer() 70 | for i := 0; i < b.N; i++ { 71 | j = sel.Index() 72 | } 73 | if j != 3 { 74 | b.Fatalf("want 3, got %d", j) 75 | } 76 | } 77 | 78 | func BenchmarkIndexSelector(b *testing.B) { 79 | var j int 80 | 81 | b.StopTimer() 82 | sel := DocB().Find("#manual-nav dl dd:nth-child(1)") 83 | b.StartTimer() 84 | for i := 0; i < b.N; i++ { 85 | j = sel.IndexSelector("dd") 86 | } 87 | if j != 4 { 88 | b.Fatalf("want 4, got %d", j) 89 | } 90 | } 91 | 92 | func BenchmarkIndexOfNode(b *testing.B) { 93 | var j int 94 | 95 | b.StopTimer() 96 | sel := DocB().Find("span a") 97 | sel2 := DocB().Find("span a:nth-child(3)") 98 | n := sel2.Get(0) 99 | b.StartTimer() 100 | for i := 0; i < b.N; i++ { 101 | j = sel.IndexOfNode(n) 102 | } 103 | if j != 2 { 104 | b.Fatalf("want 2, got %d", j) 105 | } 106 | } 107 | 108 | func BenchmarkIndexOfSelection(b *testing.B) { 109 | var j int 110 | b.StopTimer() 111 | sel := DocB().Find("span a") 112 | sel2 := DocB().Find("span a:nth-child(3)") 113 | b.StartTimer() 114 | for i := 0; i < b.N; i++ { 115 | j = sel.IndexOfSelection(sel2) 116 | } 117 | if j != 2 { 118 | b.Fatalf("want 2, got %d", j) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /bench_example_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | func BenchmarkMetalReviewExample(b *testing.B) { 11 | var n int 12 | var builder strings.Builder 13 | 14 | b.StopTimer() 15 | doc := loadDoc("metalreview.html") 16 | b.StartTimer() 17 | for i := 0; i < b.N; i++ { 18 | doc.Find(".slider-row:nth-child(1) .slider-item").Each(func(i int, s *Selection) { 19 | var band, title string 20 | var score float64 21 | var e error 22 | 23 | n++ 24 | // For each item found, get the band, title and score, and print it 25 | band = s.Find("strong").Text() 26 | title = s.Find("em").Text() 27 | if score, e = strconv.ParseFloat(s.Find(".score").Text(), 64); e != nil { 28 | // Not a valid float, ignore score 29 | if n <= 4 { 30 | builder.WriteString(fmt.Sprintf("Review %d: %s - %s.\n", i, band, title)) 31 | } 32 | } else { 33 | // Print all, including score 34 | if n <= 4 { 35 | builder.WriteString(fmt.Sprintf("Review %d: %s - %s (%2.1f).\n", i, band, title, score)) 36 | } 37 | } 38 | }) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /bench_expand_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkAdd(b *testing.B) { 8 | var n int 9 | 10 | b.StopTimer() 11 | sel := DocB().Find("dd") 12 | b.StartTimer() 13 | for i := 0; i < b.N; i++ { 14 | if n == 0 { 15 | n = sel.Add("h2[title]").Length() 16 | } else { 17 | sel.Add("h2[title]") 18 | } 19 | } 20 | if n != 43 { 21 | b.Fatalf("want 43, got %d", n) 22 | } 23 | } 24 | 25 | func BenchmarkAddSelection(b *testing.B) { 26 | var n int 27 | 28 | b.StopTimer() 29 | sel := DocB().Find("dd") 30 | sel2 := DocB().Find("h2[title]") 31 | b.StartTimer() 32 | for i := 0; i < b.N; i++ { 33 | if n == 0 { 34 | n = sel.AddSelection(sel2).Length() 35 | } else { 36 | sel.AddSelection(sel2) 37 | } 38 | } 39 | if n != 43 { 40 | b.Fatalf("want 43, got %d", n) 41 | } 42 | } 43 | 44 | func BenchmarkAddNodes(b *testing.B) { 45 | var n int 46 | 47 | b.StopTimer() 48 | sel := DocB().Find("dd") 49 | sel2 := DocB().Find("h2[title]") 50 | nodes := sel2.Nodes 51 | b.StartTimer() 52 | for i := 0; i < b.N; i++ { 53 | if n == 0 { 54 | n = sel.AddNodes(nodes...).Length() 55 | } else { 56 | sel.AddNodes(nodes...) 57 | } 58 | } 59 | if n != 43 { 60 | b.Fatalf("want 43, got %d", n) 61 | } 62 | } 63 | 64 | func BenchmarkAddNodesBig(b *testing.B) { 65 | var n int 66 | 67 | doc := DocW() 68 | sel := doc.Find("li") 69 | // make nodes > 1000 70 | nodes := sel.Nodes 71 | nodes = append(nodes, nodes...) 72 | nodes = append(nodes, nodes...) 73 | sel = doc.Find("xyz") 74 | b.ResetTimer() 75 | 76 | for i := 0; i < b.N; i++ { 77 | if n == 0 { 78 | n = sel.AddNodes(nodes...).Length() 79 | } else { 80 | sel.AddNodes(nodes...) 81 | } 82 | } 83 | if n != 373 { 84 | b.Fatalf("want 373, got %d", n) 85 | } 86 | } 87 | 88 | func BenchmarkAndSelf(b *testing.B) { 89 | var n int 90 | 91 | b.StopTimer() 92 | sel := DocB().Find("dd").Parent() 93 | b.StartTimer() 94 | for i := 0; i < b.N; i++ { 95 | if n == 0 { 96 | n = sel.AndSelf().Length() 97 | } else { 98 | sel.AndSelf() 99 | } 100 | } 101 | if n != 44 { 102 | b.Fatalf("want 44, got %d", n) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /bench_filter_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkFilter(b *testing.B) { 8 | var n int 9 | 10 | b.StopTimer() 11 | sel := DocW().Find("li") 12 | b.StartTimer() 13 | for i := 0; i < b.N; i++ { 14 | if n == 0 { 15 | n = sel.Filter(".toclevel-1").Length() 16 | } else { 17 | sel.Filter(".toclevel-1") 18 | } 19 | } 20 | if n != 13 { 21 | b.Fatalf("want 13, got %d", n) 22 | } 23 | } 24 | 25 | func BenchmarkNot(b *testing.B) { 26 | var n int 27 | 28 | b.StopTimer() 29 | sel := DocW().Find("li") 30 | b.StartTimer() 31 | for i := 0; i < b.N; i++ { 32 | if n == 0 { 33 | n = sel.Not(".toclevel-2").Length() 34 | } else { 35 | sel.Filter(".toclevel-2") 36 | } 37 | } 38 | if n != 371 { 39 | b.Fatalf("want 371, got %d", n) 40 | } 41 | } 42 | 43 | func BenchmarkFilterFunction(b *testing.B) { 44 | var n int 45 | 46 | b.StopTimer() 47 | sel := DocW().Find("li") 48 | f := func(i int, s *Selection) bool { 49 | return len(s.Get(0).Attr) > 0 50 | } 51 | b.StartTimer() 52 | for i := 0; i < b.N; i++ { 53 | if n == 0 { 54 | n = sel.FilterFunction(f).Length() 55 | } else { 56 | sel.FilterFunction(f) 57 | } 58 | } 59 | if n != 112 { 60 | b.Fatalf("want 112, got %d", n) 61 | } 62 | } 63 | 64 | func BenchmarkNotFunction(b *testing.B) { 65 | var n int 66 | 67 | b.StopTimer() 68 | sel := DocW().Find("li") 69 | f := func(i int, s *Selection) bool { 70 | return len(s.Get(0).Attr) > 0 71 | } 72 | b.StartTimer() 73 | for i := 0; i < b.N; i++ { 74 | if n == 0 { 75 | n = sel.NotFunction(f).Length() 76 | } else { 77 | sel.NotFunction(f) 78 | } 79 | } 80 | if n != 261 { 81 | b.Fatalf("want 261, got %d", n) 82 | } 83 | } 84 | 85 | func BenchmarkFilterNodes(b *testing.B) { 86 | var n int 87 | 88 | b.StopTimer() 89 | sel := DocW().Find("li") 90 | sel2 := DocW().Find(".toclevel-2") 91 | nodes := sel2.Nodes 92 | b.StartTimer() 93 | for i := 0; i < b.N; i++ { 94 | if n == 0 { 95 | n = sel.FilterNodes(nodes...).Length() 96 | } else { 97 | sel.FilterNodes(nodes...) 98 | } 99 | } 100 | if n != 2 { 101 | b.Fatalf("want 2, got %d", n) 102 | } 103 | } 104 | 105 | func BenchmarkNotNodes(b *testing.B) { 106 | var n int 107 | 108 | b.StopTimer() 109 | sel := DocW().Find("li") 110 | sel2 := DocW().Find(".toclevel-1") 111 | nodes := sel2.Nodes 112 | b.StartTimer() 113 | for i := 0; i < b.N; i++ { 114 | if n == 0 { 115 | n = sel.NotNodes(nodes...).Length() 116 | } else { 117 | sel.NotNodes(nodes...) 118 | } 119 | } 120 | if n != 360 { 121 | b.Fatalf("want 360, got %d", n) 122 | } 123 | } 124 | 125 | func BenchmarkFilterSelection(b *testing.B) { 126 | var n int 127 | 128 | b.StopTimer() 129 | sel := DocW().Find("li") 130 | sel2 := DocW().Find(".toclevel-2") 131 | b.StartTimer() 132 | for i := 0; i < b.N; i++ { 133 | if n == 0 { 134 | n = sel.FilterSelection(sel2).Length() 135 | } else { 136 | sel.FilterSelection(sel2) 137 | } 138 | } 139 | if n != 2 { 140 | b.Fatalf("want 2, got %d", n) 141 | } 142 | } 143 | 144 | func BenchmarkNotSelection(b *testing.B) { 145 | var n int 146 | 147 | b.StopTimer() 148 | sel := DocW().Find("li") 149 | sel2 := DocW().Find(".toclevel-1") 150 | b.StartTimer() 151 | for i := 0; i < b.N; i++ { 152 | if n == 0 { 153 | n = sel.NotSelection(sel2).Length() 154 | } else { 155 | sel.NotSelection(sel2) 156 | } 157 | } 158 | if n != 360 { 159 | b.Fatalf("want 360, got %d", n) 160 | } 161 | } 162 | 163 | func BenchmarkHas(b *testing.B) { 164 | var n int 165 | 166 | b.StopTimer() 167 | sel := DocW().Find("h2") 168 | b.StartTimer() 169 | for i := 0; i < b.N; i++ { 170 | if n == 0 { 171 | n = sel.Has(".editsection").Length() 172 | } else { 173 | sel.Has(".editsection") 174 | } 175 | } 176 | if n != 13 { 177 | b.Fatalf("want 13, got %d", n) 178 | } 179 | } 180 | 181 | func BenchmarkHasNodes(b *testing.B) { 182 | var n int 183 | 184 | b.StopTimer() 185 | sel := DocW().Find("li") 186 | sel2 := DocW().Find(".tocnumber") 187 | nodes := sel2.Nodes 188 | b.StartTimer() 189 | for i := 0; i < b.N; i++ { 190 | if n == 0 { 191 | n = sel.HasNodes(nodes...).Length() 192 | } else { 193 | sel.HasNodes(nodes...) 194 | } 195 | } 196 | if n != 15 { 197 | b.Fatalf("want 15, got %d", n) 198 | } 199 | } 200 | 201 | func BenchmarkHasSelection(b *testing.B) { 202 | var n int 203 | 204 | b.StopTimer() 205 | sel := DocW().Find("li") 206 | sel2 := DocW().Find(".tocnumber") 207 | b.StartTimer() 208 | for i := 0; i < b.N; i++ { 209 | if n == 0 { 210 | n = sel.HasSelection(sel2).Length() 211 | } else { 212 | sel.HasSelection(sel2) 213 | } 214 | } 215 | if n != 15 { 216 | b.Fatalf("want 15, got %d", n) 217 | } 218 | } 219 | 220 | func BenchmarkEnd(b *testing.B) { 221 | var n int 222 | 223 | b.StopTimer() 224 | sel := DocW().Find("li").Has(".tocnumber") 225 | b.StartTimer() 226 | for i := 0; i < b.N; i++ { 227 | if n == 0 { 228 | n = sel.End().Length() 229 | } else { 230 | sel.End() 231 | } 232 | } 233 | if n != 373 { 234 | b.Fatalf("want 373, got %d", n) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /bench_iteration_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "strconv" 5 | "testing" 6 | ) 7 | 8 | func BenchmarkEach(b *testing.B) { 9 | var tmp, n int 10 | 11 | b.StopTimer() 12 | sel := DocW().Find("td") 13 | f := func(i int, s *Selection) { 14 | tmp++ 15 | } 16 | b.StartTimer() 17 | for i := 0; i < b.N; i++ { 18 | sel.Each(f) 19 | if n == 0 { 20 | n = tmp 21 | } 22 | } 23 | if n != 59 { 24 | b.Fatalf("want 59, got %d", n) 25 | } 26 | } 27 | 28 | func BenchmarkEachIter(b *testing.B) { 29 | var tmp, n int 30 | 31 | b.StopTimer() 32 | sel := DocW().Find("td") 33 | b.StartTimer() 34 | for i := 0; i < b.N; i++ { 35 | for range sel.EachIter() { 36 | tmp++ 37 | } 38 | if n == 0 { 39 | n = tmp 40 | } 41 | } 42 | if n != 59 { 43 | b.Fatalf("want 59, got %d", n) 44 | } 45 | } 46 | 47 | func BenchmarkEachIterWithBreak(b *testing.B) { 48 | var tmp, n int 49 | 50 | b.StopTimer() 51 | sel := DocW().Find("td") 52 | b.StartTimer() 53 | for i := 0; i < b.N; i++ { 54 | tmp = 0 55 | for range sel.EachIter() { 56 | tmp++ 57 | if tmp >= 10 { 58 | break 59 | } 60 | } 61 | if n == 0 { 62 | n = tmp 63 | } 64 | } 65 | if n != 10 { 66 | b.Fatalf("want 10, got %d", n) 67 | } 68 | } 69 | 70 | func BenchmarkMap(b *testing.B) { 71 | var tmp, n int 72 | 73 | b.StopTimer() 74 | sel := DocW().Find("td") 75 | f := func(i int, s *Selection) string { 76 | tmp++ 77 | return strconv.Itoa(tmp) 78 | } 79 | b.StartTimer() 80 | for i := 0; i < b.N; i++ { 81 | sel.Map(f) 82 | if n == 0 { 83 | n = tmp 84 | } 85 | } 86 | if n != 59 { 87 | b.Fatalf("want 59, got %d", n) 88 | } 89 | } 90 | 91 | func BenchmarkEachWithBreak(b *testing.B) { 92 | var tmp, n int 93 | 94 | b.StopTimer() 95 | sel := DocW().Find("td") 96 | f := func(i int, s *Selection) bool { 97 | tmp++ 98 | return tmp < 10 99 | } 100 | b.StartTimer() 101 | for i := 0; i < b.N; i++ { 102 | tmp = 0 103 | sel.EachWithBreak(f) 104 | if n == 0 { 105 | n = tmp 106 | } 107 | } 108 | if n != 10 { 109 | b.Fatalf("want 10, got %d", n) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /bench_property_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkAttr(b *testing.B) { 8 | var s string 9 | 10 | b.StopTimer() 11 | sel := DocW().Find("h1") 12 | b.StartTimer() 13 | for i := 0; i < b.N; i++ { 14 | s, _ = sel.Attr("id") 15 | } 16 | if s != "firstHeading" { 17 | b.Fatalf("want firstHeading, got %q", s) 18 | } 19 | } 20 | 21 | func BenchmarkText(b *testing.B) { 22 | b.StopTimer() 23 | sel := DocW().Find("h2") 24 | b.StartTimer() 25 | for i := 0; i < b.N; i++ { 26 | sel.Text() 27 | } 28 | } 29 | 30 | func BenchmarkLength(b *testing.B) { 31 | var n int 32 | 33 | b.StopTimer() 34 | sel := DocW().Find("h2") 35 | b.StartTimer() 36 | for i := 0; i < b.N; i++ { 37 | n = sel.Length() 38 | } 39 | if n != 14 { 40 | b.Fatalf("want 14, got %d", n) 41 | } 42 | } 43 | 44 | func BenchmarkHtml(b *testing.B) { 45 | b.StopTimer() 46 | sel := DocW().Find("h2") 47 | b.StartTimer() 48 | for i := 0; i < b.N; i++ { 49 | _, _ = sel.Html() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /bench_query_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func BenchmarkIs(b *testing.B) { 8 | var y bool 9 | 10 | b.StopTimer() 11 | sel := DocW().Find("li") 12 | b.StartTimer() 13 | for i := 0; i < b.N; i++ { 14 | y = sel.Is(".toclevel-2") 15 | } 16 | if !y { 17 | b.Fatal("want true") 18 | } 19 | } 20 | 21 | func BenchmarkIsPositional(b *testing.B) { 22 | var y bool 23 | 24 | b.StopTimer() 25 | sel := DocW().Find("li") 26 | b.StartTimer() 27 | for i := 0; i < b.N; i++ { 28 | y = sel.Is("li:nth-child(2)") 29 | } 30 | if !y { 31 | b.Fatal("want true") 32 | } 33 | } 34 | 35 | func BenchmarkIsFunction(b *testing.B) { 36 | var y bool 37 | 38 | b.StopTimer() 39 | sel := DocW().Find(".toclevel-1") 40 | f := func(i int, s *Selection) bool { 41 | return i == 8 42 | } 43 | b.StartTimer() 44 | for i := 0; i < b.N; i++ { 45 | y = sel.IsFunction(f) 46 | } 47 | if !y { 48 | b.Fatal("want true") 49 | } 50 | } 51 | 52 | func BenchmarkIsSelection(b *testing.B) { 53 | var y bool 54 | 55 | b.StopTimer() 56 | sel := DocW().Find("li") 57 | sel2 := DocW().Find(".toclevel-2") 58 | b.StartTimer() 59 | for i := 0; i < b.N; i++ { 60 | y = sel.IsSelection(sel2) 61 | } 62 | if !y { 63 | b.Fatal("want true") 64 | } 65 | } 66 | 67 | func BenchmarkIsNodes(b *testing.B) { 68 | var y bool 69 | 70 | b.StopTimer() 71 | sel := DocW().Find("li") 72 | sel2 := DocW().Find(".toclevel-2") 73 | nodes := sel2.Nodes 74 | b.StartTimer() 75 | for i := 0; i < b.N; i++ { 76 | y = sel.IsNodes(nodes...) 77 | } 78 | if !y { 79 | b.Fatal("want true") 80 | } 81 | } 82 | 83 | func BenchmarkHasClass(b *testing.B) { 84 | var y bool 85 | 86 | b.StopTimer() 87 | sel := DocW().Find("span") 88 | b.StartTimer() 89 | for i := 0; i < b.N; i++ { 90 | y = sel.HasClass("official") 91 | } 92 | if !y { 93 | b.Fatal("want true") 94 | } 95 | } 96 | 97 | func BenchmarkContains(b *testing.B) { 98 | var y bool 99 | 100 | b.StopTimer() 101 | sel := DocW().Find("span.url") 102 | sel2 := DocW().Find("a[rel=\"nofollow\"]") 103 | node := sel2.Nodes[0] 104 | b.StartTimer() 105 | for i := 0; i < b.N; i++ { 106 | y = sel.Contains(node) 107 | } 108 | if !y { 109 | b.Fatal("want true") 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /bench_traversal_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/andybalholm/cascadia" 7 | ) 8 | 9 | func BenchmarkFind(b *testing.B) { 10 | var n int 11 | 12 | for i := 0; i < b.N; i++ { 13 | if n == 0 { 14 | n = DocB().Find("dd").Length() 15 | 16 | } else { 17 | DocB().Find("dd") 18 | } 19 | } 20 | if n != 41 { 21 | b.Fatalf("want 41, got %d", n) 22 | } 23 | } 24 | 25 | func BenchmarkFindWithinSelection(b *testing.B) { 26 | var n int 27 | 28 | b.StopTimer() 29 | sel := DocW().Find("ul") 30 | b.StartTimer() 31 | for i := 0; i < b.N; i++ { 32 | if n == 0 { 33 | n = sel.Find("a[class]").Length() 34 | } else { 35 | sel.Find("a[class]") 36 | } 37 | } 38 | if n != 39 { 39 | b.Fatalf("want 39, got %d", n) 40 | } 41 | } 42 | 43 | func BenchmarkFindSelection(b *testing.B) { 44 | var n int 45 | 46 | b.StopTimer() 47 | sel := DocW().Find("ul") 48 | sel2 := DocW().Find("span") 49 | b.StartTimer() 50 | for i := 0; i < b.N; i++ { 51 | if n == 0 { 52 | n = sel.FindSelection(sel2).Length() 53 | } else { 54 | sel.FindSelection(sel2) 55 | } 56 | } 57 | if n != 73 { 58 | b.Fatalf("want 73, got %d", n) 59 | } 60 | } 61 | 62 | func BenchmarkFindNodes(b *testing.B) { 63 | var n int 64 | 65 | b.StopTimer() 66 | sel := DocW().Find("ul") 67 | sel2 := DocW().Find("span") 68 | nodes := sel2.Nodes 69 | b.StartTimer() 70 | for i := 0; i < b.N; i++ { 71 | if n == 0 { 72 | n = sel.FindNodes(nodes...).Length() 73 | } else { 74 | sel.FindNodes(nodes...) 75 | } 76 | } 77 | if n != 73 { 78 | b.Fatalf("want 73, got %d", n) 79 | } 80 | } 81 | 82 | func BenchmarkContents(b *testing.B) { 83 | var n int 84 | 85 | b.StopTimer() 86 | sel := DocW().Find(".toclevel-1") 87 | b.StartTimer() 88 | for i := 0; i < b.N; i++ { 89 | if n == 0 { 90 | n = sel.Contents().Length() 91 | } else { 92 | sel.Contents() 93 | } 94 | } 95 | if n != 16 { 96 | b.Fatalf("want 16, got %d", n) 97 | } 98 | } 99 | 100 | func BenchmarkContentsFiltered(b *testing.B) { 101 | var n int 102 | 103 | b.StopTimer() 104 | sel := DocW().Find(".toclevel-1") 105 | b.StartTimer() 106 | for i := 0; i < b.N; i++ { 107 | if n == 0 { 108 | n = sel.ContentsFiltered("a[href=\"#Examples\"]").Length() 109 | } else { 110 | sel.ContentsFiltered("a[href=\"#Examples\"]") 111 | } 112 | } 113 | if n != 1 { 114 | b.Fatalf("want 1, got %d", n) 115 | } 116 | } 117 | 118 | func BenchmarkChildren(b *testing.B) { 119 | var n int 120 | 121 | b.StopTimer() 122 | sel := DocW().Find(".toclevel-2") 123 | b.StartTimer() 124 | for i := 0; i < b.N; i++ { 125 | if n == 0 { 126 | n = sel.Children().Length() 127 | } else { 128 | sel.Children() 129 | } 130 | } 131 | if n != 2 { 132 | b.Fatalf("want 2, got %d", n) 133 | } 134 | } 135 | 136 | func BenchmarkChildrenFiltered(b *testing.B) { 137 | var n int 138 | 139 | b.StopTimer() 140 | sel := DocW().Find("h3") 141 | b.StartTimer() 142 | for i := 0; i < b.N; i++ { 143 | if n == 0 { 144 | n = sel.ChildrenFiltered(".editsection").Length() 145 | } else { 146 | sel.ChildrenFiltered(".editsection") 147 | } 148 | } 149 | if n != 2 { 150 | b.Fatalf("want 2, got %d", n) 151 | } 152 | } 153 | 154 | func BenchmarkParent(b *testing.B) { 155 | var n int 156 | 157 | b.StopTimer() 158 | sel := DocW().Find("li") 159 | b.StartTimer() 160 | for i := 0; i < b.N; i++ { 161 | if n == 0 { 162 | n = sel.Parent().Length() 163 | } else { 164 | sel.Parent() 165 | } 166 | } 167 | if n != 55 { 168 | b.Fatalf("want 55, got %d", n) 169 | } 170 | } 171 | 172 | func BenchmarkParentFiltered(b *testing.B) { 173 | var n int 174 | 175 | b.StopTimer() 176 | sel := DocW().Find("li") 177 | b.StartTimer() 178 | for i := 0; i < b.N; i++ { 179 | if n == 0 { 180 | n = sel.ParentFiltered("ul[id]").Length() 181 | } else { 182 | sel.ParentFiltered("ul[id]") 183 | } 184 | } 185 | if n != 4 { 186 | b.Fatalf("want 4, got %d", n) 187 | } 188 | } 189 | 190 | func BenchmarkParents(b *testing.B) { 191 | var n int 192 | 193 | b.StopTimer() 194 | sel := DocW().Find("th a") 195 | b.StartTimer() 196 | for i := 0; i < b.N; i++ { 197 | if n == 0 { 198 | n = sel.Parents().Length() 199 | } else { 200 | sel.Parents() 201 | } 202 | } 203 | if n != 73 { 204 | b.Fatalf("want 73, got %d", n) 205 | } 206 | } 207 | 208 | func BenchmarkParentsFiltered(b *testing.B) { 209 | var n int 210 | 211 | b.StopTimer() 212 | sel := DocW().Find("th a") 213 | b.StartTimer() 214 | for i := 0; i < b.N; i++ { 215 | if n == 0 { 216 | n = sel.ParentsFiltered("tr").Length() 217 | } else { 218 | sel.ParentsFiltered("tr") 219 | } 220 | } 221 | if n != 18 { 222 | b.Fatalf("want 18, got %d", n) 223 | } 224 | } 225 | 226 | func BenchmarkParentsUntil(b *testing.B) { 227 | var n int 228 | 229 | b.StopTimer() 230 | sel := DocW().Find("th a") 231 | b.StartTimer() 232 | for i := 0; i < b.N; i++ { 233 | if n == 0 { 234 | n = sel.ParentsUntil("table").Length() 235 | } else { 236 | sel.ParentsUntil("table") 237 | } 238 | } 239 | if n != 52 { 240 | b.Fatalf("want 52, got %d", n) 241 | } 242 | } 243 | 244 | func BenchmarkParentsUntilSelection(b *testing.B) { 245 | var n int 246 | 247 | b.StopTimer() 248 | sel := DocW().Find("th a") 249 | sel2 := DocW().Find("#content") 250 | b.StartTimer() 251 | for i := 0; i < b.N; i++ { 252 | if n == 0 { 253 | n = sel.ParentsUntilSelection(sel2).Length() 254 | } else { 255 | sel.ParentsUntilSelection(sel2) 256 | } 257 | } 258 | if n != 70 { 259 | b.Fatalf("want 70, got %d", n) 260 | } 261 | } 262 | 263 | func BenchmarkParentsUntilNodes(b *testing.B) { 264 | var n int 265 | 266 | b.StopTimer() 267 | sel := DocW().Find("th a") 268 | sel2 := DocW().Find("#content") 269 | nodes := sel2.Nodes 270 | b.StartTimer() 271 | for i := 0; i < b.N; i++ { 272 | if n == 0 { 273 | n = sel.ParentsUntilNodes(nodes...).Length() 274 | } else { 275 | sel.ParentsUntilNodes(nodes...) 276 | } 277 | } 278 | if n != 70 { 279 | b.Fatalf("want 70, got %d", n) 280 | } 281 | } 282 | 283 | func BenchmarkParentsFilteredUntil(b *testing.B) { 284 | var n int 285 | 286 | b.StopTimer() 287 | sel := DocW().Find(".toclevel-1 a") 288 | b.StartTimer() 289 | for i := 0; i < b.N; i++ { 290 | if n == 0 { 291 | n = sel.ParentsFilteredUntil(":nth-child(1)", "ul").Length() 292 | } else { 293 | sel.ParentsFilteredUntil(":nth-child(1)", "ul") 294 | } 295 | } 296 | if n != 2 { 297 | b.Fatalf("want 2, got %d", n) 298 | } 299 | } 300 | 301 | func BenchmarkParentsFilteredUntilSelection(b *testing.B) { 302 | var n int 303 | 304 | b.StopTimer() 305 | sel := DocW().Find(".toclevel-1 a") 306 | sel2 := DocW().Find("ul") 307 | b.StartTimer() 308 | for i := 0; i < b.N; i++ { 309 | if n == 0 { 310 | n = sel.ParentsFilteredUntilSelection(":nth-child(1)", sel2).Length() 311 | } else { 312 | sel.ParentsFilteredUntilSelection(":nth-child(1)", sel2) 313 | } 314 | } 315 | if n != 2 { 316 | b.Fatalf("want 2, got %d", n) 317 | } 318 | } 319 | 320 | func BenchmarkParentsFilteredUntilNodes(b *testing.B) { 321 | var n int 322 | 323 | b.StopTimer() 324 | sel := DocW().Find(".toclevel-1 a") 325 | sel2 := DocW().Find("ul") 326 | nodes := sel2.Nodes 327 | b.StartTimer() 328 | for i := 0; i < b.N; i++ { 329 | if n == 0 { 330 | n = sel.ParentsFilteredUntilNodes(":nth-child(1)", nodes...).Length() 331 | } else { 332 | sel.ParentsFilteredUntilNodes(":nth-child(1)", nodes...) 333 | } 334 | } 335 | if n != 2 { 336 | b.Fatalf("want 2, got %d", n) 337 | } 338 | } 339 | 340 | func BenchmarkSiblings(b *testing.B) { 341 | var n int 342 | 343 | b.StopTimer() 344 | sel := DocW().Find("ul li:nth-child(1)") 345 | b.StartTimer() 346 | for i := 0; i < b.N; i++ { 347 | if n == 0 { 348 | n = sel.Siblings().Length() 349 | } else { 350 | sel.Siblings() 351 | } 352 | } 353 | if n != 293 { 354 | b.Fatalf("want 293, got %d", n) 355 | } 356 | } 357 | 358 | func BenchmarkSiblingsFiltered(b *testing.B) { 359 | var n int 360 | 361 | b.StopTimer() 362 | sel := DocW().Find("ul li:nth-child(1)") 363 | b.StartTimer() 364 | for i := 0; i < b.N; i++ { 365 | if n == 0 { 366 | n = sel.SiblingsFiltered("[class]").Length() 367 | } else { 368 | sel.SiblingsFiltered("[class]") 369 | } 370 | } 371 | if n != 46 { 372 | b.Fatalf("want 46, got %d", n) 373 | } 374 | } 375 | 376 | func BenchmarkNext(b *testing.B) { 377 | var n int 378 | 379 | b.StopTimer() 380 | sel := DocW().Find("li:nth-child(1)") 381 | b.StartTimer() 382 | for i := 0; i < b.N; i++ { 383 | if n == 0 { 384 | n = sel.Next().Length() 385 | } else { 386 | sel.Next() 387 | } 388 | } 389 | if n != 49 { 390 | b.Fatalf("want 49, got %d", n) 391 | } 392 | } 393 | 394 | func BenchmarkNextFiltered(b *testing.B) { 395 | var n int 396 | 397 | b.StopTimer() 398 | sel := DocW().Find("li:nth-child(1)") 399 | b.StartTimer() 400 | for i := 0; i < b.N; i++ { 401 | if n == 0 { 402 | n = sel.NextFiltered("[class]").Length() 403 | } else { 404 | sel.NextFiltered("[class]") 405 | } 406 | } 407 | if n != 6 { 408 | b.Fatalf("want 6, got %d", n) 409 | } 410 | } 411 | 412 | func BenchmarkNextAll(b *testing.B) { 413 | var n int 414 | 415 | b.StopTimer() 416 | sel := DocW().Find("li:nth-child(3)") 417 | b.StartTimer() 418 | for i := 0; i < b.N; i++ { 419 | if n == 0 { 420 | n = sel.NextAll().Length() 421 | } else { 422 | sel.NextAll() 423 | } 424 | } 425 | if n != 234 { 426 | b.Fatalf("want 234, got %d", n) 427 | } 428 | } 429 | 430 | func BenchmarkNextAllFiltered(b *testing.B) { 431 | var n int 432 | 433 | b.StopTimer() 434 | sel := DocW().Find("li:nth-child(3)") 435 | b.StartTimer() 436 | for i := 0; i < b.N; i++ { 437 | if n == 0 { 438 | n = sel.NextAllFiltered("[class]").Length() 439 | } else { 440 | sel.NextAllFiltered("[class]") 441 | } 442 | } 443 | if n != 33 { 444 | b.Fatalf("want 33, got %d", n) 445 | } 446 | } 447 | 448 | func BenchmarkPrev(b *testing.B) { 449 | var n int 450 | 451 | b.StopTimer() 452 | sel := DocW().Find("li:last-child") 453 | b.StartTimer() 454 | for i := 0; i < b.N; i++ { 455 | if n == 0 { 456 | n = sel.Prev().Length() 457 | } else { 458 | sel.Prev() 459 | } 460 | } 461 | if n != 49 { 462 | b.Fatalf("want 49, got %d", n) 463 | } 464 | } 465 | 466 | func BenchmarkPrevFiltered(b *testing.B) { 467 | var n int 468 | 469 | b.StopTimer() 470 | sel := DocW().Find("li:last-child") 471 | b.StartTimer() 472 | for i := 0; i < b.N; i++ { 473 | if n == 0 { 474 | n = sel.PrevFiltered("[class]").Length() 475 | } else { 476 | sel.PrevFiltered("[class]") 477 | } 478 | } 479 | // There is one more Prev li with a class, compared to Next li with a class 480 | // (confirmed by looking at the HTML, this is ok) 481 | if n != 7 { 482 | b.Fatalf("want 7, got %d", n) 483 | } 484 | } 485 | 486 | func BenchmarkPrevAll(b *testing.B) { 487 | var n int 488 | 489 | b.StopTimer() 490 | sel := DocW().Find("li:nth-child(4)") 491 | b.StartTimer() 492 | for i := 0; i < b.N; i++ { 493 | if n == 0 { 494 | n = sel.PrevAll().Length() 495 | } else { 496 | sel.PrevAll() 497 | } 498 | } 499 | if n != 78 { 500 | b.Fatalf("want 78, got %d", n) 501 | } 502 | } 503 | 504 | func BenchmarkPrevAllFiltered(b *testing.B) { 505 | var n int 506 | 507 | b.StopTimer() 508 | sel := DocW().Find("li:nth-child(4)") 509 | b.StartTimer() 510 | for i := 0; i < b.N; i++ { 511 | if n == 0 { 512 | n = sel.PrevAllFiltered("[class]").Length() 513 | } else { 514 | sel.PrevAllFiltered("[class]") 515 | } 516 | } 517 | if n != 6 { 518 | b.Fatalf("want 6, got %d", n) 519 | } 520 | } 521 | 522 | func BenchmarkNextUntil(b *testing.B) { 523 | var n int 524 | 525 | b.StopTimer() 526 | sel := DocW().Find("li:first-child") 527 | b.StartTimer() 528 | for i := 0; i < b.N; i++ { 529 | if n == 0 { 530 | n = sel.NextUntil(":nth-child(4)").Length() 531 | } else { 532 | sel.NextUntil(":nth-child(4)") 533 | } 534 | } 535 | if n != 84 { 536 | b.Fatalf("want 84, got %d", n) 537 | } 538 | } 539 | 540 | func BenchmarkNextUntilSelection(b *testing.B) { 541 | var n int 542 | 543 | b.StopTimer() 544 | sel := DocW().Find("h2") 545 | sel2 := DocW().Find("ul") 546 | b.StartTimer() 547 | for i := 0; i < b.N; i++ { 548 | if n == 0 { 549 | n = sel.NextUntilSelection(sel2).Length() 550 | } else { 551 | sel.NextUntilSelection(sel2) 552 | } 553 | } 554 | if n != 42 { 555 | b.Fatalf("want 42, got %d", n) 556 | } 557 | } 558 | 559 | func BenchmarkNextUntilNodes(b *testing.B) { 560 | var n int 561 | 562 | b.StopTimer() 563 | sel := DocW().Find("h2") 564 | sel2 := DocW().Find("p") 565 | nodes := sel2.Nodes 566 | b.StartTimer() 567 | for i := 0; i < b.N; i++ { 568 | if n == 0 { 569 | n = sel.NextUntilNodes(nodes...).Length() 570 | } else { 571 | sel.NextUntilNodes(nodes...) 572 | } 573 | } 574 | if n != 12 { 575 | b.Fatalf("want 12, got %d", n) 576 | } 577 | } 578 | 579 | func BenchmarkPrevUntil(b *testing.B) { 580 | var n int 581 | 582 | b.StopTimer() 583 | sel := DocW().Find("li:last-child") 584 | b.StartTimer() 585 | for i := 0; i < b.N; i++ { 586 | if n == 0 { 587 | n = sel.PrevUntil(":nth-child(4)").Length() 588 | } else { 589 | sel.PrevUntil(":nth-child(4)") 590 | } 591 | } 592 | if n != 238 { 593 | b.Fatalf("want 238, got %d", n) 594 | } 595 | } 596 | 597 | func BenchmarkPrevUntilSelection(b *testing.B) { 598 | var n int 599 | 600 | b.StopTimer() 601 | sel := DocW().Find("h2") 602 | sel2 := DocW().Find("ul") 603 | b.StartTimer() 604 | for i := 0; i < b.N; i++ { 605 | if n == 0 { 606 | n = sel.PrevUntilSelection(sel2).Length() 607 | } else { 608 | sel.PrevUntilSelection(sel2) 609 | } 610 | } 611 | if n != 49 { 612 | b.Fatalf("want 49, got %d", n) 613 | } 614 | } 615 | 616 | func BenchmarkPrevUntilNodes(b *testing.B) { 617 | var n int 618 | 619 | b.StopTimer() 620 | sel := DocW().Find("h2") 621 | sel2 := DocW().Find("p") 622 | nodes := sel2.Nodes 623 | b.StartTimer() 624 | for i := 0; i < b.N; i++ { 625 | if n == 0 { 626 | n = sel.PrevUntilNodes(nodes...).Length() 627 | } else { 628 | sel.PrevUntilNodes(nodes...) 629 | } 630 | } 631 | if n != 11 { 632 | b.Fatalf("want 11, got %d", n) 633 | } 634 | } 635 | 636 | func BenchmarkNextFilteredUntil(b *testing.B) { 637 | var n int 638 | 639 | b.StopTimer() 640 | sel := DocW().Find("h2") 641 | b.StartTimer() 642 | for i := 0; i < b.N; i++ { 643 | if n == 0 { 644 | n = sel.NextFilteredUntil("p", "div").Length() 645 | } else { 646 | sel.NextFilteredUntil("p", "div") 647 | } 648 | } 649 | if n != 22 { 650 | b.Fatalf("want 22, got %d", n) 651 | } 652 | } 653 | 654 | func BenchmarkNextFilteredUntilSelection(b *testing.B) { 655 | var n int 656 | 657 | b.StopTimer() 658 | sel := DocW().Find("h2") 659 | sel2 := DocW().Find("div") 660 | b.StartTimer() 661 | for i := 0; i < b.N; i++ { 662 | if n == 0 { 663 | n = sel.NextFilteredUntilSelection("p", sel2).Length() 664 | } else { 665 | sel.NextFilteredUntilSelection("p", sel2) 666 | } 667 | } 668 | if n != 22 { 669 | b.Fatalf("want 22, got %d", n) 670 | } 671 | } 672 | 673 | func BenchmarkNextFilteredUntilNodes(b *testing.B) { 674 | var n int 675 | 676 | b.StopTimer() 677 | sel := DocW().Find("h2") 678 | sel2 := DocW().Find("div") 679 | nodes := sel2.Nodes 680 | b.StartTimer() 681 | for i := 0; i < b.N; i++ { 682 | if n == 0 { 683 | n = sel.NextFilteredUntilNodes("p", nodes...).Length() 684 | } else { 685 | sel.NextFilteredUntilNodes("p", nodes...) 686 | } 687 | } 688 | if n != 22 { 689 | b.Fatalf("want 22, got %d", n) 690 | } 691 | } 692 | 693 | func BenchmarkPrevFilteredUntil(b *testing.B) { 694 | var n int 695 | 696 | b.StopTimer() 697 | sel := DocW().Find("h2") 698 | b.StartTimer() 699 | for i := 0; i < b.N; i++ { 700 | if n == 0 { 701 | n = sel.PrevFilteredUntil("p", "div").Length() 702 | } else { 703 | sel.PrevFilteredUntil("p", "div") 704 | } 705 | } 706 | if n != 20 { 707 | b.Fatalf("want 20, got %d", n) 708 | } 709 | } 710 | 711 | func BenchmarkPrevFilteredUntilSelection(b *testing.B) { 712 | var n int 713 | 714 | b.StopTimer() 715 | sel := DocW().Find("h2") 716 | sel2 := DocW().Find("div") 717 | b.StartTimer() 718 | for i := 0; i < b.N; i++ { 719 | if n == 0 { 720 | n = sel.PrevFilteredUntilSelection("p", sel2).Length() 721 | } else { 722 | sel.PrevFilteredUntilSelection("p", sel2) 723 | } 724 | } 725 | if n != 20 { 726 | b.Fatalf("want 20, got %d", n) 727 | } 728 | } 729 | 730 | func BenchmarkPrevFilteredUntilNodes(b *testing.B) { 731 | var n int 732 | 733 | b.StopTimer() 734 | sel := DocW().Find("h2") 735 | sel2 := DocW().Find("div") 736 | nodes := sel2.Nodes 737 | b.StartTimer() 738 | for i := 0; i < b.N; i++ { 739 | if n == 0 { 740 | n = sel.PrevFilteredUntilNodes("p", nodes...).Length() 741 | } else { 742 | sel.PrevFilteredUntilNodes("p", nodes...) 743 | } 744 | } 745 | if n != 20 { 746 | b.Fatalf("want 20, got %d", n) 747 | } 748 | } 749 | 750 | func BenchmarkClosest(b *testing.B) { 751 | var n int 752 | 753 | b.StopTimer() 754 | sel := Doc().Find(".container-fluid") 755 | b.StartTimer() 756 | for i := 0; i < b.N; i++ { 757 | if n == 0 { 758 | n = sel.Closest(".pvk-content").Length() 759 | } else { 760 | sel.Closest(".pvk-content") 761 | } 762 | } 763 | if n != 2 { 764 | b.Fatalf("want 2, got %d", n) 765 | } 766 | } 767 | 768 | func BenchmarkClosestSelection(b *testing.B) { 769 | var n int 770 | 771 | b.StopTimer() 772 | sel := Doc().Find(".container-fluid") 773 | sel2 := Doc().Find(".pvk-content") 774 | b.StartTimer() 775 | for i := 0; i < b.N; i++ { 776 | if n == 0 { 777 | n = sel.ClosestSelection(sel2).Length() 778 | } else { 779 | sel.ClosestSelection(sel2) 780 | } 781 | } 782 | if n != 2 { 783 | b.Fatalf("want 2, got %d", n) 784 | } 785 | } 786 | 787 | func BenchmarkClosestNodes(b *testing.B) { 788 | var n int 789 | 790 | b.StopTimer() 791 | sel := Doc().Find(".container-fluid") 792 | nodes := Doc().Find(".pvk-content").Nodes 793 | b.StartTimer() 794 | for i := 0; i < b.N; i++ { 795 | if n == 0 { 796 | n = sel.ClosestNodes(nodes...).Length() 797 | } else { 798 | sel.ClosestNodes(nodes...) 799 | } 800 | } 801 | if n != 2 { 802 | b.Fatalf("want 2, got %d", n) 803 | } 804 | } 805 | 806 | func BenchmarkSingleMatcher(b *testing.B) { 807 | doc := Doc() 808 | multi := cascadia.MustCompile(`div`) 809 | single := SingleMatcher(multi) 810 | b.ResetTimer() 811 | 812 | b.Run("multi", func(b *testing.B) { 813 | for i := 0; i < b.N; i++ { 814 | _ = doc.FindMatcher(multi) 815 | } 816 | }) 817 | b.Run("single", func(b *testing.B) { 818 | for i := 0; i < b.N; i++ { 819 | _ = doc.FindMatcher(single) 820 | } 821 | }) 822 | } 823 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012-2016, Martin Angers & Contributors 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without modification, 5 | // are permitted provided that the following conditions are met: 6 | // 7 | // * Redistributions of source code must retain the above copyright notice, 8 | // this list of conditions and the following disclaimer. 9 | // * Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation and/or 11 | // other materials provided with the distribution. 12 | // * Neither the name of the author nor the names of its contributors may be used to 13 | // endorse or promote products derived from this software without specific prior written permission. 14 | // 15 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 16 | // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 17 | // AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 18 | // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 21 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY 22 | // WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | /* 25 | Package goquery implements features similar to jQuery, including the chainable 26 | syntax, to manipulate and query an HTML document. 27 | 28 | It brings a syntax and a set of features similar to jQuery to the Go language. 29 | It is based on Go's net/html package and the CSS Selector library cascadia. 30 | Since the net/html parser returns nodes, and not a full-featured DOM 31 | tree, jQuery's stateful manipulation functions (like height(), css(), detach()) 32 | have been left off. 33 | 34 | Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is 35 | the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML. 36 | See the repository's wiki for various options on how to do this. 37 | 38 | Syntax-wise, it is as close as possible to jQuery, with the same method names when 39 | possible, and that warm and fuzzy chainable interface. jQuery being the 40 | ultra-popular library that it is, writing a similar HTML-manipulating 41 | library was better to follow its API than to start anew (in the same spirit as 42 | Go's fmt package), even though some of its methods are less than intuitive (looking 43 | at you, index()...). 44 | 45 | It is hosted on GitHub, along with additional documentation in the README.md 46 | file: https://github.com/puerkitobio/goquery 47 | 48 | Please note that because of the net/html dependency, goquery requires Go1.1+. 49 | 50 | The various methods are split into files based on the category of behavior. 51 | The three dots (...) indicate that various "overloads" are available. 52 | 53 | * array.go : array-like positional manipulation of the selection. 54 | - Eq() 55 | - First() 56 | - Get() 57 | - Index...() 58 | - Last() 59 | - Slice() 60 | 61 | * expand.go : methods that expand or augment the selection's set. 62 | - Add...() 63 | - AndSelf() 64 | - Union(), which is an alias for AddSelection() 65 | 66 | * filter.go : filtering methods, that reduce the selection's set. 67 | - End() 68 | - Filter...() 69 | - Has...() 70 | - Intersection(), which is an alias of FilterSelection() 71 | - Not...() 72 | 73 | * iteration.go : methods to loop over the selection's nodes. 74 | - Each() 75 | - EachWithBreak() 76 | - Map() 77 | 78 | * manipulation.go : methods for modifying the document 79 | - After...() 80 | - Append...() 81 | - Before...() 82 | - Clone() 83 | - Empty() 84 | - Prepend...() 85 | - Remove...() 86 | - ReplaceWith...() 87 | - Unwrap() 88 | - Wrap...() 89 | - WrapAll...() 90 | - WrapInner...() 91 | 92 | * property.go : methods that inspect and get the node's properties values. 93 | - Attr*(), RemoveAttr(), SetAttr() 94 | - AddClass(), HasClass(), RemoveClass(), ToggleClass() 95 | - Html() 96 | - Length() 97 | - Size(), which is an alias for Length() 98 | - Text() 99 | 100 | * query.go : methods that query, or reflect, a node's identity. 101 | - Contains() 102 | - Is...() 103 | 104 | * traversal.go : methods to traverse the HTML document tree. 105 | - Children...() 106 | - Contents() 107 | - Find...() 108 | - Next...() 109 | - Parent[s]...() 110 | - Prev...() 111 | - Siblings...() 112 | 113 | * type.go : definition of the types exposed by goquery. 114 | - Document 115 | - Selection 116 | - Matcher 117 | 118 | * utilities.go : definition of helper functions (and not methods on a *Selection) 119 | that are not part of jQuery, but are useful to goquery. 120 | - NodeName 121 | - OuterHtml 122 | */ 123 | package goquery 124 | -------------------------------------------------------------------------------- /doc/tips.md: -------------------------------------------------------------------------------- 1 | # Tips and tricks 2 | 3 | ## Handle Non-UTF8 html Pages 4 | 5 | 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): 6 | 7 | ```bash 8 | $ go get -u github.com/djimenez/iconv-go 9 | ``` 10 | 11 | and then: 12 | 13 | ```golang 14 | // Load the URL 15 | res, err := http.Get(url) 16 | if err != nil { 17 | // handle error 18 | } 19 | defer res.Body.Close() 20 | 21 | // Convert the designated charset HTML to utf-8 encoded HTML. 22 | // `charset` being one of the charsets known by the iconv package. 23 | utfBody, err := iconv.NewReader(res.Body, charset, "utf-8") 24 | if err != nil { 25 | // handler error 26 | } 27 | 28 | // use utfBody using goquery 29 | doc, err := goquery.NewDocumentFromReader(utfBody) 30 | if err != nil { 31 | // handler error 32 | } 33 | // use doc... 34 | ``` 35 | 36 | Thanks to github user @YuheiNakasaka. 37 | 38 | Actually, the official go.text repository covers this use case too, see its [godoc page][text] for the details. 39 | 40 | 41 | ## Handle Javascript-based Pages 42 | 43 | `goquery` is great to handle normal html pages, but when most of the page is build dynamically using javascript, there's not much it can do. There are various options when faced with this problem: 44 | 45 | * Use a headless browser such as [webloop][]. 46 | * Use a Go javascript parser package, such as [otto][]. 47 | 48 | You can find a code example using `otto` [in this gist][exotto]. Thanks to github user @cryptix. 49 | 50 | ## For Loop 51 | 52 | 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: 53 | 54 | ```golang 55 | sel := Doc().Find(".selector") 56 | for i := range sel.Nodes { 57 | single := sel.Eq(i) 58 | // use `single` as a selection of 1 node 59 | } 60 | ``` 61 | 62 | Thanks to github user @jmoiron. 63 | 64 | [webloop]: https://github.com/sourcegraph/webloop 65 | [otto]: https://github.com/robertkrimen/otto 66 | [exotto]: https://gist.github.com/cryptix/87127f76a94183747b53 67 | [iconv]: http://godoc.org/?q=iconv 68 | [text]: https://godoc.org/golang.org/x/text/encoding 69 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package goquery_test 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "github.com/PuerkitoBio/goquery" 11 | ) 12 | 13 | // This example scrapes the reviews shown on the home page of metalsucks.net. 14 | func Example() { 15 | // Request the HTML page. 16 | res, err := http.Get("http://metalsucks.net") 17 | if err != nil { 18 | log.Fatal(err) 19 | } 20 | defer res.Body.Close() 21 | if res.StatusCode != 200 { 22 | log.Fatalf("status code error: %d %s", res.StatusCode, res.Status) 23 | } 24 | 25 | // Load the HTML document 26 | doc, err := goquery.NewDocumentFromReader(res.Body) 27 | if err != nil { 28 | log.Fatal(err) 29 | } 30 | 31 | // Find the review items 32 | doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) { 33 | // For each item found, get the band and title 34 | band := s.Find("a").Text() 35 | title := s.Find("i").Text() 36 | fmt.Printf("Review %d: %s - %s\n", i, band, title) 37 | }) 38 | // To see the output of the Example while running the test suite (go test), simply 39 | // remove the leading "x" before Output on the next line. This will cause the 40 | // example to fail (all the "real" tests should pass). 41 | 42 | // xOutput: voluntarily fail the Example output. 43 | } 44 | 45 | // This example shows how to use NewDocumentFromReader from a file. 46 | func ExampleNewDocumentFromReader_file() { 47 | // create from a file 48 | f, err := os.Open("some/file.html") 49 | if err != nil { 50 | log.Fatal(err) 51 | } 52 | defer f.Close() 53 | doc, err := goquery.NewDocumentFromReader(f) 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | // use the goquery document... 58 | _ = doc.Find("h1") 59 | } 60 | 61 | // This example shows how to use NewDocumentFromReader from a string. 62 | func ExampleNewDocumentFromReader_string() { 63 | // create from a string 64 | data := ` 65 | 66 | 67 | My document 68 | 69 | 70 |

Header

71 | 72 | ` 73 | 74 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(data)) 75 | if err != nil { 76 | log.Fatal(err) 77 | } 78 | header := doc.Find("h1").Text() 79 | fmt.Println(header) 80 | 81 | // Output: Header 82 | } 83 | 84 | func ExampleSingle() { 85 | html := ` 86 | 87 | 88 |
1
89 |
2
90 |
3
91 | 92 | 93 | ` 94 | doc, err := goquery.NewDocumentFromReader(strings.NewReader(html)) 95 | if err != nil { 96 | log.Fatal(err) 97 | } 98 | 99 | // By default, the selector string selects all matching nodes 100 | multiSel := doc.Find("div") 101 | fmt.Println(multiSel.Text()) 102 | 103 | // Using goquery.Single, only the first match is selected 104 | singleSel := doc.FindMatcher(goquery.Single("div")) 105 | fmt.Println(singleSel.Text()) 106 | 107 | // Output: 108 | // 123 109 | // 1 110 | } 111 | -------------------------------------------------------------------------------- /expand.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import "golang.org/x/net/html" 4 | 5 | // Add adds the selector string's matching nodes to those in the current 6 | // selection and returns a new Selection object. 7 | // The selector string is run in the context of the document of the current 8 | // Selection object. 9 | func (s *Selection) Add(selector string) *Selection { 10 | return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, compileMatcher(selector))...) 11 | } 12 | 13 | // AddMatcher adds the matcher's matching nodes to those in the current 14 | // selection and returns a new Selection object. 15 | // The matcher is run in the context of the document of the current 16 | // Selection object. 17 | func (s *Selection) AddMatcher(m Matcher) *Selection { 18 | return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, m)...) 19 | } 20 | 21 | // AddSelection adds the specified Selection object's nodes to those in the 22 | // current selection and returns a new Selection object. 23 | func (s *Selection) AddSelection(sel *Selection) *Selection { 24 | if sel == nil { 25 | return s.AddNodes() 26 | } 27 | return s.AddNodes(sel.Nodes...) 28 | } 29 | 30 | // Union is an alias for AddSelection. 31 | func (s *Selection) Union(sel *Selection) *Selection { 32 | return s.AddSelection(sel) 33 | } 34 | 35 | // AddNodes adds the specified nodes to those in the 36 | // current selection and returns a new Selection object. 37 | func (s *Selection) AddNodes(nodes ...*html.Node) *Selection { 38 | return pushStack(s, appendWithoutDuplicates(s.Nodes, nodes, nil)) 39 | } 40 | 41 | // AndSelf adds the previous set of elements on the stack to the current set. 42 | // It returns a new Selection object containing the current Selection combined 43 | // with the previous one. 44 | // Deprecated: This function has been deprecated and is now an alias for AddBack(). 45 | func (s *Selection) AndSelf() *Selection { 46 | return s.AddBack() 47 | } 48 | 49 | // AddBack adds the previous set of elements on the stack to the current set. 50 | // It returns a new Selection object containing the current Selection combined 51 | // with the previous one. 52 | func (s *Selection) AddBack() *Selection { 53 | return s.AddSelection(s.prevSel) 54 | } 55 | 56 | // AddBackFiltered reduces the previous set of elements on the stack to those that 57 | // match the selector string, and adds them to the current set. 58 | // It returns a new Selection object containing the current Selection combined 59 | // with the filtered previous one 60 | func (s *Selection) AddBackFiltered(selector string) *Selection { 61 | return s.AddSelection(s.prevSel.Filter(selector)) 62 | } 63 | 64 | // AddBackMatcher reduces the previous set of elements on the stack to those that match 65 | // the matcher, and adds them to the current set. 66 | // It returns a new Selection object containing the current Selection combined 67 | // with the filtered previous one 68 | func (s *Selection) AddBackMatcher(m Matcher) *Selection { 69 | return s.AddSelection(s.prevSel.FilterMatcher(m)) 70 | } 71 | -------------------------------------------------------------------------------- /expand_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestAdd(t *testing.T) { 8 | sel := Doc().Find("div.row-fluid").Add("a") 9 | assertLength(t, sel.Nodes, 19) 10 | } 11 | 12 | func TestAddInvalid(t *testing.T) { 13 | sel1 := Doc().Find("div.row-fluid") 14 | sel2 := sel1.Add("") 15 | assertLength(t, sel1.Nodes, 9) 16 | assertLength(t, sel2.Nodes, 9) 17 | if sel1 == sel2 { 18 | t.Errorf("selections should not be the same") 19 | } 20 | } 21 | 22 | func TestAddRollback(t *testing.T) { 23 | sel := Doc().Find(".pvk-content") 24 | sel2 := sel.Add("a").End() 25 | assertEqual(t, sel, sel2) 26 | } 27 | 28 | func TestAddSelection(t *testing.T) { 29 | sel := Doc().Find("div.row-fluid") 30 | sel2 := Doc().Find("a") 31 | sel = sel.AddSelection(sel2) 32 | assertLength(t, sel.Nodes, 19) 33 | } 34 | 35 | func TestAddSelectionNil(t *testing.T) { 36 | sel := Doc().Find("div.row-fluid") 37 | assertLength(t, sel.Nodes, 9) 38 | 39 | sel = sel.AddSelection(nil) 40 | assertLength(t, sel.Nodes, 9) 41 | } 42 | 43 | func TestAddSelectionRollback(t *testing.T) { 44 | sel := Doc().Find(".pvk-content") 45 | sel2 := sel.Find("a") 46 | sel2 = sel.AddSelection(sel2).End() 47 | assertEqual(t, sel, sel2) 48 | } 49 | 50 | func TestAddNodes(t *testing.T) { 51 | sel := Doc().Find("div.pvk-gutter") 52 | sel2 := Doc().Find(".pvk-content") 53 | sel = sel.AddNodes(sel2.Nodes...) 54 | assertLength(t, sel.Nodes, 9) 55 | } 56 | 57 | func TestAddNodesNone(t *testing.T) { 58 | sel := Doc().Find("div.pvk-gutter").AddNodes() 59 | assertLength(t, sel.Nodes, 6) 60 | } 61 | 62 | func TestAddNodesRollback(t *testing.T) { 63 | sel := Doc().Find(".pvk-content") 64 | sel2 := sel.Find("a") 65 | sel2 = sel.AddNodes(sel2.Nodes...).End() 66 | assertEqual(t, sel, sel2) 67 | } 68 | 69 | func TestAddNodesBig(t *testing.T) { 70 | doc := DocW() 71 | sel := doc.Find("li") 72 | assertLength(t, sel.Nodes, 373) 73 | sel2 := doc.Find("xyz") 74 | assertLength(t, sel2.Nodes, 0) 75 | 76 | nodes := sel.Nodes 77 | sel2 = sel2.AddNodes(nodes...) 78 | assertLength(t, sel2.Nodes, 373) 79 | nodes2 := append(nodes, nodes...) 80 | sel2 = sel2.End().AddNodes(nodes2...) 81 | assertLength(t, sel2.Nodes, 373) 82 | nodes3 := append(nodes2, nodes...) 83 | sel2 = sel2.End().AddNodes(nodes3...) 84 | assertLength(t, sel2.Nodes, 373) 85 | } 86 | 87 | func TestAndSelf(t *testing.T) { 88 | sel := Doc().Find(".span12").Last().AndSelf() 89 | assertLength(t, sel.Nodes, 2) 90 | } 91 | 92 | func TestAndSelfRollback(t *testing.T) { 93 | sel := Doc().Find(".pvk-content") 94 | sel2 := sel.Find("a").AndSelf().End().End() 95 | assertEqual(t, sel, sel2) 96 | } 97 | 98 | func TestAddBack(t *testing.T) { 99 | sel := Doc().Find(".span12").Last().AddBack() 100 | assertLength(t, sel.Nodes, 2) 101 | } 102 | 103 | func TestAddBackRollback(t *testing.T) { 104 | sel := Doc().Find(".pvk-content") 105 | sel2 := sel.Find("a").AddBack().End().End() 106 | assertEqual(t, sel, sel2) 107 | } 108 | 109 | func TestAddBackFiltered(t *testing.T) { 110 | sel := Doc().Find(".span12, .footer").Find("h1").AddBackFiltered(".footer") 111 | assertLength(t, sel.Nodes, 2) 112 | } 113 | 114 | func TestAddBackFilteredRollback(t *testing.T) { 115 | sel := Doc().Find(".span12, .footer") 116 | sel2 := sel.Find("h1").AddBackFiltered(".footer").End().End() 117 | assertEqual(t, sel, sel2) 118 | } 119 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import "golang.org/x/net/html" 4 | 5 | // Filter reduces the set of matched elements to those that match the selector string. 6 | // It returns a new Selection object for this subset of matching elements. 7 | func (s *Selection) Filter(selector string) *Selection { 8 | return s.FilterMatcher(compileMatcher(selector)) 9 | } 10 | 11 | // FilterMatcher reduces the set of matched elements to those that match 12 | // the given matcher. It returns a new Selection object for this subset 13 | // of matching elements. 14 | func (s *Selection) FilterMatcher(m Matcher) *Selection { 15 | return pushStack(s, winnow(s, m, true)) 16 | } 17 | 18 | // Not removes elements from the Selection that match the selector string. 19 | // It returns a new Selection object with the matching elements removed. 20 | func (s *Selection) Not(selector string) *Selection { 21 | return s.NotMatcher(compileMatcher(selector)) 22 | } 23 | 24 | // NotMatcher removes elements from the Selection that match the given matcher. 25 | // It returns a new Selection object with the matching elements removed. 26 | func (s *Selection) NotMatcher(m Matcher) *Selection { 27 | return pushStack(s, winnow(s, m, false)) 28 | } 29 | 30 | // FilterFunction reduces the set of matched elements to those that pass the function's test. 31 | // It returns a new Selection object for this subset of elements. 32 | func (s *Selection) FilterFunction(f func(int, *Selection) bool) *Selection { 33 | return pushStack(s, winnowFunction(s, f, true)) 34 | } 35 | 36 | // NotFunction removes elements from the Selection that pass the function's test. 37 | // It returns a new Selection object with the matching elements removed. 38 | func (s *Selection) NotFunction(f func(int, *Selection) bool) *Selection { 39 | return pushStack(s, winnowFunction(s, f, false)) 40 | } 41 | 42 | // FilterNodes reduces the set of matched elements to those that match the specified nodes. 43 | // It returns a new Selection object for this subset of elements. 44 | func (s *Selection) FilterNodes(nodes ...*html.Node) *Selection { 45 | return pushStack(s, winnowNodes(s, nodes, true)) 46 | } 47 | 48 | // NotNodes removes elements from the Selection that match the specified nodes. 49 | // It returns a new Selection object with the matching elements removed. 50 | func (s *Selection) NotNodes(nodes ...*html.Node) *Selection { 51 | return pushStack(s, winnowNodes(s, nodes, false)) 52 | } 53 | 54 | // FilterSelection reduces the set of matched elements to those that match a 55 | // node in the specified Selection object. 56 | // It returns a new Selection object for this subset of elements. 57 | func (s *Selection) FilterSelection(sel *Selection) *Selection { 58 | if sel == nil { 59 | return pushStack(s, winnowNodes(s, nil, true)) 60 | } 61 | return pushStack(s, winnowNodes(s, sel.Nodes, true)) 62 | } 63 | 64 | // NotSelection removes elements from the Selection that match a node in the specified 65 | // Selection object. It returns a new Selection object with the matching elements removed. 66 | func (s *Selection) NotSelection(sel *Selection) *Selection { 67 | if sel == nil { 68 | return pushStack(s, winnowNodes(s, nil, false)) 69 | } 70 | return pushStack(s, winnowNodes(s, sel.Nodes, false)) 71 | } 72 | 73 | // Intersection is an alias for FilterSelection. 74 | func (s *Selection) Intersection(sel *Selection) *Selection { 75 | return s.FilterSelection(sel) 76 | } 77 | 78 | // Has reduces the set of matched elements to those that have a descendant 79 | // that matches the selector. 80 | // It returns a new Selection object with the matching elements. 81 | func (s *Selection) Has(selector string) *Selection { 82 | return s.HasSelection(s.document.Find(selector)) 83 | } 84 | 85 | // HasMatcher reduces the set of matched elements to those that have a descendant 86 | // that matches the matcher. 87 | // It returns a new Selection object with the matching elements. 88 | func (s *Selection) HasMatcher(m Matcher) *Selection { 89 | return s.HasSelection(s.document.FindMatcher(m)) 90 | } 91 | 92 | // HasNodes reduces the set of matched elements to those that have a 93 | // descendant that matches one of the nodes. 94 | // It returns a new Selection object with the matching elements. 95 | func (s *Selection) HasNodes(nodes ...*html.Node) *Selection { 96 | return s.FilterFunction(func(_ int, sel *Selection) bool { 97 | // Add all nodes that contain one of the specified nodes 98 | for _, n := range nodes { 99 | if sel.Contains(n) { 100 | return true 101 | } 102 | } 103 | return false 104 | }) 105 | } 106 | 107 | // HasSelection reduces the set of matched elements to those that have a 108 | // descendant that matches one of the nodes of the specified Selection object. 109 | // It returns a new Selection object with the matching elements. 110 | func (s *Selection) HasSelection(sel *Selection) *Selection { 111 | if sel == nil { 112 | return s.HasNodes() 113 | } 114 | return s.HasNodes(sel.Nodes...) 115 | } 116 | 117 | // End ends the most recent filtering operation in the current chain and 118 | // returns the set of matched elements to its previous state. 119 | func (s *Selection) End() *Selection { 120 | if s.prevSel != nil { 121 | return s.prevSel 122 | } 123 | return newEmptySelection(s.document) 124 | } 125 | 126 | // Filter based on the matcher, and the indicator to keep (Filter) or 127 | // to get rid of (Not) the matching elements. 128 | func winnow(sel *Selection, m Matcher, keep bool) []*html.Node { 129 | // Optimize if keep is requested 130 | if keep { 131 | return m.Filter(sel.Nodes) 132 | } 133 | // Use grep 134 | return grep(sel, func(i int, s *Selection) bool { 135 | return !m.Match(s.Get(0)) 136 | }) 137 | } 138 | 139 | // Filter based on an array of nodes, and the indicator to keep (Filter) or 140 | // to get rid of (Not) the matching elements. 141 | func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node { 142 | if len(nodes)+len(sel.Nodes) < minNodesForSet { 143 | return grep(sel, func(i int, s *Selection) bool { 144 | return isInSlice(nodes, s.Get(0)) == keep 145 | }) 146 | } 147 | 148 | set := make(map[*html.Node]bool) 149 | for _, n := range nodes { 150 | set[n] = true 151 | } 152 | return grep(sel, func(i int, s *Selection) bool { 153 | return set[s.Get(0)] == keep 154 | }) 155 | } 156 | 157 | // Filter based on a function test, and the indicator to keep (Filter) or 158 | // to get rid of (Not) the matching elements. 159 | func winnowFunction(sel *Selection, f func(int, *Selection) bool, keep bool) []*html.Node { 160 | return grep(sel, func(i int, s *Selection) bool { 161 | return f(i, s) == keep 162 | }) 163 | } 164 | -------------------------------------------------------------------------------- /filter_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFilter(t *testing.T) { 8 | sel := Doc().Find(".span12").Filter(".alert") 9 | assertLength(t, sel.Nodes, 1) 10 | } 11 | 12 | func TestFilterNone(t *testing.T) { 13 | sel := Doc().Find(".span12").Filter(".zzalert") 14 | assertLength(t, sel.Nodes, 0) 15 | } 16 | 17 | func TestFilterInvalid(t *testing.T) { 18 | sel := Doc().Find(".span12").Filter("") 19 | assertLength(t, sel.Nodes, 0) 20 | } 21 | 22 | func TestFilterRollback(t *testing.T) { 23 | sel := Doc().Find(".pvk-content") 24 | sel2 := sel.Filter(".alert").End() 25 | assertEqual(t, sel, sel2) 26 | } 27 | 28 | func TestFilterFunction(t *testing.T) { 29 | sel := Doc().Find(".pvk-content").FilterFunction(func(i int, s *Selection) bool { 30 | return i > 0 31 | }) 32 | assertLength(t, sel.Nodes, 2) 33 | } 34 | 35 | func TestFilterFunctionRollback(t *testing.T) { 36 | sel := Doc().Find(".pvk-content") 37 | sel2 := sel.FilterFunction(func(i int, s *Selection) bool { 38 | return i > 0 39 | }).End() 40 | assertEqual(t, sel, sel2) 41 | } 42 | 43 | func TestFilterNode(t *testing.T) { 44 | sel := Doc().Find(".pvk-content") 45 | sel2 := sel.FilterNodes(sel.Nodes[2]) 46 | assertLength(t, sel2.Nodes, 1) 47 | } 48 | 49 | func TestFilterNodeRollback(t *testing.T) { 50 | sel := Doc().Find(".pvk-content") 51 | sel2 := sel.FilterNodes(sel.Nodes[2]).End() 52 | assertEqual(t, sel, sel2) 53 | } 54 | 55 | func TestFilterSelection(t *testing.T) { 56 | sel := Doc().Find(".link") 57 | sel2 := Doc().Find("a[ng-click]") 58 | sel3 := sel.FilterSelection(sel2) 59 | assertLength(t, sel3.Nodes, 1) 60 | } 61 | 62 | func TestFilterSelectionRollback(t *testing.T) { 63 | sel := Doc().Find(".link") 64 | sel2 := Doc().Find("a[ng-click]") 65 | sel2 = sel.FilterSelection(sel2).End() 66 | assertEqual(t, sel, sel2) 67 | } 68 | 69 | func TestFilterSelectionNil(t *testing.T) { 70 | var sel2 *Selection 71 | 72 | sel := Doc().Find(".link") 73 | sel3 := sel.FilterSelection(sel2) 74 | assertLength(t, sel3.Nodes, 0) 75 | } 76 | 77 | func TestNot(t *testing.T) { 78 | sel := Doc().Find(".span12").Not(".alert") 79 | assertLength(t, sel.Nodes, 1) 80 | } 81 | 82 | func TestNotInvalid(t *testing.T) { 83 | sel := Doc().Find(".span12").Not("") 84 | assertLength(t, sel.Nodes, 2) 85 | } 86 | 87 | func TestNotRollback(t *testing.T) { 88 | sel := Doc().Find(".span12") 89 | sel2 := sel.Not(".alert").End() 90 | assertEqual(t, sel, sel2) 91 | } 92 | 93 | func TestNotNone(t *testing.T) { 94 | sel := Doc().Find(".span12").Not(".zzalert") 95 | assertLength(t, sel.Nodes, 2) 96 | } 97 | 98 | func TestNotFunction(t *testing.T) { 99 | sel := Doc().Find(".pvk-content").NotFunction(func(i int, s *Selection) bool { 100 | return i > 0 101 | }) 102 | assertLength(t, sel.Nodes, 1) 103 | } 104 | 105 | func TestNotFunctionRollback(t *testing.T) { 106 | sel := Doc().Find(".pvk-content") 107 | sel2 := sel.NotFunction(func(i int, s *Selection) bool { 108 | return i > 0 109 | }).End() 110 | assertEqual(t, sel, sel2) 111 | } 112 | 113 | func TestNotNode(t *testing.T) { 114 | sel := Doc().Find(".pvk-content") 115 | sel2 := sel.NotNodes(sel.Nodes[2]) 116 | assertLength(t, sel2.Nodes, 2) 117 | } 118 | 119 | func TestNotNodeRollback(t *testing.T) { 120 | sel := Doc().Find(".pvk-content") 121 | sel2 := sel.NotNodes(sel.Nodes[2]).End() 122 | assertEqual(t, sel, sel2) 123 | } 124 | 125 | func TestNotSelection(t *testing.T) { 126 | sel := Doc().Find(".link") 127 | sel2 := Doc().Find("a[ng-click]") 128 | sel3 := sel.NotSelection(sel2) 129 | assertLength(t, sel3.Nodes, 6) 130 | } 131 | 132 | func TestNotSelectionRollback(t *testing.T) { 133 | sel := Doc().Find(".link") 134 | sel2 := Doc().Find("a[ng-click]") 135 | sel2 = sel.NotSelection(sel2).End() 136 | assertEqual(t, sel, sel2) 137 | } 138 | 139 | func TestIntersection(t *testing.T) { 140 | sel := Doc().Find(".pvk-gutter") 141 | sel2 := Doc().Find("div").Intersection(sel) 142 | assertLength(t, sel2.Nodes, 6) 143 | } 144 | 145 | func TestIntersectionRollback(t *testing.T) { 146 | sel := Doc().Find(".pvk-gutter") 147 | sel2 := Doc().Find("div") 148 | sel2 = sel.Intersection(sel2).End() 149 | assertEqual(t, sel, sel2) 150 | } 151 | 152 | func TestHas(t *testing.T) { 153 | sel := Doc().Find(".container-fluid").Has(".center-content") 154 | assertLength(t, sel.Nodes, 2) 155 | // Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content 156 | } 157 | 158 | func TestHasInvalid(t *testing.T) { 159 | sel := Doc().Find(".container-fluid").Has("") 160 | assertLength(t, sel.Nodes, 0) 161 | } 162 | 163 | func TestHasRollback(t *testing.T) { 164 | sel := Doc().Find(".container-fluid") 165 | sel2 := sel.Has(".center-content").End() 166 | assertEqual(t, sel, sel2) 167 | } 168 | 169 | func TestHasNodes(t *testing.T) { 170 | sel := Doc().Find(".container-fluid") 171 | sel2 := Doc().Find(".center-content") 172 | sel = sel.HasNodes(sel2.Nodes...) 173 | assertLength(t, sel.Nodes, 2) 174 | // Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content 175 | } 176 | 177 | func TestHasNodesRollback(t *testing.T) { 178 | sel := Doc().Find(".container-fluid") 179 | sel2 := Doc().Find(".center-content") 180 | sel2 = sel.HasNodes(sel2.Nodes...).End() 181 | assertEqual(t, sel, sel2) 182 | } 183 | 184 | func TestHasSelection(t *testing.T) { 185 | sel := Doc().Find("p") 186 | sel2 := Doc().Find("small") 187 | sel = sel.HasSelection(sel2) 188 | assertLength(t, sel.Nodes, 1) 189 | } 190 | 191 | func TestHasSelectionRollback(t *testing.T) { 192 | sel := Doc().Find("p") 193 | sel2 := Doc().Find("small") 194 | sel2 = sel.HasSelection(sel2).End() 195 | assertEqual(t, sel, sel2) 196 | } 197 | 198 | func TestEnd(t *testing.T) { 199 | sel := Doc().Find("p").Has("small").End() 200 | assertLength(t, sel.Nodes, 4) 201 | } 202 | 203 | func TestEndToTop(t *testing.T) { 204 | sel := Doc().Find("p").Has("small").End().End().End() 205 | assertLength(t, sel.Nodes, 0) 206 | } 207 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PuerkitoBio/goquery 2 | 3 | require ( 4 | github.com/andybalholm/cascadia v1.3.3 5 | golang.org/x/net v0.40.0 6 | ) 7 | 8 | go 1.23.0 9 | 10 | toolchain go1.24.1 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= 2 | github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= 3 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 4 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 5 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 6 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 7 | golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= 8 | golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 9 | golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= 10 | golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= 11 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 12 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 13 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 14 | golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 15 | golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 16 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 17 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 18 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 19 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 20 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 21 | golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= 22 | golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= 23 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 24 | golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 25 | golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= 26 | golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= 27 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 28 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 29 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 30 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 31 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 32 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 33 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 34 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 35 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 36 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 37 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 40 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 41 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 42 | golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 43 | golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 44 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 45 | golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= 46 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 47 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 48 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 49 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 50 | golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 51 | golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 52 | golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= 53 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 54 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 55 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 56 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 57 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 58 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 59 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 60 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 61 | golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 62 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 63 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 64 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 65 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 66 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 67 | golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= 68 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 69 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 70 | -------------------------------------------------------------------------------- /iteration.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import "iter" 4 | 5 | // Each iterates over a Selection object, executing a function for each 6 | // matched element. It returns the current Selection object. The function 7 | // f is called for each element in the selection with the index of the 8 | // element in that selection starting at 0, and a *Selection that contains 9 | // only that element. 10 | func (s *Selection) Each(f func(int, *Selection)) *Selection { 11 | for i, n := range s.Nodes { 12 | f(i, newSingleSelection(n, s.document)) 13 | } 14 | return s 15 | } 16 | 17 | // EachIter returns an iterator that yields the Selection object in order. 18 | // The implementation is similar to Each, but it returns an iterator instead. 19 | func (s *Selection) EachIter() iter.Seq2[int, *Selection] { 20 | return func(yield func(int, *Selection) bool) { 21 | for i, n := range s.Nodes { 22 | if !yield(i, newSingleSelection(n, s.document)) { 23 | return 24 | } 25 | } 26 | } 27 | } 28 | 29 | // EachWithBreak iterates over a Selection object, executing a function for each 30 | // matched element. It is identical to Each except that it is possible to break 31 | // out of the loop by returning false in the callback function. It returns the 32 | // current Selection object. 33 | func (s *Selection) EachWithBreak(f func(int, *Selection) bool) *Selection { 34 | for i, n := range s.Nodes { 35 | if !f(i, newSingleSelection(n, s.document)) { 36 | return s 37 | } 38 | } 39 | return s 40 | } 41 | 42 | // Map passes each element in the current matched set through a function, 43 | // producing a slice of string holding the returned values. The function 44 | // f is called for each element in the selection with the index of the 45 | // element in that selection starting at 0, and a *Selection that contains 46 | // only that element. 47 | func (s *Selection) Map(f func(int, *Selection) string) (result []string) { 48 | return Map(s, f) 49 | } 50 | 51 | // Map is the generic version of Selection.Map, allowing any type to be 52 | // returned. 53 | func Map[E any](s *Selection, f func(int, *Selection) E) (result []E) { 54 | result = make([]E, len(s.Nodes)) 55 | 56 | for i, n := range s.Nodes { 57 | result[i] = f(i, newSingleSelection(n, s.document)) 58 | } 59 | 60 | return result 61 | } 62 | -------------------------------------------------------------------------------- /iteration_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | 6 | "golang.org/x/net/html" 7 | ) 8 | 9 | func TestEach(t *testing.T) { 10 | var cnt int 11 | 12 | sel := Doc().Find(".hero-unit .row-fluid").Each(func(i int, n *Selection) { 13 | cnt++ 14 | t.Logf("At index %v, node %v", i, n.Nodes[0].Data) 15 | }).Find("a") 16 | 17 | if cnt != 4 { 18 | t.Errorf("Expected Each() to call function 4 times, got %v times.", cnt) 19 | } 20 | assertLength(t, sel.Nodes, 6) 21 | } 22 | 23 | func TestEachWithBreak(t *testing.T) { 24 | var cnt int 25 | 26 | sel := Doc().Find(".hero-unit .row-fluid").EachWithBreak(func(i int, n *Selection) bool { 27 | cnt++ 28 | t.Logf("At index %v, node %v", i, n.Nodes[0].Data) 29 | return false 30 | }).Find("a") 31 | 32 | if cnt != 1 { 33 | t.Errorf("Expected Each() to call function 1 time, got %v times.", cnt) 34 | } 35 | assertLength(t, sel.Nodes, 6) 36 | } 37 | 38 | func TestEachEmptySelection(t *testing.T) { 39 | var cnt int 40 | 41 | sel := Doc().Find("zzzz") 42 | sel.Each(func(i int, n *Selection) { 43 | cnt++ 44 | }) 45 | if cnt > 0 { 46 | t.Error("Expected Each() to not be called on empty Selection.") 47 | } 48 | sel2 := sel.Find("div") 49 | assertLength(t, sel2.Nodes, 0) 50 | } 51 | 52 | func TestMap(t *testing.T) { 53 | sel := Doc().Find(".pvk-content") 54 | vals := sel.Map(func(i int, s *Selection) string { 55 | n := s.Get(0) 56 | if n.Type == html.ElementNode { 57 | return n.Data 58 | } 59 | return "" 60 | }) 61 | for _, v := range vals { 62 | if v != "div" { 63 | t.Error("Expected Map array result to be all 'div's.") 64 | } 65 | } 66 | if len(vals) != 3 { 67 | t.Errorf("Expected Map array result to have a length of 3, found %v.", len(vals)) 68 | } 69 | } 70 | 71 | func TestForRange(t *testing.T) { 72 | sel := Doc().Find(".pvk-content") 73 | initLen := sel.Length() 74 | for i := range sel.Nodes { 75 | single := sel.Eq(i) 76 | //h, err := single.Html() 77 | //if err != nil { 78 | // t.Fatal(err) 79 | //} 80 | //fmt.Println(i, h) 81 | if single.Length() != 1 { 82 | t.Errorf("%d: expected length of 1, got %d", i, single.Length()) 83 | } 84 | } 85 | if sel.Length() != initLen { 86 | t.Errorf("expected initial selection to still have length %d, got %d", initLen, sel.Length()) 87 | } 88 | } 89 | 90 | func TestGenericMap(t *testing.T) { 91 | sel := Doc().Find(".pvk-content") 92 | vals := Map(sel, func(i int, s *Selection) *html.NodeType { 93 | n := s.Get(0) 94 | if n.Type == html.ElementNode { 95 | return &n.Type 96 | } 97 | return nil 98 | }) 99 | for _, v := range vals { 100 | if v == nil || *v != html.ElementNode { 101 | t.Error("Expected Map array result to be all 'div's.") 102 | } 103 | } 104 | if len(vals) != 3 { 105 | t.Errorf("Expected Map array result to have a length of 3, found %v.", len(vals)) 106 | } 107 | } 108 | 109 | func TestEachIter(t *testing.T) { 110 | var cnt int 111 | 112 | sel := Doc().Find(".hero-unit .row-fluid") 113 | 114 | for i, s := range sel.EachIter() { 115 | cnt++ 116 | t.Logf("At index %v, node %v", i, s.Nodes[0].Data) 117 | } 118 | 119 | sel = sel.Find("a") 120 | 121 | if cnt != 4 { 122 | t.Errorf("Expected Each() to call function 4 times, got %v times.", cnt) 123 | } 124 | assertLength(t, sel.Nodes, 6) 125 | } 126 | 127 | func TestEachIterWithBreak(t *testing.T) { 128 | var cnt int 129 | 130 | sel := Doc().Find(".hero-unit .row-fluid") 131 | for i, s := range sel.EachIter() { 132 | cnt++ 133 | t.Logf("At index %v, node %v", i, s.Nodes[0].Data) 134 | break 135 | } 136 | 137 | sel = sel.Find("a") 138 | 139 | if cnt != 1 { 140 | t.Errorf("Expected Each() to call function 1 time, got %v times.", cnt) 141 | } 142 | assertLength(t, sel.Nodes, 6) 143 | } 144 | -------------------------------------------------------------------------------- /misc/git/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo ">>> golint" 4 | for dir in $(go list ./... | grep -v /vendor/) 5 | do 6 | golint "${dir}" 7 | done 8 | echo "<<< golint" 9 | echo 10 | 11 | echo ">>> go vet" 12 | go vet $(go list ./... | grep -v /vendor/) 13 | echo "<<< go vet" 14 | echo 15 | 16 | echo ">>> gosimple" 17 | gosimple $(go list ./... | grep -v /vendor/) 18 | echo "<<< gosimple" 19 | echo 20 | 21 | # Check for gofmt problems and report if any. 22 | gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$' | grep -v /vendor/) 23 | [ -z "$gofiles" ] && echo "EXIT $vetres" && exit $vetres 24 | 25 | if [ -n "$gofiles" ]; then 26 | unformatted=$(gofmt -l $gofiles) 27 | 28 | if [ -n "$unformatted" ]; then 29 | # Some files are not gofmt'd. 30 | echo >&2 "Go files must be formatted with gofmt. Please run:" 31 | for fn in $unformatted; do 32 | echo >&2 " gofmt -w $PWD/$fn" 33 | done 34 | fi 35 | fi 36 | echo 37 | 38 | -------------------------------------------------------------------------------- /property.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | 7 | "golang.org/x/net/html" 8 | ) 9 | 10 | var rxClassTrim = regexp.MustCompile("[\t\r\n]") 11 | 12 | // Attr gets the specified attribute's value for the first element in the 13 | // Selection. To get the value for each element individually, use a looping 14 | // construct such as Each or Map method. 15 | func (s *Selection) Attr(attrName string) (val string, exists bool) { 16 | if len(s.Nodes) == 0 { 17 | return 18 | } 19 | return getAttributeValue(attrName, s.Nodes[0]) 20 | } 21 | 22 | // AttrOr works like Attr but returns default value if attribute is not present. 23 | func (s *Selection) AttrOr(attrName, defaultValue string) string { 24 | if len(s.Nodes) == 0 { 25 | return defaultValue 26 | } 27 | 28 | val, exists := getAttributeValue(attrName, s.Nodes[0]) 29 | if !exists { 30 | return defaultValue 31 | } 32 | 33 | return val 34 | } 35 | 36 | // RemoveAttr removes the named attribute from each element in the set of matched elements. 37 | func (s *Selection) RemoveAttr(attrName string) *Selection { 38 | for _, n := range s.Nodes { 39 | removeAttr(n, attrName) 40 | } 41 | 42 | return s 43 | } 44 | 45 | // SetAttr sets the given attribute on each element in the set of matched elements. 46 | func (s *Selection) SetAttr(attrName, val string) *Selection { 47 | for _, n := range s.Nodes { 48 | attr := getAttributePtr(attrName, n) 49 | if attr == nil { 50 | n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val}) 51 | } else { 52 | attr.Val = val 53 | } 54 | } 55 | 56 | return s 57 | } 58 | 59 | // Text gets the combined text contents of each element in the set of matched 60 | // elements, including their descendants. 61 | func (s *Selection) Text() string { 62 | var builder strings.Builder 63 | 64 | // Slightly optimized vs calling Each: no single selection object created 65 | var f func(*html.Node) 66 | f = func(n *html.Node) { 67 | if n.Type == html.TextNode { 68 | // Keep newlines and spaces, like jQuery 69 | builder.WriteString(n.Data) 70 | } 71 | if n.FirstChild != nil { 72 | for c := n.FirstChild; c != nil; c = c.NextSibling { 73 | f(c) 74 | } 75 | } 76 | } 77 | for _, n := range s.Nodes { 78 | f(n) 79 | } 80 | 81 | return builder.String() 82 | } 83 | 84 | // Size is an alias for Length. 85 | func (s *Selection) Size() int { 86 | return s.Length() 87 | } 88 | 89 | // Length returns the number of elements in the Selection object. 90 | func (s *Selection) Length() int { 91 | return len(s.Nodes) 92 | } 93 | 94 | // Html gets the HTML contents of the first element in the set of matched 95 | // elements. It includes text and comment nodes. 96 | func (s *Selection) Html() (ret string, e error) { 97 | // Since there is no .innerHtml, the HTML content must be re-created from 98 | // the nodes using html.Render. 99 | var builder strings.Builder 100 | 101 | if len(s.Nodes) > 0 { 102 | for c := s.Nodes[0].FirstChild; c != nil; c = c.NextSibling { 103 | e = html.Render(&builder, c) 104 | if e != nil { 105 | return 106 | } 107 | } 108 | ret = builder.String() 109 | } 110 | 111 | return 112 | } 113 | 114 | // AddClass adds the given class(es) to each element in the set of matched elements. 115 | // Multiple class names can be specified, separated by a space or via multiple arguments. 116 | func (s *Selection) AddClass(class ...string) *Selection { 117 | classStr := strings.TrimSpace(strings.Join(class, " ")) 118 | 119 | if classStr == "" { 120 | return s 121 | } 122 | 123 | tcls := getClassesSlice(classStr) 124 | for _, n := range s.Nodes { 125 | curClasses, attr := getClassesAndAttr(n, true) 126 | for _, newClass := range tcls { 127 | if !strings.Contains(curClasses, " "+newClass+" ") { 128 | curClasses += newClass + " " 129 | } 130 | } 131 | 132 | setClasses(n, attr, curClasses) 133 | } 134 | 135 | return s 136 | } 137 | 138 | // HasClass determines whether any of the matched elements are assigned the 139 | // given class. 140 | func (s *Selection) HasClass(class string) bool { 141 | class = " " + class + " " 142 | for _, n := range s.Nodes { 143 | classes, _ := getClassesAndAttr(n, false) 144 | if strings.Contains(classes, class) { 145 | return true 146 | } 147 | } 148 | return false 149 | } 150 | 151 | // RemoveClass removes the given class(es) from each element in the set of matched elements. 152 | // Multiple class names can be specified, separated by a space or via multiple arguments. 153 | // If no class name is provided, all classes are removed. 154 | func (s *Selection) RemoveClass(class ...string) *Selection { 155 | var rclasses []string 156 | 157 | classStr := strings.TrimSpace(strings.Join(class, " ")) 158 | remove := classStr == "" 159 | 160 | if !remove { 161 | rclasses = getClassesSlice(classStr) 162 | } 163 | 164 | for _, n := range s.Nodes { 165 | if remove { 166 | removeAttr(n, "class") 167 | } else { 168 | classes, attr := getClassesAndAttr(n, true) 169 | for _, rcl := range rclasses { 170 | classes = strings.ReplaceAll(classes, " "+rcl+" ", " ") 171 | } 172 | 173 | setClasses(n, attr, classes) 174 | } 175 | } 176 | 177 | return s 178 | } 179 | 180 | // ToggleClass adds or removes the given class(es) for each element in the set of matched elements. 181 | // Multiple class names can be specified, separated by a space or via multiple arguments. 182 | func (s *Selection) ToggleClass(class ...string) *Selection { 183 | classStr := strings.TrimSpace(strings.Join(class, " ")) 184 | 185 | if classStr == "" { 186 | return s 187 | } 188 | 189 | tcls := getClassesSlice(classStr) 190 | 191 | for _, n := range s.Nodes { 192 | classes, attr := getClassesAndAttr(n, true) 193 | for _, tcl := range tcls { 194 | spaceAroundTcl := " " + tcl + " " 195 | if strings.Contains(classes, spaceAroundTcl) { 196 | classes = strings.ReplaceAll(classes, spaceAroundTcl, " ") 197 | } else { 198 | classes += tcl + " " 199 | } 200 | } 201 | 202 | setClasses(n, attr, classes) 203 | } 204 | 205 | return s 206 | } 207 | 208 | func getAttributePtr(attrName string, n *html.Node) *html.Attribute { 209 | if n == nil { 210 | return nil 211 | } 212 | 213 | for i, a := range n.Attr { 214 | if a.Key == attrName { 215 | return &n.Attr[i] 216 | } 217 | } 218 | return nil 219 | } 220 | 221 | // Private function to get the specified attribute's value from a node. 222 | func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) { 223 | if a := getAttributePtr(attrName, n); a != nil { 224 | val = a.Val 225 | exists = true 226 | } 227 | return 228 | } 229 | 230 | // Get and normalize the "class" attribute from the node. 231 | func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) { 232 | // Applies only to element nodes 233 | if n.Type == html.ElementNode { 234 | attr = getAttributePtr("class", n) 235 | if attr == nil && create { 236 | n.Attr = append(n.Attr, html.Attribute{ 237 | Key: "class", 238 | Val: "", 239 | }) 240 | attr = &n.Attr[len(n.Attr)-1] 241 | } 242 | } 243 | 244 | if attr == nil { 245 | classes = " " 246 | } else { 247 | classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ") 248 | } 249 | 250 | return 251 | } 252 | 253 | func getClassesSlice(classes string) []string { 254 | return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ") 255 | } 256 | 257 | func removeAttr(n *html.Node, attrName string) { 258 | for i, a := range n.Attr { 259 | if a.Key == attrName { 260 | n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr = 261 | n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1] 262 | return 263 | } 264 | } 265 | } 266 | 267 | func setClasses(n *html.Node, attr *html.Attribute, classes string) { 268 | classes = strings.TrimSpace(classes) 269 | if classes == "" { 270 | removeAttr(n, "class") 271 | return 272 | } 273 | 274 | attr.Val = classes 275 | } 276 | -------------------------------------------------------------------------------- /property_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | "testing" 7 | ) 8 | 9 | func TestAttrExists(t *testing.T) { 10 | if val, ok := Doc().Find("a").Attr("href"); !ok { 11 | t.Error("Expected a value for the href attribute.") 12 | } else { 13 | t.Logf("Href of first anchor: %v.", val) 14 | } 15 | } 16 | 17 | func TestAttrOr(t *testing.T) { 18 | if val := Doc().Find("a").AttrOr("fake-attribute", "alternative"); val != "alternative" { 19 | t.Error("Expected an alternative value for 'fake-attribute' attribute.") 20 | } else { 21 | t.Logf("Value returned for not existing attribute: %v.", val) 22 | } 23 | if val := Doc().Find("zz").AttrOr("fake-attribute", "alternative"); val != "alternative" { 24 | t.Error("Expected an alternative value for 'fake-attribute' on an empty selection.") 25 | } else { 26 | t.Logf("Value returned for empty selection: %v.", val) 27 | } 28 | } 29 | 30 | func TestAttrNotExist(t *testing.T) { 31 | if val, ok := Doc().Find("div.row-fluid").Attr("href"); ok { 32 | t.Errorf("Expected no value for the href attribute, got %v.", val) 33 | } 34 | } 35 | 36 | func TestRemoveAttr(t *testing.T) { 37 | sel := Doc2Clone().Find("div") 38 | 39 | sel.RemoveAttr("id") 40 | 41 | _, ok := sel.Attr("id") 42 | if ok { 43 | t.Error("Expected there to be no id attributes set") 44 | } 45 | } 46 | 47 | func TestSetAttr(t *testing.T) { 48 | sel := Doc2Clone().Find("#main") 49 | 50 | sel.SetAttr("id", "not-main") 51 | 52 | val, ok := sel.Attr("id") 53 | if !ok { 54 | t.Error("Expected an id attribute on main") 55 | } 56 | 57 | if val != "not-main" { 58 | t.Errorf("Expected an attribute id to be not-main, got %s", val) 59 | } 60 | } 61 | 62 | func TestSetAttr2(t *testing.T) { 63 | sel := Doc2Clone().Find("#main") 64 | 65 | sel.SetAttr("foo", "bar") 66 | 67 | val, ok := sel.Attr("foo") 68 | if !ok { 69 | t.Error("Expected an 'foo' attribute on main") 70 | } 71 | 72 | if val != "bar" { 73 | t.Errorf("Expected an attribute 'foo' to be 'bar', got '%s'", val) 74 | } 75 | } 76 | 77 | func TestText(t *testing.T) { 78 | txt := Doc().Find("h1").Text() 79 | if strings.Trim(txt, " \n\r\t") != "Provok.in" { 80 | t.Errorf("Expected text to be Provok.in, found %s.", txt) 81 | } 82 | } 83 | 84 | func TestText2(t *testing.T) { 85 | txt := Doc().Find(".hero-unit .container-fluid .row-fluid:nth-child(1)").Text() 86 | if ok, e := regexp.MatchString(`^\s+Provok\.in\s+Prove your point.\s+$`, txt); !ok || e != nil { 87 | t.Errorf("Expected text to be Provok.in Prove your point., found %s.", txt) 88 | if e != nil { 89 | t.Logf("Error: %s.", e.Error()) 90 | } 91 | } 92 | } 93 | 94 | func TestText3(t *testing.T) { 95 | txt := Doc().Find(".pvk-gutter").First().Text() 96 | // There's an   character in there... 97 | if ok, e := regexp.MatchString(`^[\s\x{00A0}]+$`, txt); !ok || e != nil { 98 | t.Errorf("Expected spaces, found <%v>.", txt) 99 | if e != nil { 100 | t.Logf("Error: %s.", e.Error()) 101 | } 102 | } 103 | } 104 | 105 | func TestHtml(t *testing.T) { 106 | txt, e := Doc().Find("h1").Html() 107 | if e != nil { 108 | t.Errorf("Error: %s.", e) 109 | } 110 | 111 | if ok, e := regexp.MatchString(`^\s*Provok\.in\s*$`, txt); !ok || e != nil { 112 | t.Errorf("Unexpected HTML content, found %s.", txt) 113 | if e != nil { 114 | t.Logf("Error: %s.", e.Error()) 115 | } 116 | } 117 | } 118 | 119 | func TestNbsp(t *testing.T) { 120 | src := `

Some text

` 121 | d, err := NewDocumentFromReader(strings.NewReader(src)) 122 | if err != nil { 123 | t.Fatal(err) 124 | } 125 | txt := d.Find("p").Text() 126 | ix := strings.Index(txt, "\u00a0") 127 | if ix != 4 { 128 | t.Errorf("Text: expected a non-breaking space at index 4, got %d", ix) 129 | } 130 | 131 | h, err := d.Find("p").Html() 132 | if err != nil { 133 | t.Fatal(err) 134 | } 135 | ix = strings.Index(h, "\u00a0") 136 | if ix != 4 { 137 | t.Errorf("Html: expected a non-breaking space at index 4, got %d", ix) 138 | } 139 | } 140 | 141 | func TestAddClass(t *testing.T) { 142 | sel := Doc2Clone().Find("#main") 143 | sel.AddClass("main main main") 144 | 145 | // Make sure that class was only added once 146 | if a, ok := sel.Attr("class"); !ok || a != "main" { 147 | t.Error("Expected #main to have class main") 148 | } 149 | } 150 | 151 | func TestAddClassSimilar(t *testing.T) { 152 | sel := Doc2Clone().Find("#nf5") 153 | sel.AddClass("odd") 154 | 155 | assertClass(t, sel, "odd") 156 | assertClass(t, sel, "odder") 157 | printSel(t, sel.Parent()) 158 | } 159 | 160 | func TestAddEmptyClass(t *testing.T) { 161 | sel := Doc2Clone().Find("#main") 162 | sel.AddClass("") 163 | 164 | // Make sure that class was only added once 165 | if a, ok := sel.Attr("class"); ok { 166 | t.Errorf("Expected #main to not to have a class, have: %s", a) 167 | } 168 | } 169 | 170 | func TestAddClasses(t *testing.T) { 171 | sel := Doc2Clone().Find("#main") 172 | sel.AddClass("a b") 173 | 174 | // Make sure that class was only added once 175 | if !sel.HasClass("a") || !sel.HasClass("b") { 176 | t.Errorf("#main does not have classes") 177 | } 178 | } 179 | 180 | func TestHasClass(t *testing.T) { 181 | sel := Doc().Find("div") 182 | if !sel.HasClass("span12") { 183 | t.Error("Expected at least one div to have class span12.") 184 | } 185 | } 186 | 187 | func TestHasClassNone(t *testing.T) { 188 | sel := Doc().Find("h2") 189 | if sel.HasClass("toto") { 190 | t.Error("Expected h1 to have no class.") 191 | } 192 | } 193 | 194 | func TestHasClassNotFirst(t *testing.T) { 195 | sel := Doc().Find(".alert") 196 | if !sel.HasClass("alert-error") { 197 | t.Error("Expected .alert to also have class .alert-error.") 198 | } 199 | } 200 | 201 | func TestRemoveClass(t *testing.T) { 202 | sel := Doc2Clone().Find("#nf1") 203 | sel.RemoveClass("one row") 204 | 205 | if !sel.HasClass("even") || sel.HasClass("one") || sel.HasClass("row") { 206 | classes, _ := sel.Attr("class") 207 | t.Error("Expected #nf1 to have class even, has ", classes) 208 | } 209 | } 210 | 211 | func TestRemoveClassSimilar(t *testing.T) { 212 | sel := Doc2Clone().Find("#nf5, #nf6") 213 | assertLength(t, sel.Nodes, 2) 214 | 215 | sel.RemoveClass("odd") 216 | assertClass(t, sel.Eq(0), "odder") 217 | printSel(t, sel) 218 | } 219 | 220 | func TestRemoveAllClasses(t *testing.T) { 221 | sel := Doc2Clone().Find("#nf1") 222 | sel.RemoveClass() 223 | 224 | if a, ok := sel.Attr("class"); ok { 225 | t.Error("All classes were not removed, has ", a) 226 | } 227 | 228 | sel = Doc2Clone().Find("#main") 229 | sel.RemoveClass() 230 | if a, ok := sel.Attr("class"); ok { 231 | t.Error("All classes were not removed, has ", a) 232 | } 233 | } 234 | 235 | func TestToggleClass(t *testing.T) { 236 | sel := Doc2Clone().Find("#nf1") 237 | 238 | sel.ToggleClass("one") 239 | if sel.HasClass("one") { 240 | t.Error("Expected #nf1 to not have class one") 241 | } 242 | 243 | sel.ToggleClass("one") 244 | if !sel.HasClass("one") { 245 | t.Error("Expected #nf1 to have class one") 246 | } 247 | 248 | sel.ToggleClass("one even row") 249 | if a, ok := sel.Attr("class"); ok { 250 | t.Errorf("Expected #nf1 to have no classes, have %q", a) 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /query.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import "golang.org/x/net/html" 4 | 5 | // Is checks the current matched set of elements against a selector and 6 | // returns true if at least one of these elements matches. 7 | func (s *Selection) Is(selector string) bool { 8 | return s.IsMatcher(compileMatcher(selector)) 9 | } 10 | 11 | // IsMatcher checks the current matched set of elements against a matcher and 12 | // returns true if at least one of these elements matches. 13 | func (s *Selection) IsMatcher(m Matcher) bool { 14 | if len(s.Nodes) > 0 { 15 | if len(s.Nodes) == 1 { 16 | return m.Match(s.Nodes[0]) 17 | } 18 | return len(m.Filter(s.Nodes)) > 0 19 | } 20 | 21 | return false 22 | } 23 | 24 | // IsFunction checks the current matched set of elements against a predicate and 25 | // returns true if at least one of these elements matches. 26 | func (s *Selection) IsFunction(f func(int, *Selection) bool) bool { 27 | return s.FilterFunction(f).Length() > 0 28 | } 29 | 30 | // IsSelection checks the current matched set of elements against a Selection object 31 | // and returns true if at least one of these elements matches. 32 | func (s *Selection) IsSelection(sel *Selection) bool { 33 | return s.FilterSelection(sel).Length() > 0 34 | } 35 | 36 | // IsNodes checks the current matched set of elements against the specified nodes 37 | // and returns true if at least one of these elements matches. 38 | func (s *Selection) IsNodes(nodes ...*html.Node) bool { 39 | return s.FilterNodes(nodes...).Length() > 0 40 | } 41 | 42 | // Contains returns true if the specified Node is within, 43 | // at any depth, one of the nodes in the Selection object. 44 | // It is NOT inclusive, to behave like jQuery's implementation, and 45 | // unlike Javascript's .contains, so if the contained 46 | // node is itself in the selection, it returns false. 47 | func (s *Selection) Contains(n *html.Node) bool { 48 | return sliceContains(s.Nodes, n) 49 | } 50 | -------------------------------------------------------------------------------- /query_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestIs(t *testing.T) { 8 | sel := Doc().Find(".footer p:nth-child(1)") 9 | if !sel.Is("p") { 10 | t.Error("Expected .footer p:nth-child(1) to be p.") 11 | } 12 | } 13 | 14 | func TestIsInvalid(t *testing.T) { 15 | sel := Doc().Find(".footer p:nth-child(1)") 16 | if sel.Is("") { 17 | t.Error("Is should not succeed with invalid selector string") 18 | } 19 | } 20 | 21 | func TestIsPositional(t *testing.T) { 22 | sel := Doc().Find(".footer p:nth-child(2)") 23 | if !sel.Is("p:nth-child(2)") { 24 | t.Error("Expected .footer p:nth-child(2) to be p:nth-child(2).") 25 | } 26 | } 27 | 28 | func TestIsPositionalNot(t *testing.T) { 29 | sel := Doc().Find(".footer p:nth-child(1)") 30 | if sel.Is("p:nth-child(2)") { 31 | t.Error("Expected .footer p:nth-child(1) NOT to be p:nth-child(2).") 32 | } 33 | } 34 | 35 | func TestIsFunction(t *testing.T) { 36 | ok := Doc().Find("div").IsFunction(func(i int, s *Selection) bool { 37 | return s.HasClass("container-fluid") 38 | }) 39 | 40 | if !ok { 41 | t.Error("Expected some div to have a container-fluid class.") 42 | } 43 | } 44 | 45 | func TestIsFunctionRollback(t *testing.T) { 46 | ok := Doc().Find("div").IsFunction(func(i int, s *Selection) bool { 47 | return s.HasClass("container-fluid") 48 | }) 49 | 50 | if !ok { 51 | t.Error("Expected some div to have a container-fluid class.") 52 | } 53 | } 54 | 55 | func TestIsSelection(t *testing.T) { 56 | sel := Doc().Find("div") 57 | sel2 := Doc().Find(".pvk-gutter") 58 | 59 | if !sel.IsSelection(sel2) { 60 | t.Error("Expected some div to have a pvk-gutter class.") 61 | } 62 | } 63 | 64 | func TestIsSelectionNot(t *testing.T) { 65 | sel := Doc().Find("div") 66 | sel2 := Doc().Find("a") 67 | 68 | if sel.IsSelection(sel2) { 69 | t.Error("Expected some div NOT to be an anchor.") 70 | } 71 | } 72 | 73 | func TestIsNodes(t *testing.T) { 74 | sel := Doc().Find("div") 75 | sel2 := Doc().Find(".footer") 76 | 77 | if !sel.IsNodes(sel2.Nodes[0]) { 78 | t.Error("Expected some div to have a footer class.") 79 | } 80 | } 81 | 82 | func TestDocContains(t *testing.T) { 83 | sel := Doc().Find("h1") 84 | if !Doc().Contains(sel.Nodes[0]) { 85 | t.Error("Expected document to contain H1 tag.") 86 | } 87 | } 88 | 89 | func TestSelContains(t *testing.T) { 90 | sel := Doc().Find(".row-fluid") 91 | sel2 := Doc().Find("a[ng-click]") 92 | if !sel.Contains(sel2.Nodes[0]) { 93 | t.Error("Expected .row-fluid to contain a[ng-click] tag.") 94 | } 95 | } 96 | 97 | func TestSelNotContains(t *testing.T) { 98 | sel := Doc().Find("a.link") 99 | sel2 := Doc().Find("span") 100 | if sel.Contains(sel2.Nodes[0]) { 101 | t.Error("Expected a.link to NOT contain span tag.") 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /testdata/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Provok.in 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |
19 |
20 |   21 |
22 |
23 |
24 |
25 |
26 |
27 |

28 | Provok.in 29 |

30 |

31 | Prove your point. 32 |

33 |
34 |
35 |
36 |
37 | Beta Version. Things may change. Or disappear. Or fail miserably. If it's the latter, please file an issue. 38 |
39 |
40 | 43 |
44 | Welcome, {{getUserName()}} ( logout ) 45 |
46 |
47 |
48 |
49 |
50 |   51 |
52 |
53 |
54 |
55 |   56 |
57 |
58 |
59 |
60 |
61 |
62 | × 63 |

64 | {{ title }} 65 |

66 |

67 | {{ message }} 68 |

69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |   80 |
81 |
82 |
83 |
84 |   85 |
86 |
87 | 95 |
96 |
97 |   98 |
99 |
100 |
101 | 102 | -------------------------------------------------------------------------------- /testdata/page2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tests for siblings 5 | 6 | 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /testdata/page3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Tests for siblings 5 | 6 | 7 |
8 |
hello
9 |
10 |
11 |
12 |
13 |
14 |
15 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /type.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "errors" 5 | "io" 6 | "net/http" 7 | "net/url" 8 | 9 | "github.com/andybalholm/cascadia" 10 | "golang.org/x/net/html" 11 | ) 12 | 13 | // Document represents an HTML document to be manipulated. Unlike jQuery, which 14 | // is loaded as part of a DOM document, and thus acts upon its containing 15 | // document, GoQuery doesn't know which HTML document to act upon. So it needs 16 | // to be told, and that's what the Document class is for. It holds the root 17 | // document node to manipulate, and can make selections on this document. 18 | type Document struct { 19 | *Selection 20 | Url *url.URL 21 | rootNode *html.Node 22 | } 23 | 24 | // NewDocumentFromNode is a Document constructor that takes a root html Node 25 | // as argument. 26 | func NewDocumentFromNode(root *html.Node) *Document { 27 | return newDocument(root, nil) 28 | } 29 | 30 | // NewDocument is a Document constructor that takes a string URL as argument. 31 | // It loads the specified document, parses it, and stores the root Document 32 | // node, ready to be manipulated. 33 | // 34 | // Deprecated: Use the net/http standard library package to make the request 35 | // and validate the response before calling goquery.NewDocumentFromReader 36 | // with the response's body. 37 | func NewDocument(url string) (*Document, error) { 38 | // Load the URL 39 | res, e := http.Get(url) 40 | if e != nil { 41 | return nil, e 42 | } 43 | return NewDocumentFromResponse(res) 44 | } 45 | 46 | // NewDocumentFromReader returns a Document from an io.Reader. 47 | // It returns an error as second value if the reader's data cannot be parsed 48 | // as html. It does not check if the reader is also an io.Closer, the 49 | // provided reader is never closed by this call. It is the responsibility 50 | // of the caller to close it if required. 51 | func NewDocumentFromReader(r io.Reader) (*Document, error) { 52 | root, e := html.Parse(r) 53 | if e != nil { 54 | return nil, e 55 | } 56 | return newDocument(root, nil), nil 57 | } 58 | 59 | // NewDocumentFromResponse is another Document constructor that takes an http response as argument. 60 | // It loads the specified response's document, parses it, and stores the root Document 61 | // node, ready to be manipulated. The response's body is closed on return. 62 | // 63 | // Deprecated: Use goquery.NewDocumentFromReader with the response's body. 64 | func NewDocumentFromResponse(res *http.Response) (*Document, error) { 65 | if res == nil { 66 | return nil, errors.New("Response is nil") 67 | } 68 | defer res.Body.Close() 69 | if res.Request == nil { 70 | return nil, errors.New("Response.Request is nil") 71 | } 72 | 73 | // Parse the HTML into nodes 74 | root, e := html.Parse(res.Body) 75 | if e != nil { 76 | return nil, e 77 | } 78 | 79 | // Create and fill the document 80 | return newDocument(root, res.Request.URL), nil 81 | } 82 | 83 | // CloneDocument creates a deep-clone of a document. 84 | func CloneDocument(doc *Document) *Document { 85 | return newDocument(cloneNode(doc.rootNode), doc.Url) 86 | } 87 | 88 | // Private constructor, make sure all fields are correctly filled. 89 | func newDocument(root *html.Node, url *url.URL) *Document { 90 | // Create and fill the document 91 | d := &Document{nil, url, root} 92 | d.Selection = newSingleSelection(root, d) 93 | return d 94 | } 95 | 96 | // Selection represents a collection of nodes matching some criteria. The 97 | // initial Selection can be created by using Document.Find, and then 98 | // manipulated using the jQuery-like chainable syntax and methods. 99 | type Selection struct { 100 | Nodes []*html.Node 101 | document *Document 102 | prevSel *Selection 103 | } 104 | 105 | // Helper constructor to create an empty selection 106 | func newEmptySelection(doc *Document) *Selection { 107 | return &Selection{nil, doc, nil} 108 | } 109 | 110 | // Helper constructor to create a selection of only one node 111 | func newSingleSelection(node *html.Node, doc *Document) *Selection { 112 | return &Selection{[]*html.Node{node}, doc, nil} 113 | } 114 | 115 | // Matcher is an interface that defines the methods to match 116 | // HTML nodes against a compiled selector string. Cascadia's 117 | // Selector implements this interface. 118 | type Matcher interface { 119 | Match(*html.Node) bool 120 | MatchAll(*html.Node) []*html.Node 121 | Filter([]*html.Node) []*html.Node 122 | } 123 | 124 | // Single compiles a selector string to a Matcher that stops after the first 125 | // match is found. 126 | // 127 | // By default, Selection.Find and other functions that accept a selector string 128 | // to select nodes will use all matches corresponding to that selector. By 129 | // using the Matcher returned by Single, at most the first match will be 130 | // selected. 131 | // 132 | // For example, those two statements are semantically equivalent: 133 | // 134 | // sel1 := doc.Find("a").First() 135 | // sel2 := doc.FindMatcher(goquery.Single("a")) 136 | // 137 | // The one using Single is optimized to be potentially much faster on large 138 | // documents. 139 | // 140 | // Only the behaviour of the MatchAll method of the Matcher interface is 141 | // altered compared to standard Matchers. This means that the single-selection 142 | // property of the Matcher only applies for Selection methods where the Matcher 143 | // is used to select nodes, not to filter or check if a node matches the 144 | // Matcher - in those cases, the behaviour of the Matcher is unchanged (e.g. 145 | // FilterMatcher(Single("div")) will still result in a Selection with multiple 146 | // "div"s if there were many "div"s in the Selection to begin with). 147 | func Single(selector string) Matcher { 148 | return singleMatcher{compileMatcher(selector)} 149 | } 150 | 151 | // SingleMatcher returns a Matcher matches the same nodes as m, but that stops 152 | // after the first match is found. 153 | // 154 | // See the documentation of function Single for more details. 155 | func SingleMatcher(m Matcher) Matcher { 156 | if _, ok := m.(singleMatcher); ok { 157 | // m is already a singleMatcher 158 | return m 159 | } 160 | return singleMatcher{m} 161 | } 162 | 163 | // compileMatcher compiles the selector string s and returns 164 | // the corresponding Matcher. If s is an invalid selector string, 165 | // it returns a Matcher that fails all matches. 166 | func compileMatcher(s string) Matcher { 167 | cs, err := cascadia.Compile(s) 168 | if err != nil { 169 | return invalidMatcher{} 170 | } 171 | return cs 172 | } 173 | 174 | type singleMatcher struct { 175 | Matcher 176 | } 177 | 178 | func (m singleMatcher) MatchAll(n *html.Node) []*html.Node { 179 | // Optimized version - stops finding at the first match (cascadia-compiled 180 | // matchers all use this code path). 181 | if mm, ok := m.Matcher.(interface{ MatchFirst(*html.Node) *html.Node }); ok { 182 | node := mm.MatchFirst(n) 183 | if node == nil { 184 | return nil 185 | } 186 | return []*html.Node{node} 187 | } 188 | 189 | // Fallback version, for e.g. test mocks that don't provide the MatchFirst 190 | // method. 191 | nodes := m.Matcher.MatchAll(n) 192 | if len(nodes) > 0 { 193 | return nodes[:1:1] 194 | } 195 | return nil 196 | } 197 | 198 | // invalidMatcher is a Matcher that always fails to match. 199 | type invalidMatcher struct{} 200 | 201 | func (invalidMatcher) Match(n *html.Node) bool { return false } 202 | func (invalidMatcher) MatchAll(n *html.Node) []*html.Node { return nil } 203 | func (invalidMatcher) Filter(ns []*html.Node) []*html.Node { return nil } 204 | -------------------------------------------------------------------------------- /type_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "os" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/andybalholm/cascadia" 11 | "golang.org/x/net/html" 12 | ) 13 | 14 | // Test helper functions and members 15 | var doc *Document 16 | var doc2 *Document 17 | var doc3 *Document 18 | var docB *Document 19 | var docW *Document 20 | 21 | func Doc() *Document { 22 | if doc == nil { 23 | doc = loadDoc("page.html") 24 | } 25 | return doc 26 | } 27 | 28 | func Doc2() *Document { 29 | if doc2 == nil { 30 | doc2 = loadDoc("page2.html") 31 | } 32 | return doc2 33 | } 34 | 35 | func Doc2Clone() *Document { 36 | return CloneDocument(Doc2()) 37 | } 38 | 39 | func Doc3() *Document { 40 | if doc3 == nil { 41 | doc3 = loadDoc("page3.html") 42 | } 43 | return doc3 44 | } 45 | 46 | func Doc3Clone() *Document { 47 | return CloneDocument(Doc3()) 48 | } 49 | 50 | func DocB() *Document { 51 | if docB == nil { 52 | docB = loadDoc("gotesting.html") 53 | } 54 | return docB 55 | } 56 | 57 | func DocW() *Document { 58 | if docW == nil { 59 | docW = loadDoc("gowiki.html") 60 | } 61 | return docW 62 | } 63 | 64 | func assertLength(t *testing.T, nodes []*html.Node, length int) { 65 | if len(nodes) != length { 66 | t.Errorf("Expected %d nodes, found %d.", length, len(nodes)) 67 | for i, n := range nodes { 68 | t.Logf("Node %d: %+v.", i, n) 69 | } 70 | } 71 | } 72 | 73 | func assertClass(t *testing.T, sel *Selection, class string) { 74 | if !sel.HasClass(class) { 75 | t.Errorf("Expected node to have class %s, found %+v.", class, sel.Get(0)) 76 | } 77 | } 78 | 79 | func assertPanic(t *testing.T) { 80 | if e := recover(); e == nil { 81 | t.Error("Expected a panic.") 82 | } 83 | } 84 | 85 | func assertEqual(t *testing.T, s1 *Selection, s2 *Selection) { 86 | if s1 != s2 { 87 | t.Error("Expected selection objects to be the same.") 88 | } 89 | } 90 | 91 | func assertSelectionIs(t *testing.T, sel *Selection, is ...string) { 92 | for i := 0; i < sel.Length(); i++ { 93 | if !sel.Eq(i).Is(is[i]) { 94 | t.Errorf("Expected node %d to be %s, found %+v", i, is[i], sel.Get(i)) 95 | } 96 | } 97 | } 98 | 99 | func printSel(t *testing.T, sel *Selection) { 100 | if testing.Verbose() { 101 | h, err := sel.Html() 102 | if err != nil { 103 | t.Fatal(err) 104 | } 105 | t.Log(h) 106 | } 107 | } 108 | 109 | func loadDoc(page string) *Document { 110 | var f *os.File 111 | var e error 112 | 113 | if f, e = os.Open(fmt.Sprintf("./testdata/%s", page)); e != nil { 114 | panic(e.Error()) 115 | } 116 | defer f.Close() 117 | 118 | var node *html.Node 119 | if node, e = html.Parse(f); e != nil { 120 | panic(e.Error()) 121 | } 122 | return NewDocumentFromNode(node) 123 | } 124 | 125 | func loadString(t *testing.T, doc string) *Document { 126 | d, err := NewDocumentFromReader(strings.NewReader(doc)) 127 | if err != nil { 128 | t.Error("Failed to parse test document") 129 | } 130 | return d 131 | } 132 | 133 | func TestNewDocument(t *testing.T) { 134 | if f, e := os.Open("./testdata/page.html"); e != nil { 135 | t.Error(e.Error()) 136 | } else { 137 | defer f.Close() 138 | if node, e := html.Parse(f); e != nil { 139 | t.Error(e.Error()) 140 | } else { 141 | doc = NewDocumentFromNode(node) 142 | } 143 | } 144 | } 145 | 146 | func TestNewDocumentFromReader(t *testing.T) { 147 | cases := []struct { 148 | src string 149 | err bool 150 | sel string 151 | cnt int 152 | }{ 153 | 0: { 154 | src: ` 155 | 156 | 157 | Test 158 | 159 |

Hi

160 | 161 | `, 162 | sel: "h1", 163 | cnt: 1, 164 | }, 165 | 1: { 166 | // Actually pretty hard to make html.Parse return an error 167 | // based on content... 168 | src: `>>qq>`, 169 | }, 170 | } 171 | buf := bytes.NewBuffer(nil) 172 | 173 | for i, c := range cases { 174 | buf.Reset() 175 | buf.WriteString(c.src) 176 | 177 | d, e := NewDocumentFromReader(buf) 178 | if (e != nil) != c.err { 179 | if c.err { 180 | t.Errorf("[%d] - expected error, got none", i) 181 | } else { 182 | t.Errorf("[%d] - expected no error, got %s", i, e) 183 | } 184 | } 185 | if c.sel != "" { 186 | s := d.Find(c.sel) 187 | if s.Length() != c.cnt { 188 | t.Errorf("[%d] - expected %d nodes, found %d", i, c.cnt, s.Length()) 189 | } 190 | } 191 | } 192 | } 193 | 194 | func TestNewDocumentFromResponseNil(t *testing.T) { 195 | _, e := NewDocumentFromResponse(nil) 196 | if e == nil { 197 | t.Error("Expected error, got none") 198 | } 199 | } 200 | 201 | func TestIssue103(t *testing.T) { 202 | d, err := NewDocumentFromReader(strings.NewReader("Scientists Stored These Images in DNA—Then Flawlessly Retrieved Them")) 203 | if err != nil { 204 | t.Error(err) 205 | } 206 | text := d.Find("title").Text() 207 | for i, r := range text { 208 | t.Logf("%d: %d - %q\n", i, r, string(r)) 209 | } 210 | t.Log(text) 211 | } 212 | 213 | func TestSingle(t *testing.T) { 214 | data := ` 215 | 216 | 217 |
1
218 |
2
219 |
3
220 |

4

221 | 222 | 223 | ` 224 | doc, err := NewDocumentFromReader(strings.NewReader(data)) 225 | if err != nil { 226 | t.Fatal(err) 227 | } 228 | 229 | text := doc.FindMatcher(Single("div")).Text() 230 | if text != "1" { 231 | t.Fatalf("want %q, got %q", "1", text) 232 | } 233 | 234 | // Verify semantic equivalence 235 | sel1 := doc.Find("div").First() 236 | sel2 := doc.FindMatcher(Single("div")) 237 | if sel1.Text() != sel2.Text() { 238 | t.Fatalf("want sel1 to equal sel2") 239 | } 240 | 241 | // Here, the Single has no effect as the selector is used to filter 242 | // from the existing selection, not to find nodes in the document. 243 | divs := doc.Find("div") 244 | text = divs.FilterMatcher(Single(".a")).Text() 245 | if text != "23" { 246 | t.Fatalf("want %q, got %q", "23", text) 247 | } 248 | 249 | classA := cascadia.MustCompile(".a") 250 | classB := cascadia.MustCompile(".b") 251 | text = doc.FindMatcher(classB).AddMatcher(SingleMatcher(classA)).Text() 252 | if text != "142" { 253 | t.Fatalf("want %q, got %q", "142", text) 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /utilities.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "io" 5 | "strings" 6 | 7 | "golang.org/x/net/html" 8 | ) 9 | 10 | // used to determine if a set (map[*html.Node]bool) should be used 11 | // instead of iterating over a slice. The set uses more memory and 12 | // is slower than slice iteration for small N. 13 | const minNodesForSet = 1000 14 | 15 | var nodeNames = []string{ 16 | html.ErrorNode: "#error", 17 | html.TextNode: "#text", 18 | html.DocumentNode: "#document", 19 | html.CommentNode: "#comment", 20 | } 21 | 22 | // NodeName returns the node name of the first element in the selection. 23 | // It tries to behave in a similar way as the DOM's nodeName property 24 | // (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName). 25 | // 26 | // Go's net/html package defines the following node types, listed with 27 | // the corresponding returned value from this function: 28 | // 29 | // ErrorNode : #error 30 | // TextNode : #text 31 | // DocumentNode : #document 32 | // ElementNode : the element's tag name 33 | // CommentNode : #comment 34 | // DoctypeNode : the name of the document type 35 | func NodeName(s *Selection) string { 36 | if s.Length() == 0 { 37 | return "" 38 | } 39 | return nodeName(s.Get(0)) 40 | } 41 | 42 | // nodeName returns the node name of the given html node. 43 | // See NodeName for additional details on behaviour. 44 | func nodeName(node *html.Node) string { 45 | if node == nil { 46 | return "" 47 | } 48 | 49 | switch node.Type { 50 | case html.ElementNode, html.DoctypeNode: 51 | return node.Data 52 | default: 53 | if int(node.Type) < len(nodeNames) { 54 | return nodeNames[node.Type] 55 | } 56 | return "" 57 | } 58 | } 59 | 60 | // Render renders the HTML of the first item in the selection and writes it to 61 | // the writer. It behaves the same as OuterHtml but writes to w instead of 62 | // returning the string. 63 | func Render(w io.Writer, s *Selection) error { 64 | if s.Length() == 0 { 65 | return nil 66 | } 67 | n := s.Get(0) 68 | return html.Render(w, n) 69 | } 70 | 71 | // OuterHtml returns the outer HTML rendering of the first item in 72 | // the selection - that is, the HTML including the first element's 73 | // tag and attributes. 74 | // 75 | // Unlike Html, this is a function and not a method on the Selection, 76 | // because this is not a jQuery method (in javascript-land, this is 77 | // a property provided by the DOM). 78 | func OuterHtml(s *Selection) (string, error) { 79 | var builder strings.Builder 80 | if err := Render(&builder, s); err != nil { 81 | return "", err 82 | } 83 | return builder.String(), nil 84 | } 85 | 86 | // Loop through all container nodes to search for the target node. 87 | func sliceContains(container []*html.Node, contained *html.Node) bool { 88 | for _, n := range container { 89 | if nodeContains(n, contained) { 90 | return true 91 | } 92 | } 93 | 94 | return false 95 | } 96 | 97 | // Checks if the contained node is within the container node. 98 | func nodeContains(container *html.Node, contained *html.Node) bool { 99 | // Check if the parent of the contained node is the container node, traversing 100 | // upward until the top is reached, or the container is found. 101 | for contained = contained.Parent; contained != nil; contained = contained.Parent { 102 | if container == contained { 103 | return true 104 | } 105 | } 106 | return false 107 | } 108 | 109 | // Checks if the target node is in the slice of nodes. 110 | func isInSlice(slice []*html.Node, node *html.Node) bool { 111 | return indexInSlice(slice, node) > -1 112 | } 113 | 114 | // Returns the index of the target node in the slice, or -1. 115 | func indexInSlice(slice []*html.Node, node *html.Node) int { 116 | if node != nil { 117 | for i, n := range slice { 118 | if n == node { 119 | return i 120 | } 121 | } 122 | } 123 | return -1 124 | } 125 | 126 | // Appends the new nodes to the target slice, making sure no duplicate is added. 127 | // There is no check to the original state of the target slice, so it may still 128 | // contain duplicates. The target slice is returned because append() may create 129 | // a new underlying array. If targetSet is nil, a local set is created with the 130 | // target if len(target) + len(nodes) is greater than minNodesForSet. 131 | func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node { 132 | // if there are not that many nodes, don't use the map, faster to just use nested loops 133 | // (unless a non-nil targetSet is passed, in which case the caller knows better). 134 | if targetSet == nil && len(target)+len(nodes) < minNodesForSet { 135 | for _, n := range nodes { 136 | if !isInSlice(target, n) { 137 | target = append(target, n) 138 | } 139 | } 140 | return target 141 | } 142 | 143 | // if a targetSet is passed, then assume it is reliable, otherwise create one 144 | // and initialize it with the current target contents. 145 | if targetSet == nil { 146 | targetSet = make(map[*html.Node]bool, len(target)) 147 | for _, n := range target { 148 | targetSet[n] = true 149 | } 150 | } 151 | for _, n := range nodes { 152 | if !targetSet[n] { 153 | target = append(target, n) 154 | targetSet[n] = true 155 | } 156 | } 157 | 158 | return target 159 | } 160 | 161 | // Loop through a selection, returning only those nodes that pass the predicate 162 | // function. 163 | func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) { 164 | for i, n := range sel.Nodes { 165 | if predicate(i, newSingleSelection(n, sel.document)) { 166 | result = append(result, n) 167 | } 168 | } 169 | return result 170 | } 171 | 172 | // Creates a new Selection object based on the specified nodes, and keeps the 173 | // source Selection object on the stack (linked list). 174 | func pushStack(fromSel *Selection, nodes []*html.Node) *Selection { 175 | result := &Selection{nodes, fromSel.document, fromSel} 176 | return result 177 | } 178 | -------------------------------------------------------------------------------- /utilities_test.go: -------------------------------------------------------------------------------- 1 | package goquery 2 | 3 | import ( 4 | "reflect" 5 | "sort" 6 | "strings" 7 | "testing" 8 | 9 | "golang.org/x/net/html" 10 | ) 11 | 12 | var allNodes = ` 13 | 14 | 15 | 16 | 17 | 18 |

19 | This is some text. 20 |

21 |
22 |

23 |

24 | 25 | ` 26 | 27 | func TestNodeName(t *testing.T) { 28 | doc, err := NewDocumentFromReader(strings.NewReader(allNodes)) 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | 33 | n0 := doc.Nodes[0] 34 | nDT := n0.FirstChild 35 | sMeta := doc.Find("meta") 36 | nMeta := sMeta.Get(0) 37 | sP := doc.Find("p") 38 | nP := sP.Get(0) 39 | nComment := nP.FirstChild 40 | nText := nComment.NextSibling 41 | 42 | cases := []struct { 43 | node *html.Node 44 | typ html.NodeType 45 | want string 46 | }{ 47 | {n0, html.DocumentNode, nodeNames[html.DocumentNode]}, 48 | {nDT, html.DoctypeNode, "html"}, 49 | {nMeta, html.ElementNode, "meta"}, 50 | {nP, html.ElementNode, "p"}, 51 | {nComment, html.CommentNode, nodeNames[html.CommentNode]}, 52 | {nText, html.TextNode, nodeNames[html.TextNode]}, 53 | } 54 | for i, c := range cases { 55 | got := NodeName(newSingleSelection(c.node, doc)) 56 | if c.node.Type != c.typ { 57 | t.Errorf("%d: want type %v, got %v", i, c.typ, c.node.Type) 58 | } 59 | if got != c.want { 60 | t.Errorf("%d: want %q, got %q", i, c.want, got) 61 | } 62 | } 63 | } 64 | 65 | func TestNodeNameMultiSel(t *testing.T) { 66 | doc, err := NewDocumentFromReader(strings.NewReader(allNodes)) 67 | if err != nil { 68 | t.Fatal(err) 69 | } 70 | 71 | in := []string{"p", "h1", "div"} 72 | var out []string 73 | doc.Find(strings.Join(in, ", ")).Each(func(i int, s *Selection) { 74 | got := NodeName(s) 75 | out = append(out, got) 76 | }) 77 | sort.Strings(in) 78 | sort.Strings(out) 79 | if !reflect.DeepEqual(in, out) { 80 | t.Errorf("want %v, got %v", in, out) 81 | } 82 | } 83 | 84 | func TestOuterHtml(t *testing.T) { 85 | doc, err := NewDocumentFromReader(strings.NewReader(allNodes)) 86 | if err != nil { 87 | t.Fatal(err) 88 | } 89 | 90 | n0 := doc.Nodes[0] 91 | nDT := n0.FirstChild 92 | sMeta := doc.Find("meta") 93 | sP := doc.Find("p") 94 | nP := sP.Get(0) 95 | nComment := nP.FirstChild 96 | nText := nComment.NextSibling 97 | sHeaders := doc.Find(".header") 98 | 99 | cases := []struct { 100 | node *html.Node 101 | sel *Selection 102 | want string 103 | }{ 104 | {nDT, nil, ""}, // render makes DOCTYPE all caps 105 | {nil, sMeta, ``}, // and auto-closes the meta 106 | {nil, sP, `

107 | This is some text. 108 |

`}, 109 | {nComment, nil, ""}, 110 | {nText, nil, ` 111 | This is some text. 112 | `}, 113 | {nil, sHeaders, `

`}, 114 | } 115 | for i, c := range cases { 116 | if c.sel == nil { 117 | c.sel = newSingleSelection(c.node, doc) 118 | } 119 | got, err := OuterHtml(c.sel) 120 | if err != nil { 121 | t.Fatal(err) 122 | } 123 | 124 | if got != c.want { 125 | t.Errorf("%d: want %q, got %q", i, c.want, got) 126 | } 127 | } 128 | } 129 | --------------------------------------------------------------------------------