Compare commits

..
Author SHA1 Message Date
Martin Angers 5d7b0271f0 add tests to confirm erroneous behaviour 2020-10-07 10:54:01 -04:00
thiemok d89086ead1 fix(manipulation): address issues from code review of #235
* SetHml remains unchanged, see comment on the isParent param of
eachNodeHtml
* Removed unused functions
* Added documentation on eachNodeHtml
* Fixed node parsing cache keys and added test
* Refactored some tests for clarity
2020-09-28 15:37:51 +02:00
David Wilkinsandthiemok 99c8f1ecd5 Fixes #178 - Send context to parseHtml 2020-09-25 18:45:19 +02:00
Martin AngersandGitHub c942deb265 Merge pull request #347 from AbhigyanKumar/patch-1
Go syntax added
2020-09-24 09:53:53 -04:00
Abhigyan KumarandGitHub 0132bb8147 Go syntax
Go syntax colouring added.
2020-09-24 19:10:37 +05:30
Martin Angers 6f0f9d6b87 Merge branch 'eclipseo-fix_conversion_int_to_string' 2020-08-01 19:32:31 -04:00
Martin Angers cd12820764 use strconv.Itoa instead of fmt.Sprintf 2020-08-01 19:32:09 -04:00
Robert-André Mauchin 51a0e1a4bb Convert int to string using fmt.Sprintf
See https://github.com/golang/go/issues/32479

Fix #341.

Signed-off-by: Robert-André Mauchin <zebob.m@gmail.com>
2020-07-31 23:58:32 +02:00
Martin AngersandGitHub 89946c829f Merge pull request #330 from foolin/master
add [Pagser] to Related Projects
2020-04-19 11:21:48 -04:00
foolin 8b2a6e25d5 fix an to a 2020-04-19 23:18:31 +08:00
foolin 538dd4a913 add [Pagser] to Related Projects 2020-04-19 23:04:27 +08:00
Martin Angers 844d6bd46b doc: clarify how RemoveFiltered works 2020-02-24 18:34:32 -05:00
Martin Angers 1b16b92ddf doc: add 1.5.1 2020-02-04 18:10:22 -05:00
Martin AngersandGitHub 3d11372b35 Merge pull request #321 from akkumar/gomodupgrade
feat: upgrade gomod
2020-02-04 18:08:31 -05:00
Karthik K bd23f0c6e0 feat: upgrade gomod 2020-02-04 13:00:44 +05:30
Martin Angers 56ba1e6f08 add Geziyor to related projects 2019-12-09 15:00:45 -05:00
Martin Angers b17a2bbb6d remove go1.1 from travis, consistently fails 2019-10-15 18:09:08 -04:00
Martin Angers c70bac62f5 add Go versions to travis 2019-10-15 18:07:18 -04:00
Martin AngersandGitHub 8674076365 Merge pull request #310 from theophanous/colly-readme-patch
Updated colly project reference in README.md
2019-10-15 18:05:36 -04:00
Peter TheophanousandGitHub 6dcff85b2e Updated colly project reference in README.md 2019-10-14 21:58:31 +01:00
Martin AngersandGitHub 3dcf72e6c1 Merge pull request #280 from slotix/master
README.md: Add "Dataflow kit" to related projects.
2019-01-09 18:07:04 -05:00
Dmitry NarizhnykhandGitHub 22fd749627 README.md: Add "Dataflow kit" to related projects. 2019-01-09 22:44:10 +01:00
Martin AngersandGitHub 9b813b2309 Merge pull request #277 from tauraamui/tauraamui-update-readme
Update README.md to include BerryCMS in related
2019-01-08 07:15:36 -05:00
Adam Prakash LewisandGitHub 0255f16068 Update README.md to include BerryCMS in related 2019-01-08 09:36:23 +00:00
Martin Angers 2d2796f417 doc: 1.5 release with go modules 2018-11-15 15:42:34 -05:00
Martin AngersandGitHub 80f0084f4c Merge pull request #267 from Zaba505/master
Added Go Module support
2018-11-15 15:40:51 -05:00
Carson Derr 68fe358134 Add Go Module support 2018-11-14 19:30:47 -05:00
Martin AngersandGitHub 2af3d16e2b Merge pull request #261 from msoap/patch-1
Added Go 1.11 on travis CI
2018-10-14 13:58:06 -04:00
Sergey MudrikandGitHub 6c2cec4567 Added Go 1.11 on travis CI 2018-10-14 20:07:07 +03:00
Martin Angers 8311f594d7 readme: add ferret in projects using goquery 2018-10-02 20:21:05 -04:00
Martin Angers dc2ec5c7ca release 1.4.1 2018-06-07 11:06:10 -04:00
Martin AngersandGitHub 7757e27950 add examples for NewDocumentFromReader (#254) 2018-06-07 11:04:59 -04:00
Martin Angers ea1bc64a63 fix example indentation 2018-03-24 12:22:12 -04:00
Martin Angers dd77530fdb update examples to use the preferred NewDocumentFromReader approach 2018-03-24 12:12:09 -04:00
Martin AngersandGitHub a86ea07301 Merge pull request #247 from PuerkitoBio/wip-deprecate
Deprecate NewDocument and NewDocumentFromResponse. Closes #173 .
2018-03-24 12:02:17 -04:00
Martin Angers b4912d23e5 document the new release 2018-03-24 11:52:59 -04:00
Martin Angers 106fcbf95a deprecate NewDocument and NewDocumentFromResponse 2018-03-24 11:50:21 -04:00
Martin AngersandGitHub 61aa1975b1 Merge pull request #243 from gnulnx/master
Added gnulnx/goperf to Related Projects
2018-03-13 18:36:54 -04:00
John Furr 7a8f63580d Added gnulnx/goperf to Related Projects 2018-03-13 16:31:29 -04:00
Martin Angers 6285ceb1e9 last attempt to get go1.1 with travis 2018-03-10 15:43:14 -05:00
Martin Angers b55d492104 travis go1.1 gets 1.7 for some reason 2018-03-10 15:37:49 -05:00
Martin Angers ef34a70bad add 1.10 in travis config 2018-03-10 15:30:08 -05:00
Martin AngersandGitHub 5819122076 add Support section to readme (#241) 2018-03-10 15:26:54 -05:00
Martin AngersandGitHub 09540e5659 Merge pull request #234 from PuerkitoBio/wip-slice-end
Add ToEnd constant to use as special end index in Slice, closes #232.
2018-01-28 14:56:50 -05:00
Martin Angers 46f28d87ad document addition of ToEnd 2018-01-28 14:43:53 -05:00
Martin Angers 520f19d599 add ToEnd marker to Slice until the end of the Selection 2018-01-28 14:41:06 -05:00
Martin AngersandGitHub 2324bda66f Merge pull request #231 from Strum355/patch-1
Removed unnecessary conditional check
2018-01-18 09:26:56 -05:00
Noah Santschi-CooneyandGitHub 3855699cc0 Removed unnecessary if
IsMatcher returns false if len(s.Nodes) !> 0, so Is was double checking if len(s.Nodes) > 0
2018-01-17 13:49:09 +00:00
Martin Angers a41cf096e6 update changelog 2018-01-11 08:54:59 -05:00
Martin Angers 7d9aa5c1e6 misc/git: remove excessively verbose lint tools 2018-01-11 08:49:46 -05:00
Martin Angers c56812c109 fix godoc comment to comply with golint 2018-01-11 08:48:49 -05:00
Martin AngersandGitHub cab5e2c9d6 Merge pull request #229 from davidjwilkins/feature-226
Fixes #226 - Added the AddBack, AddBackFiltered, and AddBackMatcher methods.
2018-01-11 08:45:45 -05:00
David Wilkins 802bece6d0 Move deprecated notice to end of comment 2018-01-10 21:36:54 -08:00
David Wilkins 499bd47957 Fixes #226 - Added the AddBack, AddBackFiltered, and AddBackMatcher methods. Changed AndSelf to simply be an alias for AddBack, and added a deprecation notice to AddBack (as with https://api.jquery.com/andself/) 2018-01-09 20:34:18 -08:00
Martin AngersandGitHub bc4e06eb07 Merge pull request #221 from y-yagi/patch-1
Add go1.9 to travis
2017-12-06 07:16:06 -05:00
Yuji YaginumaandGitHub d8ab9abbf4 Add go1.9 to travis 2017-12-06 18:46:13 +09:00
Martin AngersandGitHub 1a71cc719d Merge pull request #215 from kataras/patch-1
README.md: Add colly - Scraping Framework to the Related Projects
2017-10-07 10:12:28 -04:00
Gerasimos (Makis) MaropoulosandGitHub 4b3a6d701e README.md: Add colly - Scraping Framework to the Related Projects 2017-10-07 07:57:40 +03:00
Martin AngersandGitHub 63e7f870c2 Merge pull request #211 from 38elements/fix-typo
Fix typo
2017-09-29 06:56:09 -04:00
38elementsandGitHub 109d3a6a56 Fix typo 2017-09-29 18:51:07 +09:00
Martin Angers ce645ea5e6 use svg travis badge, master branch 2017-08-16 09:38:31 -04:00
Martin Angers 8806ada2a4 update README with "related projects" section instead of just "downstream" 2017-06-23 12:21:02 -04:00
Martin AngersandGitHub 2dc93891ab Merge pull request #183 from andrewstuart/master
Add goquery marshaling library as a downstream project in Readme
2017-05-20 15:45:20 -04:00
Andrew Stuart 4a7586a83f Update readme with some downstream projects 2017-05-15 16:49:20 -07:00
Martin Angers ed7d758e9a add 1.8, test on latest patches 2017-03-24 09:54:48 -04:00
Martin AngersandGitHub c641b87978 Merge pull request #162 from rohanpai/master
Added Sourcegraph Badge
2017-03-08 14:48:52 -05:00
Rohan PaiandGitHub 28076b7c97 Added Sourcegraph Badge 2017-03-08 11:27:24 -08:00
Martin Angers e1271ee34c Merge branch 'glebtv-setHtml' 2017-02-12 11:36:42 -05:00
Martin Angers f5c5de8530 update readme for new release 2017-02-12 11:36:10 -05:00
Martin Angers 46c32cef0e unexport SetNodes, add SetHtml tests 2017-02-12 11:34:10 -05:00
Gleb Tv 755fd592bb implement SetHtml and SetText, fixes #123 2017-02-08 16:27:36 +03:00
Martin AngersandGitHub 3cb3b86568 Update README.md 2016-12-29 22:41:54 -05:00
Martin AngersandGitHub 2ee2e49ac5 Merge pull request #142 from radovskyb/master
Optimizes the *Selection.Text method and reduces allocations.
2016-12-29 22:37:29 -05:00
Benjamin Radovsky c0b805c7dc greatly optimize the *Selection.Text method and highly reduce it's amount of allocations per op. 2016-12-27 17:37:31 +11:00
Martin Angers 152b1a2c8f remove wiki section on big docs, cascadia is now almost as fast as workarounds 2016-08-31 12:22:11 -04:00
Martin Angers 9043fe2599 add tip for large documents 2016-08-30 20:10:13 -04:00
Martin Angers 56a198aba8 add go1.7 to travis 2016-08-28 21:21:01 -04:00
Martin Angers 5dccaa115b optimize winnowNodes and ClosestNodes in the same way 2016-08-28 11:55:36 -04:00
Martin Angers 26f4f54c91 simplify string search expressions, remove unused code 2016-08-28 11:09:29 -04:00
Martin Angers e89fe653fd update README for v1.0.1 2016-08-28 11:06:06 -04:00
Martin Angers ab2bb46123 update pre-commit hook with other static checks 2016-08-28 11:04:26 -04:00
Martin Angers b91267330e add tests and benchmark for appendWithoutDuplicates optimization 2016-08-28 11:03:18 -04:00
Martin Angers d85ad7e2ed remove var declaration 2016-08-27 22:44:10 -04:00
Martin Angers 6841d28c6f comment and explain the appendWithoutDuplicates logic 2016-08-27 22:29:53 -04:00
Martin Angers 7bd630023a reuse the deduplication set map in mapNodes 2016-08-27 22:25:02 -04:00
Martin Angers c7533ba696 maintain a set of existing target nodes to deduplicate
The old implementation used nested loops to check if one of the
new nodes was already in the existing nodes. This commit
maintains a map[*html.Node]bool so that a single loop over
the new elements is required. This is slower for small
slices though, and allocates more. The next step is for
appendWithoutDuplicates to accept a map as argument so
that the set is not recreated on each call from mapNodes.
2016-08-27 18:55:21 -04:00
Martin Angers ad8b3d8222 remove logging from benchmarks, validate expected result instead 2016-08-27 18:37:55 -04:00
Martin Angers 7f4291566c tag v1.0.0, closes #124 2016-07-27 21:43:13 -04:00
Martin Angers f0d75731e0 fix example, closes #120 2016-06-29 08:59:28 -04:00
Martin AngersandGitHub 77cbaef46a Merge pull request #115 from PuerkitoBio/i114
Treat invalid selector strings as a Matcher that doesn't match anything
2016-06-15 09:48:05 -04:00
Martin Angers 2441483128 update doc 2016-06-15 09:40:19 -04:00
Martin Angers 2ab590df05 use a failing matcher if selector fails to compile, adjust tests 2016-06-15 09:27:17 -04:00
Martin Angers 9cc531274b implement tests for selection methods with invalid selector strings 2016-06-15 08:42:27 -04:00
Martin Angers 2cd8b4e49a test all go versions since 1.1 2016-06-15 07:50:27 -04:00
Martin Angers aeecb06b1e doc: better explain the arguments to the function passed to Each and Map, closes #107 2016-05-26 08:12:20 -04:00
Martin Angers 2e29ea41f0 add a test for issue #103 2016-04-08 07:41:18 -04:00
Martin Angers 5cc721859e edit example to follow Go conventions 2016-04-08 07:39:02 -04:00
Martin Angers 4126223a86 exclude testdata/ files from github language stats 2016-02-16 20:35:17 -05:00
Martin Angers 81e6517758 doc: fix list for godoc 2016-02-02 12:40:27 -05:00
Martin Angers bb16855614 Merge pull request #102 from PuerkitoBio/helper-funcs
add NodeName and OuterHtml utility functions, closes #96 and #101 .
2016-02-02 12:37:00 -05:00
41 changed files with 1648 additions and 280 deletions
+1
View File
@@ -0,0 +1 @@
testdata/* linguist-vendored
+10 -1
View File
@@ -1,7 +1,16 @@
# editor temporary files
*.sublime-*
.DS_Store
*.swp
#*.*#
tags
goquery.test
# direnv config
.env*
# test binaries
*.test
# coverage and profilte outputs
*.out
+13 -3
View File
@@ -1,7 +1,17 @@
language: go
go:
- 1.1
- 1.2
- 1.3
- 1.2.x
- 1.3.x
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- 1.10.x
- 1.11.x
- 1.12.x
- 1.13.x
- tip
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) 2012-2014, Martin Angers & Contributors
Copyright (c) 2012-2016, Martin Angers & Contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+78 -10
View File
@@ -1,8 +1,5 @@
# goquery - a little like that j-thing, only in Go
[![build status](https://secure.travis-ci.org/PuerkitoBio/goquery.png)](http://travis-ci.org/PuerkitoBio/goquery)
[![GoDoc](https://godoc.org/github.com/PuerkitoBio/goquery?status.png)](http://godoc.org/github.com/PuerkitoBio/goquery)
[![build status](https://secure.travis-ci.org/PuerkitoBio/goquery.svg?branch=master)](http://travis-ci.org/PuerkitoBio/goquery) [![GoDoc](https://godoc.org/github.com/PuerkitoBio/goquery?status.png)](http://godoc.org/github.com/PuerkitoBio/goquery) [![Sourcegraph Badge](https://sourcegraph.com/github.com/PuerkitoBio/goquery/-/badge.svg)](https://sourcegraph.com/github.com/PuerkitoBio/goquery?badge)
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.
@@ -10,6 +7,16 @@ Also, because the net/html parser requires UTF-8 encoding, so does goquery: it i
Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...).
## Table of Contents
* [Installation](#installation)
* [Changelog](#changelog)
* [API](#api)
* [Examples](#examples)
* [Related Projects](#related-projects)
* [Support](#support)
* [License](#license)
## Installation
Please note that because of the net/html dependency, goquery requires Go1.1+.
@@ -17,7 +24,7 @@ Please note that because of the net/html dependency, goquery requires Go1.1+.
$ go get github.com/PuerkitoBio/goquery
(optional) To run unit tests:
$ cd $GOPATH/src/github.com/PuerkitoBio/goquery
$ go test
@@ -30,6 +37,17 @@ Please note that because of the net/html dependency, goquery requires Go1.1+.
**Note that goquery's API is now stable, and will not break.**
* **2020-02-04 (v1.5.1)** : Update module dependencies.
* **2018-11-15 (v1.5.0)** : Go module support (thanks @Zaba505).
* **2018-06-07 (v1.4.1)** : Add `NewDocumentFromReader` examples.
* **2018-03-24 (v1.4.0)** : Deprecate `NewDocument(url)` and `NewDocumentFromResponse(response)`.
* **2018-01-28 (v1.3.0)** : Add `ToEnd` constant to `Slice` until the end of the selection (thanks to @davidjwilkins for raising the issue).
* **2018-01-11 (v1.2.0)** : Add `AddBack*` and deprecate `AndSelf` (thanks to @davidjwilkins).
* **2017-02-12 (v1.1.0)** : Add `SetHtml` and `SetText` (thanks to @glebtv).
* **2016-12-29 (v1.0.2)** : Optimize allocations for `Selection.Text` (thanks to @radovskyb).
* **2016-08-28 (v1.0.1)** : Optimize performance for large documents.
* **2016-07-27 (v1.0.0)** : Tag version 1.0.0.
* **2016-06-15** : Invalid selector strings internally compile to a `Matcher` implementation that never matches any node (instead of a panic). So for example, `doc.Find("~")` returns an empty `*Selection` object.
* **2016-02-02** : Add `NodeName` utility function similar to the DOM's `nodeName` property. It returns the tag name of the first element in a selection, and other relevant values of non-element nodes (see godoc for details). Add `OuterHtml` utility function similar to the DOM's `outerHTML` property (named `OuterHtml` in small caps for consistency with the existing `Html` method on the `Selection`).
* **2015-04-20** : Add `AttrOr` helper method to return the attribute's value or a default value if absent. Thanks to [piotrkowalczuk][piotr].
* **2015-02-04** : Add more manipulation functions - Prepend* - thanks again to [Andrew Stone][thatguystone].
@@ -61,13 +79,18 @@ Utility functions that are not in jQuery but are useful in Go are implemented as
The complete [godoc reference documentation can be found here][doc].
Please note that Cascadia's selectors do not necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details.
Please note that Cascadia's selectors do not necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details. Invalid selector strings compile to a `Matcher` that fails to match any node. Behaviour of the various functions that take a selector string as argument follows from that fact, e.g. (where `~` is an invalid selector string):
* `Find("~")` returns an empty selection because the selector string doesn't match anything.
* `Add("~")` returns a new selection that holds the same nodes as the original selection, because it didn't add any node (selector string didn't match anything).
* `ParentsFiltered("~")` returns an empty selection because the selector string doesn't match anything.
* `ParentsUntil("~")` returns all parents of the selection because the selector string didn't match any element to stop before the top element.
## Examples
See some tips and tricks in the [wiki][].
Taken (and adapted as if executed from outside the goquery package) from example_test.go:
Adapted from example_test.go:
```Go
package main
@@ -75,18 +98,32 @@ package main
import (
"fmt"
"log"
"net/http"
"github.com/PuerkitoBio/goquery"
)
func ExampleScrape() {
doc, err := goquery.NewDocument("http://metalsucks.net")
// Request the HTML page.
res, err := http.Get("http://metalsucks.net")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
}
// Load the HTML document
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatal(err)
}
doc.Find(".reviews-wrap article .review-rhs").Each(func(i int, s *goquery.Selection) {
band := s.Find("h3").Text()
// Find the review items
doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) {
// For each item found, get the band and title
band := s.Find("a").Text()
title := s.Find("i").Text()
fmt.Printf("Review %d: %s - %s\n", i, band, title)
})
@@ -97,6 +134,35 @@ func main() {
}
```
## Related Projects
- [Goq][goq], an HTML deserialization and scraping library based on goquery and struct tags.
- [andybalholm/cascadia][cascadia], the CSS selector library used by goquery.
- [suntong/cascadia][cascadiacli], a command-line interface to the cascadia CSS selector library, useful to test selectors.
- [gocolly/colly](https://github.com/gocolly/colly), a lightning fast and elegant Scraping Framework
- [gnulnx/goperf](https://github.com/gnulnx/goperf), a website performance test tool that also fetches static assets.
- [MontFerret/ferret](https://github.com/MontFerret/ferret), declarative web scraping.
- [tacusci/berrycms](https://github.com/tacusci/berrycms), a modern simple to use CMS with easy to write plugins
- [Dataflow kit](https://github.com/slotix/dataflowkit), Web Scraping framework for Gophers.
- [Geziyor](https://github.com/geziyor/geziyor), a fast web crawling & scraping framework for Go. Supports JS rendering.
- [Pagser](https://github.com/foolin/pagser), a simple, easy, extensible, configurable HTML parser to struct based on goquery and struct tags.
## Support
There are a number of ways you can support the project:
* Use it, star it, build something with it, spread the word!
- If you do build something open-source or otherwise publicly-visible, let me know so I can add it to the [Related Projects](#related-projects) section!
* Raise issues to improve the project (note: doc typos and clarifications are issues too!)
- Please search existing issues before opening a new one - it may have already been adressed.
* Pull requests: please discuss new code in an issue first, unless the fix is really trivial.
- Make sure new code is tested.
- Be mindful of existing code - PRs that break existing code have a high probability of being declined, unless it fixes a serious issue.
If you desperately want to send money my way, I have a BuyMeACoffee.com page:
<a href="https://www.buymeacoffee.com/mna" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>
## License
The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia's license is [here][caslic].
@@ -104,6 +170,7 @@ The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia'
[jquery]: http://jquery.com/
[go]: http://golang.org/
[cascadia]: https://github.com/andybalholm/cascadia
[cascadiacli]: https://github.com/suntong/cascadia
[bsd]: http://opensource.org/licenses/BSD-3-Clause
[golic]: http://golang.org/LICENSE
[caslic]: https://github.com/andybalholm/cascadia/blob/master/LICENSE
@@ -114,3 +181,4 @@ The [BSD 3-Clause license][bsd], the same as the [Go language][golic]. Cascadia'
[wiki]: https://github.com/PuerkitoBio/goquery/wiki/Tips-and-tricks
[thatguystone]: https://github.com/thatguystone
[piotr]: https://github.com/piotrkowalczuk
[goq]: https://github.com/andrewstuart/goq
+23 -2
View File
@@ -4,6 +4,16 @@ import (
"golang.org/x/net/html"
)
const (
maxUint = ^uint(0)
maxInt = int(maxUint >> 1)
// ToEnd is a special index value that can be used as end index in a call
// to Slice so that all elements are selected until the end of the Selection.
// It is equivalent to passing (*Selection).Length().
ToEnd = maxInt
)
// First reduces the set of matched elements to the first in the set.
// It returns a new Selection object, and an empty Selection object if the
// the selection is empty.
@@ -35,12 +45,23 @@ func (s *Selection) Eq(index int) *Selection {
}
// Slice reduces the set of matched elements to a subset specified by a range
// of indices.
// of indices. The start index is 0-based and indicates the index of the first
// element to select. The end index is 0-based and indicates the index at which
// the elements stop being selected (the end index is not selected).
//
// The indices may be negative, in which case they represent an offset from the
// end of the selection.
//
// The special value ToEnd may be specified as end index, in which case all elements
// until the end are selected. This works both for a positive and negative start
// index.
func (s *Selection) Slice(start, end int) *Selection {
if start < 0 {
start += len(s.Nodes)
}
if end < 0 {
if end == ToEnd {
end = len(s.Nodes)
} else if end < 0 {
end += len(s.Nodes)
}
return pushStack(s, s.Nodes[start:end])
+54
View File
@@ -14,6 +14,11 @@ func TestFirstEmpty(t *testing.T) {
assertLength(t, sel.Nodes, 0)
}
func TestFirstInvalid(t *testing.T) {
sel := Doc().Find("").First()
assertLength(t, sel.Nodes, 0)
}
func TestFirstRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.First().End()
@@ -36,6 +41,11 @@ func TestLastEmpty(t *testing.T) {
assertLength(t, sel.Nodes, 0)
}
func TestLastInvalid(t *testing.T) {
sel := Doc().Find("").Last()
assertLength(t, sel.Nodes, 0)
}
func TestLastRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Last().End()
@@ -63,6 +73,11 @@ func TestEqEmpty(t *testing.T) {
assertLength(t, sel.Nodes, 0)
}
func TestEqInvalid(t *testing.T) {
sel := Doc().Find("").Eq(0)
assertLength(t, sel.Nodes, 0)
}
func TestEqInvalidPositive(t *testing.T) {
sel := Doc().Find(".pvk-content").Eq(3)
assertLength(t, sel.Nodes, 0)
@@ -83,6 +98,32 @@ func TestSlice(t *testing.T) {
sel := Doc().Find(".pvk-content").Slice(0, 2)
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel, "#pc1", "#pc2")
}
func TestSliceToEnd(t *testing.T) {
sel := Doc().Find(".pvk-content").Slice(1, ToEnd)
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel.Eq(0), "#pc2")
if _, ok := sel.Eq(1).Attr("id"); ok {
t.Error("Want no attribute ID, got one")
}
}
func TestSliceEmpty(t *testing.T) {
defer assertPanic(t)
Doc().Find("x").Slice(0, 2)
}
func TestSliceInvalid(t *testing.T) {
defer assertPanic(t)
Doc().Find("").Slice(0, 2)
}
func TestSliceInvalidToEnd(t *testing.T) {
defer assertPanic(t)
Doc().Find("").Slice(2, ToEnd)
}
func TestSliceOutOfBounds(t *testing.T) {
@@ -110,6 +151,12 @@ func TestNegativeSliceBoth(t *testing.T) {
assertSelectionIs(t, sel.Eq(1), "#cf3")
}
func TestNegativeSliceToEnd(t *testing.T) {
sel := Doc().Find(".container-fluid").Slice(-3, ToEnd)
assertLength(t, sel.Nodes, 3)
assertSelectionIs(t, sel, "#cf2", "#cf3", "#cf4")
}
func TestNegativeSliceOutOfBounds(t *testing.T) {
defer assertPanic(t)
Doc().Find(".container-fluid").Slice(-12, -7)
@@ -157,6 +204,13 @@ func TestIndexSelector(t *testing.T) {
}
}
func TestIndexSelectorInvalid(t *testing.T) {
sel := Doc().Find(".hero-unit")
if i := sel.IndexSelector(""); i != -1 {
t.Errorf("Expected index of -1, got %v.", i)
}
}
func TestIndexOfNode(t *testing.T) {
sel := Doc().Find("div.pvk-gutter")
if i := sel.IndexOfNode(sel.Nodes[1]); i != 1 {
+85
View File
@@ -0,0 +1,85 @@
BenchmarkFirst-4 30000000 50.7 ns/op 48 B/op 1 allocs/op
BenchmarkLast-4 30000000 50.9 ns/op 48 B/op 1 allocs/op
BenchmarkEq-4 30000000 55.7 ns/op 48 B/op 1 allocs/op
BenchmarkSlice-4 500000000 3.45 ns/op 0 B/op 0 allocs/op
BenchmarkGet-4 2000000000 1.68 ns/op 0 B/op 0 allocs/op
BenchmarkIndex-4 3000000 541 ns/op 248 B/op 10 allocs/op
BenchmarkIndexSelector-4 200000 10749 ns/op 2464 B/op 17 allocs/op
BenchmarkIndexOfNode-4 200000000 6.47 ns/op 0 B/op 0 allocs/op
BenchmarkIndexOfSelection-4 200000000 7.27 ns/op 0 B/op 0 allocs/op
BenchmarkMetalReviewExample-4 10000 138426 ns/op 12240 B/op 319 allocs/op
BenchmarkAdd-4 200000 10192 ns/op 208 B/op 9 allocs/op
BenchmarkAddSelection-4 10000000 158 ns/op 48 B/op 1 allocs/op
BenchmarkAddNodes-4 10000000 156 ns/op 48 B/op 1 allocs/op
BenchmarkAndSelf-4 1000000 1588 ns/op 1008 B/op 5 allocs/op
BenchmarkFilter-4 100000 20427 ns/op 360 B/op 8 allocs/op
BenchmarkNot-4 100000 23508 ns/op 136 B/op 5 allocs/op
BenchmarkFilterFunction-4 50000 34178 ns/op 22976 B/op 755 allocs/op
BenchmarkNotFunction-4 50000 38173 ns/op 29120 B/op 757 allocs/op
BenchmarkFilterNodes-4 50000 34001 ns/op 20960 B/op 749 allocs/op
BenchmarkNotNodes-4 30000 40344 ns/op 29120 B/op 757 allocs/op
BenchmarkFilterSelection-4 50000 33308 ns/op 20960 B/op 749 allocs/op
BenchmarkNotSelection-4 30000 40748 ns/op 29120 B/op 757 allocs/op
BenchmarkHas-4 5000 263346 ns/op 1816 B/op 48 allocs/op
BenchmarkHasNodes-4 10000 160840 ns/op 21184 B/op 752 allocs/op
BenchmarkHasSelection-4 10000 165410 ns/op 21184 B/op 752 allocs/op
BenchmarkEnd-4 2000000000 1.01 ns/op 0 B/op 0 allocs/op
BenchmarkEach-4 300000 4664 ns/op 3304 B/op 118 allocs/op
BenchmarkMap-4 200000 8286 ns/op 5572 B/op 184 allocs/op
BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op
BenchmarkAttr-4 100000000 21.6 ns/op 0 B/op 0 allocs/op
BenchmarkText-4 200000 8909 ns/op 7536 B/op 110 allocs/op
BenchmarkLength-4 2000000000 0.34 ns/op 0 B/op 0 allocs/op
BenchmarkHtml-4 3000000 422 ns/op 120 B/op 2 allocs/op
BenchmarkIs-4 100000 22615 ns/op 88 B/op 4 allocs/op
BenchmarkIsPositional-4 50000 26655 ns/op 1112 B/op 10 allocs/op
BenchmarkIsFunction-4 1000000 1208 ns/op 784 B/op 28 allocs/op
BenchmarkIsSelection-4 50000 33497 ns/op 20960 B/op 749 allocs/op
BenchmarkIsNodes-4 50000 33572 ns/op 20960 B/op 749 allocs/op
BenchmarkHasClass-4 10000 232802 ns/op 14944 B/op 976 allocs/op
BenchmarkContains-4 200000000 7.33 ns/op 0 B/op 0 allocs/op
BenchmarkFind-4 200000 10715 ns/op 2464 B/op 17 allocs/op
BenchmarkFindWithinSelection-4 50000 35878 ns/op 2176 B/op 78 allocs/op
BenchmarkFindSelection-4 10000 194356 ns/op 2672 B/op 82 allocs/op
BenchmarkFindNodes-4 10000 195510 ns/op 2672 B/op 82 allocs/op
BenchmarkContents-4 1000000 2252 ns/op 864 B/op 34 allocs/op
BenchmarkContentsFiltered-4 500000 3015 ns/op 1016 B/op 39 allocs/op
BenchmarkChildren-4 5000000 364 ns/op 152 B/op 7 allocs/op
BenchmarkChildrenFiltered-4 1000000 2212 ns/op 352 B/op 15 allocs/op
BenchmarkParent-4 50000 24643 ns/op 4048 B/op 381 allocs/op
BenchmarkParentFiltered-4 50000 25967 ns/op 4248 B/op 388 allocs/op
BenchmarkParents-4 30000 50000 ns/op 27776 B/op 830 allocs/op
BenchmarkParentsFiltered-4 30000 53107 ns/op 28360 B/op 838 allocs/op
BenchmarkParentsUntil-4 100000 22423 ns/op 10352 B/op 353 allocs/op
BenchmarkParentsUntilSelection-4 20000 86925 ns/op 51144 B/op 1516 allocs/op
BenchmarkParentsUntilNodes-4 20000 87597 ns/op 51144 B/op 1516 allocs/op
BenchmarkParentsFilteredUntil-4 300000 5568 ns/op 2232 B/op 86 allocs/op
BenchmarkParentsFilteredUntilSelection-4 200000 10966 ns/op 5440 B/op 190 allocs/op
BenchmarkParentsFilteredUntilNodes-4 200000 10919 ns/op 5440 B/op 190 allocs/op
BenchmarkSiblings-4 30000 46018 ns/op 15400 B/op 204 allocs/op
BenchmarkSiblingsFiltered-4 30000 50566 ns/op 16496 B/op 213 allocs/op
BenchmarkNext-4 200000 7921 ns/op 3216 B/op 112 allocs/op
BenchmarkNextFiltered-4 200000 8804 ns/op 3416 B/op 118 allocs/op
BenchmarkNextAll-4 50000 31098 ns/op 9912 B/op 138 allocs/op
BenchmarkNextAllFiltered-4 50000 34677 ns/op 11008 B/op 147 allocs/op
BenchmarkPrev-4 200000 7920 ns/op 3216 B/op 112 allocs/op
BenchmarkPrevFiltered-4 200000 8913 ns/op 3416 B/op 118 allocs/op
BenchmarkPrevAll-4 200000 10845 ns/op 4376 B/op 113 allocs/op
BenchmarkPrevAllFiltered-4 100000 12030 ns/op 4576 B/op 119 allocs/op
BenchmarkNextUntil-4 100000 19193 ns/op 5760 B/op 260 allocs/op
BenchmarkNextUntilSelection-4 50000 34829 ns/op 18480 B/op 542 allocs/op
BenchmarkNextUntilNodes-4 100000 14459 ns/op 7944 B/op 248 allocs/op
BenchmarkPrevUntil-4 20000 66296 ns/op 12856 B/op 448 allocs/op
BenchmarkPrevUntilSelection-4 30000 45037 ns/op 23432 B/op 689 allocs/op
BenchmarkPrevUntilNodes-4 200000 11525 ns/op 6152 B/op 203 allocs/op
BenchmarkNextFilteredUntil-4 100000 12940 ns/op 4512 B/op 173 allocs/op
BenchmarkNextFilteredUntilSelection-4 50000 38924 ns/op 19160 B/op 567 allocs/op
BenchmarkNextFilteredUntilNodes-4 50000 38528 ns/op 19160 B/op 567 allocs/op
BenchmarkPrevFilteredUntil-4 100000 12980 ns/op 4664 B/op 175 allocs/op
BenchmarkPrevFilteredUntilSelection-4 50000 39671 ns/op 19936 B/op 587 allocs/op
BenchmarkPrevFilteredUntilNodes-4 50000 39484 ns/op 19936 B/op 587 allocs/op
BenchmarkClosest-4 500000 3310 ns/op 160 B/op 8 allocs/op
BenchmarkClosestSelection-4 5000000 361 ns/op 96 B/op 6 allocs/op
BenchmarkClosestNodes-4 5000000 359 ns/op 96 B/op 6 allocs/op
PASS
ok github.com/PuerkitoBio/goquery 163.718s
+85
View File
@@ -0,0 +1,85 @@
BenchmarkFirst-4 30000000 50.9 ns/op 48 B/op 1 allocs/op
BenchmarkLast-4 30000000 50.0 ns/op 48 B/op 1 allocs/op
BenchmarkEq-4 30000000 50.5 ns/op 48 B/op 1 allocs/op
BenchmarkSlice-4 500000000 3.53 ns/op 0 B/op 0 allocs/op
BenchmarkGet-4 2000000000 1.66 ns/op 0 B/op 0 allocs/op
BenchmarkIndex-4 2000000 832 ns/op 248 B/op 10 allocs/op
BenchmarkIndexSelector-4 100000 16073 ns/op 3839 B/op 21 allocs/op
BenchmarkIndexOfNode-4 200000000 6.38 ns/op 0 B/op 0 allocs/op
BenchmarkIndexOfSelection-4 200000000 7.14 ns/op 0 B/op 0 allocs/op
BenchmarkMetalReviewExample-4 10000 140737 ns/op 12418 B/op 320 allocs/op
BenchmarkAdd-4 100000 13162 ns/op 974 B/op 10 allocs/op
BenchmarkAddSelection-4 500000 3160 ns/op 814 B/op 2 allocs/op
BenchmarkAddNodes-4 500000 3159 ns/op 814 B/op 2 allocs/op
BenchmarkAndSelf-4 200000 7423 ns/op 2404 B/op 9 allocs/op
BenchmarkFilter-4 100000 19671 ns/op 360 B/op 8 allocs/op
BenchmarkNot-4 100000 22577 ns/op 136 B/op 5 allocs/op
BenchmarkFilterFunction-4 50000 33960 ns/op 22976 B/op 755 allocs/op
BenchmarkNotFunction-4 50000 37909 ns/op 29120 B/op 757 allocs/op
BenchmarkFilterNodes-4 50000 34196 ns/op 20960 B/op 749 allocs/op
BenchmarkNotNodes-4 30000 40446 ns/op 29120 B/op 757 allocs/op
BenchmarkFilterSelection-4 50000 33091 ns/op 20960 B/op 749 allocs/op
BenchmarkNotSelection-4 30000 40609 ns/op 29120 B/op 757 allocs/op
BenchmarkHas-4 5000 262936 ns/op 2371 B/op 50 allocs/op
BenchmarkHasNodes-4 10000 148631 ns/op 21184 B/op 752 allocs/op
BenchmarkHasSelection-4 10000 153117 ns/op 21184 B/op 752 allocs/op
BenchmarkEnd-4 2000000000 1.02 ns/op 0 B/op 0 allocs/op
BenchmarkEach-4 300000 4653 ns/op 3304 B/op 118 allocs/op
BenchmarkMap-4 200000 8257 ns/op 5572 B/op 184 allocs/op
BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op
BenchmarkAttr-4 100000000 22.0 ns/op 0 B/op 0 allocs/op
BenchmarkText-4 200000 8913 ns/op 7536 B/op 110 allocs/op
BenchmarkLength-4 2000000000 0.35 ns/op 0 B/op 0 allocs/op
BenchmarkHtml-4 5000000 398 ns/op 120 B/op 2 allocs/op
BenchmarkIs-4 100000 22392 ns/op 88 B/op 4 allocs/op
BenchmarkIsPositional-4 50000 26259 ns/op 1112 B/op 10 allocs/op
BenchmarkIsFunction-4 1000000 1212 ns/op 784 B/op 28 allocs/op
BenchmarkIsSelection-4 50000 33222 ns/op 20960 B/op 749 allocs/op
BenchmarkIsNodes-4 50000 33408 ns/op 20960 B/op 749 allocs/op
BenchmarkHasClass-4 10000 233208 ns/op 14944 B/op 976 allocs/op
BenchmarkContains-4 200000000 7.57 ns/op 0 B/op 0 allocs/op
BenchmarkFind-4 100000 16121 ns/op 3839 B/op 21 allocs/op
BenchmarkFindWithinSelection-4 20000 68019 ns/op 11521 B/op 97 allocs/op
BenchmarkFindSelection-4 5000 387582 ns/op 59787 B/op 176 allocs/op
BenchmarkFindNodes-4 5000 389246 ns/op 59797 B/op 176 allocs/op
BenchmarkContents-4 200000 11475 ns/op 2878 B/op 42 allocs/op
BenchmarkContentsFiltered-4 200000 11222 ns/op 2498 B/op 46 allocs/op
BenchmarkChildren-4 2000000 650 ns/op 152 B/op 7 allocs/op
BenchmarkChildrenFiltered-4 500000 2568 ns/op 352 B/op 15 allocs/op
BenchmarkParent-4 2000 702513 ns/op 194478 B/op 828 allocs/op
BenchmarkParentFiltered-4 2000 690778 ns/op 194658 B/op 835 allocs/op
BenchmarkParents-4 10000 124855 ns/op 49869 B/op 868 allocs/op
BenchmarkParentsFiltered-4 10000 128535 ns/op 50456 B/op 876 allocs/op
BenchmarkParentsUntil-4 20000 72982 ns/op 23802 B/op 388 allocs/op
BenchmarkParentsUntilSelection-4 10000 156099 ns/op 72453 B/op 1549 allocs/op
BenchmarkParentsUntilNodes-4 10000 156610 ns/op 72455 B/op 1549 allocs/op
BenchmarkParentsFilteredUntil-4 100000 15549 ns/op 4068 B/op 94 allocs/op
BenchmarkParentsFilteredUntilSelection-4 100000 20564 ns/op 7276 B/op 198 allocs/op
BenchmarkParentsFilteredUntilNodes-4 100000 20635 ns/op 7276 B/op 198 allocs/op
BenchmarkSiblings-4 3000 565114 ns/op 205910 B/op 336 allocs/op
BenchmarkSiblingsFiltered-4 3000 580264 ns/op 206993 B/op 345 allocs/op
BenchmarkNext-4 20000 93177 ns/op 26810 B/op 169 allocs/op
BenchmarkNextFiltered-4 20000 94171 ns/op 27013 B/op 175 allocs/op
BenchmarkNextAll-4 5000 270320 ns/op 89289 B/op 237 allocs/op
BenchmarkNextAllFiltered-4 5000 275283 ns/op 90375 B/op 246 allocs/op
BenchmarkPrev-4 20000 92777 ns/op 26810 B/op 169 allocs/op
BenchmarkPrevFiltered-4 20000 95577 ns/op 27007 B/op 175 allocs/op
BenchmarkPrevAll-4 20000 86339 ns/op 27515 B/op 151 allocs/op
BenchmarkPrevAllFiltered-4 20000 87759 ns/op 27715 B/op 157 allocs/op
BenchmarkNextUntil-4 10000 163930 ns/op 48541 B/op 330 allocs/op
BenchmarkNextUntilSelection-4 30000 56382 ns/op 23880 B/op 556 allocs/op
BenchmarkNextUntilNodes-4 100000 18883 ns/op 8703 B/op 252 allocs/op
BenchmarkPrevUntil-4 3000 484668 ns/op 145402 B/op 611 allocs/op
BenchmarkPrevUntilSelection-4 20000 72125 ns/op 28865 B/op 705 allocs/op
BenchmarkPrevUntilNodes-4 100000 14722 ns/op 6510 B/op 205 allocs/op
BenchmarkNextFilteredUntil-4 50000 39006 ns/op 10990 B/op 192 allocs/op
BenchmarkNextFilteredUntilSelection-4 20000 66048 ns/op 25641 B/op 586 allocs/op
BenchmarkNextFilteredUntilNodes-4 20000 65314 ns/op 25640 B/op 586 allocs/op
BenchmarkPrevFilteredUntil-4 50000 33312 ns/op 9709 B/op 189 allocs/op
BenchmarkPrevFilteredUntilSelection-4 20000 64197 ns/op 24981 B/op 601 allocs/op
BenchmarkPrevFilteredUntilNodes-4 20000 64505 ns/op 24982 B/op 601 allocs/op
BenchmarkClosest-4 500000 4065 ns/op 160 B/op 8 allocs/op
BenchmarkClosestSelection-4 2000000 756 ns/op 96 B/op 6 allocs/op
BenchmarkClosestNodes-4 2000000 753 ns/op 96 B/op 6 allocs/op
PASS
ok github.com/PuerkitoBio/goquery 162.053s
+85
View File
@@ -0,0 +1,85 @@
BenchmarkFirst-4 30000000 51.8 ns/op 48 B/op 1 allocs/op
BenchmarkLast-4 30000000 50.1 ns/op 48 B/op 1 allocs/op
BenchmarkEq-4 30000000 51.4 ns/op 48 B/op 1 allocs/op
BenchmarkSlice-4 500000000 3.52 ns/op 0 B/op 0 allocs/op
BenchmarkGet-4 2000000000 1.65 ns/op 0 B/op 0 allocs/op
BenchmarkIndex-4 2000000 787 ns/op 248 B/op 10 allocs/op
BenchmarkIndexSelector-4 100000 16952 ns/op 3839 B/op 21 allocs/op
BenchmarkIndexOfNode-4 200000000 6.42 ns/op 0 B/op 0 allocs/op
BenchmarkIndexOfSelection-4 200000000 7.12 ns/op 0 B/op 0 allocs/op
BenchmarkMetalReviewExample-4 10000 141994 ns/op 12418 B/op 320 allocs/op
BenchmarkAdd-4 200000 10367 ns/op 208 B/op 9 allocs/op
BenchmarkAddSelection-4 10000000 152 ns/op 48 B/op 1 allocs/op
BenchmarkAddNodes-4 10000000 147 ns/op 48 B/op 1 allocs/op
BenchmarkAndSelf-4 1000000 1647 ns/op 1008 B/op 5 allocs/op
BenchmarkFilter-4 100000 19522 ns/op 360 B/op 8 allocs/op
BenchmarkNot-4 100000 22546 ns/op 136 B/op 5 allocs/op
BenchmarkFilterFunction-4 50000 35087 ns/op 22976 B/op 755 allocs/op
BenchmarkNotFunction-4 50000 39123 ns/op 29120 B/op 757 allocs/op
BenchmarkFilterNodes-4 50000 34890 ns/op 20960 B/op 749 allocs/op
BenchmarkNotNodes-4 30000 41145 ns/op 29120 B/op 757 allocs/op
BenchmarkFilterSelection-4 50000 33735 ns/op 20960 B/op 749 allocs/op
BenchmarkNotSelection-4 30000 41334 ns/op 29120 B/op 757 allocs/op
BenchmarkHas-4 5000 264058 ns/op 2370 B/op 50 allocs/op
BenchmarkHasNodes-4 10000 151718 ns/op 21184 B/op 752 allocs/op
BenchmarkHasSelection-4 10000 156955 ns/op 21184 B/op 752 allocs/op
BenchmarkEnd-4 2000000000 1.01 ns/op 0 B/op 0 allocs/op
BenchmarkEach-4 300000 4660 ns/op 3304 B/op 118 allocs/op
BenchmarkMap-4 200000 8404 ns/op 5572 B/op 184 allocs/op
BenchmarkEachWithBreak-4 2000000 806 ns/op 560 B/op 20 allocs/op
BenchmarkAttr-4 100000000 21.6 ns/op 0 B/op 0 allocs/op
BenchmarkText-4 200000 8911 ns/op 7536 B/op 110 allocs/op
BenchmarkLength-4 2000000000 0.34 ns/op 0 B/op 0 allocs/op
BenchmarkHtml-4 3000000 405 ns/op 120 B/op 2 allocs/op
BenchmarkIs-4 100000 22228 ns/op 88 B/op 4 allocs/op
BenchmarkIsPositional-4 50000 26469 ns/op 1112 B/op 10 allocs/op
BenchmarkIsFunction-4 1000000 1240 ns/op 784 B/op 28 allocs/op
BenchmarkIsSelection-4 50000 33709 ns/op 20960 B/op 749 allocs/op
BenchmarkIsNodes-4 50000 33711 ns/op 20960 B/op 749 allocs/op
BenchmarkHasClass-4 10000 236005 ns/op 14944 B/op 976 allocs/op
BenchmarkContains-4 200000000 7.47 ns/op 0 B/op 0 allocs/op
BenchmarkFind-4 100000 16075 ns/op 3839 B/op 21 allocs/op
BenchmarkFindWithinSelection-4 30000 41418 ns/op 3539 B/op 82 allocs/op
BenchmarkFindSelection-4 10000 209490 ns/op 5616 B/op 89 allocs/op
BenchmarkFindNodes-4 10000 208206 ns/op 5614 B/op 89 allocs/op
BenchmarkContents-4 300000 4751 ns/op 1420 B/op 36 allocs/op
BenchmarkContentsFiltered-4 300000 5454 ns/op 1570 B/op 41 allocs/op
BenchmarkChildren-4 3000000 527 ns/op 152 B/op 7 allocs/op
BenchmarkChildrenFiltered-4 1000000 2484 ns/op 352 B/op 15 allocs/op
BenchmarkParent-4 50000 34724 ns/op 6940 B/op 387 allocs/op
BenchmarkParentFiltered-4 50000 35596 ns/op 7141 B/op 394 allocs/op
BenchmarkParents-4 20000 62094 ns/op 30720 B/op 837 allocs/op
BenchmarkParentsFiltered-4 20000 63223 ns/op 31304 B/op 845 allocs/op
BenchmarkParentsUntil-4 50000 30391 ns/op 11828 B/op 358 allocs/op
BenchmarkParentsUntilSelection-4 20000 99962 ns/op 54075 B/op 1523 allocs/op
BenchmarkParentsUntilNodes-4 20000 98763 ns/op 54073 B/op 1523 allocs/op
BenchmarkParentsFilteredUntil-4 200000 7982 ns/op 2787 B/op 88 allocs/op
BenchmarkParentsFilteredUntilSelection-4 100000 13618 ns/op 5995 B/op 192 allocs/op
BenchmarkParentsFilteredUntilNodes-4 100000 13639 ns/op 5994 B/op 192 allocs/op
BenchmarkSiblings-4 20000 75287 ns/op 28453 B/op 225 allocs/op
BenchmarkSiblingsFiltered-4 20000 80139 ns/op 29543 B/op 234 allocs/op
BenchmarkNext-4 100000 14270 ns/op 4659 B/op 117 allocs/op
BenchmarkNextFiltered-4 100000 15352 ns/op 4860 B/op 123 allocs/op
BenchmarkNextAll-4 20000 60811 ns/op 22771 B/op 157 allocs/op
BenchmarkNextAllFiltered-4 20000 69079 ns/op 23871 B/op 166 allocs/op
BenchmarkPrev-4 100000 14417 ns/op 4659 B/op 117 allocs/op
BenchmarkPrevFiltered-4 100000 15443 ns/op 4859 B/op 123 allocs/op
BenchmarkPrevAll-4 100000 22008 ns/op 7346 B/op 120 allocs/op
BenchmarkPrevAllFiltered-4 100000 23212 ns/op 7544 B/op 126 allocs/op
BenchmarkNextUntil-4 50000 30589 ns/op 8767 B/op 267 allocs/op
BenchmarkNextUntilSelection-4 30000 40875 ns/op 19862 B/op 546 allocs/op
BenchmarkNextUntilNodes-4 100000 15987 ns/op 8134 B/op 249 allocs/op
BenchmarkPrevUntil-4 20000 98799 ns/op 25727 B/op 467 allocs/op
BenchmarkPrevUntilSelection-4 30000 51874 ns/op 24875 B/op 694 allocs/op
BenchmarkPrevUntilNodes-4 100000 12901 ns/op 6334 B/op 204 allocs/op
BenchmarkNextFilteredUntil-4 100000 19869 ns/op 5909 B/op 177 allocs/op
BenchmarkNextFilteredUntilSelection-4 30000 45412 ns/op 20557 B/op 571 allocs/op
BenchmarkNextFilteredUntilNodes-4 30000 45363 ns/op 20557 B/op 571 allocs/op
BenchmarkPrevFilteredUntil-4 100000 19357 ns/op 6033 B/op 179 allocs/op
BenchmarkPrevFilteredUntilSelection-4 30000 46396 ns/op 21305 B/op 591 allocs/op
BenchmarkPrevFilteredUntilNodes-4 30000 46133 ns/op 21305 B/op 591 allocs/op
BenchmarkClosest-4 500000 3448 ns/op 160 B/op 8 allocs/op
BenchmarkClosestSelection-4 3000000 528 ns/op 96 B/op 6 allocs/op
BenchmarkClosestNodes-4 3000000 523 ns/op 96 B/op 6 allocs/op
PASS
ok github.com/PuerkitoBio/goquery 162.012s
+86
View File
@@ -0,0 +1,86 @@
BenchmarkFirst-4 30000000 51.7 ns/op 48 B/op 1 allocs/op
BenchmarkLast-4 30000000 51.9 ns/op 48 B/op 1 allocs/op
BenchmarkEq-4 30000000 50.0 ns/op 48 B/op 1 allocs/op
BenchmarkSlice-4 500000000 3.47 ns/op 0 B/op 0 allocs/op
BenchmarkGet-4 2000000000 1.68 ns/op 0 B/op 0 allocs/op
BenchmarkIndex-4 2000000 804 ns/op 248 B/op 10 allocs/op
BenchmarkIndexSelector-4 100000 16285 ns/op 3839 B/op 21 allocs/op
BenchmarkIndexOfNode-4 200000000 6.50 ns/op 0 B/op 0 allocs/op
BenchmarkIndexOfSelection-4 200000000 7.02 ns/op 0 B/op 0 allocs/op
BenchmarkMetalReviewExample-4 10000 143160 ns/op 12417 B/op 320 allocs/op
BenchmarkAdd-4 200000 10326 ns/op 208 B/op 9 allocs/op
BenchmarkAddSelection-4 10000000 155 ns/op 48 B/op 1 allocs/op
BenchmarkAddNodes-4 10000000 156 ns/op 48 B/op 1 allocs/op
BenchmarkAddNodesBig-4 20000 94439 ns/op 21847 B/op 37 allocs/op
BenchmarkAndSelf-4 1000000 1791 ns/op 1008 B/op 5 allocs/op
BenchmarkFilter-4 100000 19470 ns/op 360 B/op 8 allocs/op
BenchmarkNot-4 100000 22500 ns/op 136 B/op 5 allocs/op
BenchmarkFilterFunction-4 50000 34578 ns/op 22976 B/op 755 allocs/op
BenchmarkNotFunction-4 50000 38703 ns/op 29120 B/op 757 allocs/op
BenchmarkFilterNodes-4 50000 34486 ns/op 20960 B/op 749 allocs/op
BenchmarkNotNodes-4 30000 41094 ns/op 29120 B/op 757 allocs/op
BenchmarkFilterSelection-4 50000 33623 ns/op 20960 B/op 749 allocs/op
BenchmarkNotSelection-4 30000 41483 ns/op 29120 B/op 757 allocs/op
BenchmarkHas-4 5000 266628 ns/op 2371 B/op 50 allocs/op
BenchmarkHasNodes-4 10000 152617 ns/op 21184 B/op 752 allocs/op
BenchmarkHasSelection-4 10000 156682 ns/op 21184 B/op 752 allocs/op
BenchmarkEnd-4 2000000000 1.00 ns/op 0 B/op 0 allocs/op
BenchmarkEach-4 300000 4712 ns/op 3304 B/op 118 allocs/op
BenchmarkMap-4 200000 8434 ns/op 5572 B/op 184 allocs/op
BenchmarkEachWithBreak-4 2000000 819 ns/op 560 B/op 20 allocs/op
BenchmarkAttr-4 100000000 21.7 ns/op 0 B/op 0 allocs/op
BenchmarkText-4 200000 9376 ns/op 7536 B/op 110 allocs/op
BenchmarkLength-4 2000000000 0.35 ns/op 0 B/op 0 allocs/op
BenchmarkHtml-4 5000000 401 ns/op 120 B/op 2 allocs/op
BenchmarkIs-4 100000 22214 ns/op 88 B/op 4 allocs/op
BenchmarkIsPositional-4 50000 26559 ns/op 1112 B/op 10 allocs/op
BenchmarkIsFunction-4 1000000 1228 ns/op 784 B/op 28 allocs/op
BenchmarkIsSelection-4 50000 33471 ns/op 20960 B/op 749 allocs/op
BenchmarkIsNodes-4 50000 34461 ns/op 20960 B/op 749 allocs/op
BenchmarkHasClass-4 10000 232429 ns/op 14944 B/op 976 allocs/op
BenchmarkContains-4 200000000 7.62 ns/op 0 B/op 0 allocs/op
BenchmarkFind-4 100000 16114 ns/op 3839 B/op 21 allocs/op
BenchmarkFindWithinSelection-4 30000 42520 ns/op 3540 B/op 82 allocs/op
BenchmarkFindSelection-4 10000 209801 ns/op 5615 B/op 89 allocs/op
BenchmarkFindNodes-4 10000 209082 ns/op 5614 B/op 89 allocs/op
BenchmarkContents-4 300000 4836 ns/op 1420 B/op 36 allocs/op
BenchmarkContentsFiltered-4 200000 5495 ns/op 1570 B/op 41 allocs/op
BenchmarkChildren-4 3000000 527 ns/op 152 B/op 7 allocs/op
BenchmarkChildrenFiltered-4 500000 2499 ns/op 352 B/op 15 allocs/op
BenchmarkParent-4 50000 34072 ns/op 6942 B/op 387 allocs/op
BenchmarkParentFiltered-4 50000 36077 ns/op 7141 B/op 394 allocs/op
BenchmarkParents-4 20000 64118 ns/op 30719 B/op 837 allocs/op
BenchmarkParentsFiltered-4 20000 63432 ns/op 31303 B/op 845 allocs/op
BenchmarkParentsUntil-4 50000 29589 ns/op 11829 B/op 358 allocs/op
BenchmarkParentsUntilSelection-4 10000 101033 ns/op 54076 B/op 1523 allocs/op
BenchmarkParentsUntilNodes-4 10000 100584 ns/op 54076 B/op 1523 allocs/op
BenchmarkParentsFilteredUntil-4 200000 8061 ns/op 2787 B/op 88 allocs/op
BenchmarkParentsFilteredUntilSelection-4 100000 13848 ns/op 5995 B/op 192 allocs/op
BenchmarkParentsFilteredUntilNodes-4 100000 13766 ns/op 5995 B/op 192 allocs/op
BenchmarkSiblings-4 20000 75135 ns/op 28453 B/op 225 allocs/op
BenchmarkSiblingsFiltered-4 20000 80532 ns/op 29544 B/op 234 allocs/op
BenchmarkNext-4 100000 14200 ns/op 4660 B/op 117 allocs/op
BenchmarkNextFiltered-4 100000 15284 ns/op 4859 B/op 123 allocs/op
BenchmarkNextAll-4 20000 60889 ns/op 22774 B/op 157 allocs/op
BenchmarkNextAllFiltered-4 20000 65125 ns/op 23869 B/op 166 allocs/op
BenchmarkPrev-4 100000 14448 ns/op 4659 B/op 117 allocs/op
BenchmarkPrevFiltered-4 100000 15444 ns/op 4859 B/op 123 allocs/op
BenchmarkPrevAll-4 100000 22019 ns/op 7344 B/op 120 allocs/op
BenchmarkPrevAllFiltered-4 100000 23307 ns/op 7545 B/op 126 allocs/op
BenchmarkNextUntil-4 50000 30287 ns/op 8766 B/op 267 allocs/op
BenchmarkNextUntilSelection-4 30000 41476 ns/op 19862 B/op 546 allocs/op
BenchmarkNextUntilNodes-4 100000 16106 ns/op 8133 B/op 249 allocs/op
BenchmarkPrevUntil-4 20000 98951 ns/op 25728 B/op 467 allocs/op
BenchmarkPrevUntilSelection-4 30000 52390 ns/op 24875 B/op 694 allocs/op
BenchmarkPrevUntilNodes-4 100000 12986 ns/op 6334 B/op 204 allocs/op
BenchmarkNextFilteredUntil-4 100000 19365 ns/op 5908 B/op 177 allocs/op
BenchmarkNextFilteredUntilSelection-4 30000 45334 ns/op 20555 B/op 571 allocs/op
BenchmarkNextFilteredUntilNodes-4 30000 45292 ns/op 20556 B/op 571 allocs/op
BenchmarkPrevFilteredUntil-4 100000 19412 ns/op 6032 B/op 179 allocs/op
BenchmarkPrevFilteredUntilSelection-4 30000 46286 ns/op 21304 B/op 591 allocs/op
BenchmarkPrevFilteredUntilNodes-4 30000 46554 ns/op 21305 B/op 591 allocs/op
BenchmarkClosest-4 500000 3480 ns/op 160 B/op 8 allocs/op
BenchmarkClosestSelection-4 2000000 722 ns/op 96 B/op 6 allocs/op
BenchmarkClosestNodes-4 2000000 719 ns/op 96 B/op 6 allocs/op
PASS
ok github.com/PuerkitoBio/goquery 160.565s
+12 -4
View File
@@ -70,7 +70,9 @@ func BenchmarkIndex(b *testing.B) {
for i := 0; i < b.N; i++ {
j = sel.Index()
}
b.Logf("Index=%d", j)
if j != 3 {
b.Fatalf("want 3, got %d", j)
}
}
func BenchmarkIndexSelector(b *testing.B) {
@@ -82,7 +84,9 @@ func BenchmarkIndexSelector(b *testing.B) {
for i := 0; i < b.N; i++ {
j = sel.IndexSelector("dd")
}
b.Logf("IndexSelector=%d", j)
if j != 4 {
b.Fatalf("want 4, got %d", j)
}
}
func BenchmarkIndexOfNode(b *testing.B) {
@@ -96,7 +100,9 @@ func BenchmarkIndexOfNode(b *testing.B) {
for i := 0; i < b.N; i++ {
j = sel.IndexOfNode(n)
}
b.Logf("IndexOfNode=%d", j)
if j != 2 {
b.Fatalf("want 2, got %d", j)
}
}
func BenchmarkIndexOfSelection(b *testing.B) {
@@ -108,5 +114,7 @@ func BenchmarkIndexOfSelection(b *testing.B) {
for i := 0; i < b.N; i++ {
j = sel.IndexOfSelection(sel2)
}
b.Logf("IndexOfSelection=%d", j)
if j != 2 {
b.Fatalf("want 2, got %d", j)
}
}
-2
View File
@@ -37,6 +37,4 @@ func BenchmarkMetalReviewExample(b *testing.B) {
}
})
}
b.Log(buf.String())
b.Logf("MetalReviewExample=%d", n)
}
+36 -4
View File
@@ -17,7 +17,9 @@ func BenchmarkAdd(b *testing.B) {
sel.Add("h2[title]")
}
}
b.Logf("Add=%d", n)
if n != 43 {
b.Fatalf("want 43, got %d", n)
}
}
func BenchmarkAddSelection(b *testing.B) {
@@ -34,7 +36,9 @@ func BenchmarkAddSelection(b *testing.B) {
sel.AddSelection(sel2)
}
}
b.Logf("AddSelection=%d", n)
if n != 43 {
b.Fatalf("want 43, got %d", n)
}
}
func BenchmarkAddNodes(b *testing.B) {
@@ -52,7 +56,33 @@ func BenchmarkAddNodes(b *testing.B) {
sel.AddNodes(nodes...)
}
}
b.Logf("AddNodes=%d", n)
if n != 43 {
b.Fatalf("want 43, got %d", n)
}
}
func BenchmarkAddNodesBig(b *testing.B) {
var n int
doc := DocW()
sel := doc.Find("li")
// make nodes > 1000
nodes := sel.Nodes
nodes = append(nodes, nodes...)
nodes = append(nodes, nodes...)
sel = doc.Find("xyz")
b.ResetTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.AddNodes(nodes...).Length()
} else {
sel.AddNodes(nodes...)
}
}
if n != 373 {
b.Fatalf("want 373, got %d", n)
}
}
func BenchmarkAndSelf(b *testing.B) {
@@ -68,5 +98,7 @@ func BenchmarkAndSelf(b *testing.B) {
sel.AndSelf()
}
}
b.Logf("AndSelf=%d", n)
if n != 44 {
b.Fatalf("want 44, got %d", n)
}
}
+36 -12
View File
@@ -17,7 +17,9 @@ func BenchmarkFilter(b *testing.B) {
sel.Filter(".toclevel-1")
}
}
b.Logf("Filter=%d", n)
if n != 13 {
b.Fatalf("want 13, got %d", n)
}
}
func BenchmarkNot(b *testing.B) {
@@ -33,7 +35,9 @@ func BenchmarkNot(b *testing.B) {
sel.Filter(".toclevel-2")
}
}
b.Logf("Not=%d", n)
if n != 371 {
b.Fatalf("want 371, got %d", n)
}
}
func BenchmarkFilterFunction(b *testing.B) {
@@ -52,7 +56,9 @@ func BenchmarkFilterFunction(b *testing.B) {
sel.FilterFunction(f)
}
}
b.Logf("FilterFunction=%d", n)
if n != 112 {
b.Fatalf("want 112, got %d", n)
}
}
func BenchmarkNotFunction(b *testing.B) {
@@ -71,7 +77,9 @@ func BenchmarkNotFunction(b *testing.B) {
sel.NotFunction(f)
}
}
b.Logf("NotFunction=%d", n)
if n != 261 {
b.Fatalf("want 261, got %d", n)
}
}
func BenchmarkFilterNodes(b *testing.B) {
@@ -89,7 +97,9 @@ func BenchmarkFilterNodes(b *testing.B) {
sel.FilterNodes(nodes...)
}
}
b.Logf("FilterNodes=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkNotNodes(b *testing.B) {
@@ -107,7 +117,9 @@ func BenchmarkNotNodes(b *testing.B) {
sel.NotNodes(nodes...)
}
}
b.Logf("NotNodes=%d", n)
if n != 360 {
b.Fatalf("want 360, got %d", n)
}
}
func BenchmarkFilterSelection(b *testing.B) {
@@ -124,7 +136,9 @@ func BenchmarkFilterSelection(b *testing.B) {
sel.FilterSelection(sel2)
}
}
b.Logf("FilterSelection=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkNotSelection(b *testing.B) {
@@ -141,7 +155,9 @@ func BenchmarkNotSelection(b *testing.B) {
sel.NotSelection(sel2)
}
}
b.Logf("NotSelection=%d", n)
if n != 360 {
b.Fatalf("want 360, got %d", n)
}
}
func BenchmarkHas(b *testing.B) {
@@ -157,7 +173,9 @@ func BenchmarkHas(b *testing.B) {
sel.Has(".editsection")
}
}
b.Logf("Has=%d", n)
if n != 13 {
b.Fatalf("want 13, got %d", n)
}
}
func BenchmarkHasNodes(b *testing.B) {
@@ -175,7 +193,9 @@ func BenchmarkHasNodes(b *testing.B) {
sel.HasNodes(nodes...)
}
}
b.Logf("HasNodes=%d", n)
if n != 15 {
b.Fatalf("want 15, got %d", n)
}
}
func BenchmarkHasSelection(b *testing.B) {
@@ -192,7 +212,9 @@ func BenchmarkHasSelection(b *testing.B) {
sel.HasSelection(sel2)
}
}
b.Logf("HasSelection=%d", n)
if n != 15 {
b.Fatalf("want 15, got %d", n)
}
}
func BenchmarkEnd(b *testing.B) {
@@ -208,5 +230,7 @@ func BenchmarkEnd(b *testing.B) {
sel.End()
}
}
b.Logf("End=%d", n)
if n != 373 {
b.Fatalf("want 373, got %d", n)
}
}
+11 -4
View File
@@ -1,6 +1,7 @@
package goquery
import (
"strconv"
"testing"
)
@@ -19,7 +20,9 @@ func BenchmarkEach(b *testing.B) {
n = tmp
}
}
b.Logf("Each=%d", n)
if n != 59 {
b.Fatalf("want 59, got %d", n)
}
}
func BenchmarkMap(b *testing.B) {
@@ -29,7 +32,7 @@ func BenchmarkMap(b *testing.B) {
sel := DocW().Find("td")
f := func(i int, s *Selection) string {
tmp++
return string(tmp)
return strconv.Itoa(tmp)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
@@ -38,7 +41,9 @@ func BenchmarkMap(b *testing.B) {
n = tmp
}
}
b.Logf("Map=%d", n)
if n != 59 {
b.Fatalf("want 59, got %d", n)
}
}
func BenchmarkEachWithBreak(b *testing.B) {
@@ -58,5 +63,7 @@ func BenchmarkEachWithBreak(b *testing.B) {
n = tmp
}
}
b.Logf("Each=%d", n)
if n != 10 {
b.Fatalf("want 10, got %d", n)
}
}
+6 -2
View File
@@ -13,7 +13,9 @@ func BenchmarkAttr(b *testing.B) {
for i := 0; i < b.N; i++ {
s, _ = sel.Attr("id")
}
b.Logf("Attr=%s", s)
if s != "firstHeading" {
b.Fatalf("want firstHeading, got %q", s)
}
}
func BenchmarkText(b *testing.B) {
@@ -34,7 +36,9 @@ func BenchmarkLength(b *testing.B) {
for i := 0; i < b.N; i++ {
n = sel.Length()
}
b.Logf("Length=%d", n)
if n != 14 {
b.Fatalf("want 14, got %d", n)
}
}
func BenchmarkHtml(b *testing.B) {
+21 -7
View File
@@ -13,7 +13,9 @@ func BenchmarkIs(b *testing.B) {
for i := 0; i < b.N; i++ {
y = sel.Is(".toclevel-2")
}
b.Logf("Is=%v", y)
if !y {
b.Fatal("want true")
}
}
func BenchmarkIsPositional(b *testing.B) {
@@ -25,7 +27,9 @@ func BenchmarkIsPositional(b *testing.B) {
for i := 0; i < b.N; i++ {
y = sel.Is("li:nth-child(2)")
}
b.Logf("IsPositional=%v", y)
if !y {
b.Fatal("want true")
}
}
func BenchmarkIsFunction(b *testing.B) {
@@ -40,7 +44,9 @@ func BenchmarkIsFunction(b *testing.B) {
for i := 0; i < b.N; i++ {
y = sel.IsFunction(f)
}
b.Logf("IsFunction=%v", y)
if !y {
b.Fatal("want true")
}
}
func BenchmarkIsSelection(b *testing.B) {
@@ -53,7 +59,9 @@ func BenchmarkIsSelection(b *testing.B) {
for i := 0; i < b.N; i++ {
y = sel.IsSelection(sel2)
}
b.Logf("IsSelection=%v", y)
if !y {
b.Fatal("want true")
}
}
func BenchmarkIsNodes(b *testing.B) {
@@ -67,7 +75,9 @@ func BenchmarkIsNodes(b *testing.B) {
for i := 0; i < b.N; i++ {
y = sel.IsNodes(nodes...)
}
b.Logf("IsNodes=%v", y)
if !y {
b.Fatal("want true")
}
}
func BenchmarkHasClass(b *testing.B) {
@@ -79,7 +89,9 @@ func BenchmarkHasClass(b *testing.B) {
for i := 0; i < b.N; i++ {
y = sel.HasClass("official")
}
b.Logf("HasClass=%v", y)
if !y {
b.Fatal("want true")
}
}
func BenchmarkContains(b *testing.B) {
@@ -93,5 +105,7 @@ func BenchmarkContains(b *testing.B) {
for i := 0; i < b.N; i++ {
y = sel.Contains(node)
}
b.Logf("Contains=%v", y)
if !y {
b.Fatal("want true")
}
}
+129 -43
View File
@@ -15,7 +15,9 @@ func BenchmarkFind(b *testing.B) {
DocB().Find("dd")
}
}
b.Logf("Find=%d", n)
if n != 41 {
b.Fatalf("want 41, got %d", n)
}
}
func BenchmarkFindWithinSelection(b *testing.B) {
@@ -31,7 +33,9 @@ func BenchmarkFindWithinSelection(b *testing.B) {
sel.Find("a[class]")
}
}
b.Logf("FindWithinSelection=%d", n)
if n != 39 {
b.Fatalf("want 39, got %d", n)
}
}
func BenchmarkFindSelection(b *testing.B) {
@@ -48,7 +52,9 @@ func BenchmarkFindSelection(b *testing.B) {
sel.FindSelection(sel2)
}
}
b.Logf("FindSelection=%d", n)
if n != 73 {
b.Fatalf("want 73, got %d", n)
}
}
func BenchmarkFindNodes(b *testing.B) {
@@ -66,7 +72,9 @@ func BenchmarkFindNodes(b *testing.B) {
sel.FindNodes(nodes...)
}
}
b.Logf("FindNodes=%d", n)
if n != 73 {
b.Fatalf("want 73, got %d", n)
}
}
func BenchmarkContents(b *testing.B) {
@@ -82,7 +90,9 @@ func BenchmarkContents(b *testing.B) {
sel.Contents()
}
}
b.Logf("Contents=%d", n)
if n != 16 {
b.Fatalf("want 16, got %d", n)
}
}
func BenchmarkContentsFiltered(b *testing.B) {
@@ -98,7 +108,9 @@ func BenchmarkContentsFiltered(b *testing.B) {
sel.ContentsFiltered("a[href=\"#Examples\"]")
}
}
b.Logf("ContentsFiltered=%d", n)
if n != 1 {
b.Fatalf("want 1, got %d", n)
}
}
func BenchmarkChildren(b *testing.B) {
@@ -114,7 +126,9 @@ func BenchmarkChildren(b *testing.B) {
sel.Children()
}
}
b.Logf("Children=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkChildrenFiltered(b *testing.B) {
@@ -130,7 +144,9 @@ func BenchmarkChildrenFiltered(b *testing.B) {
sel.ChildrenFiltered(".editsection")
}
}
b.Logf("ChildrenFiltered=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkParent(b *testing.B) {
@@ -146,7 +162,9 @@ func BenchmarkParent(b *testing.B) {
sel.Parent()
}
}
b.Logf("Parent=%d", n)
if n != 55 {
b.Fatalf("want 55, got %d", n)
}
}
func BenchmarkParentFiltered(b *testing.B) {
@@ -162,7 +180,9 @@ func BenchmarkParentFiltered(b *testing.B) {
sel.ParentFiltered("ul[id]")
}
}
b.Logf("ParentFiltered=%d", n)
if n != 4 {
b.Fatalf("want 4, got %d", n)
}
}
func BenchmarkParents(b *testing.B) {
@@ -178,7 +198,9 @@ func BenchmarkParents(b *testing.B) {
sel.Parents()
}
}
b.Logf("Parents=%d", n)
if n != 73 {
b.Fatalf("want 73, got %d", n)
}
}
func BenchmarkParentsFiltered(b *testing.B) {
@@ -194,7 +216,9 @@ func BenchmarkParentsFiltered(b *testing.B) {
sel.ParentsFiltered("tr")
}
}
b.Logf("ParentsFiltered=%d", n)
if n != 18 {
b.Fatalf("want 18, got %d", n)
}
}
func BenchmarkParentsUntil(b *testing.B) {
@@ -210,7 +234,9 @@ func BenchmarkParentsUntil(b *testing.B) {
sel.ParentsUntil("table")
}
}
b.Logf("ParentsUntil=%d", n)
if n != 52 {
b.Fatalf("want 52, got %d", n)
}
}
func BenchmarkParentsUntilSelection(b *testing.B) {
@@ -227,7 +253,9 @@ func BenchmarkParentsUntilSelection(b *testing.B) {
sel.ParentsUntilSelection(sel2)
}
}
b.Logf("ParentsUntilSelection=%d", n)
if n != 70 {
b.Fatalf("want 70, got %d", n)
}
}
func BenchmarkParentsUntilNodes(b *testing.B) {
@@ -245,7 +273,9 @@ func BenchmarkParentsUntilNodes(b *testing.B) {
sel.ParentsUntilNodes(nodes...)
}
}
b.Logf("ParentsUntilNodes=%d", n)
if n != 70 {
b.Fatalf("want 70, got %d", n)
}
}
func BenchmarkParentsFilteredUntil(b *testing.B) {
@@ -261,7 +291,9 @@ func BenchmarkParentsFilteredUntil(b *testing.B) {
sel.ParentsFilteredUntil(":nth-child(1)", "ul")
}
}
b.Logf("ParentsFilteredUntil=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkParentsFilteredUntilSelection(b *testing.B) {
@@ -278,7 +310,9 @@ func BenchmarkParentsFilteredUntilSelection(b *testing.B) {
sel.ParentsFilteredUntilSelection(":nth-child(1)", sel2)
}
}
b.Logf("ParentsFilteredUntilSelection=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkParentsFilteredUntilNodes(b *testing.B) {
@@ -296,7 +330,9 @@ func BenchmarkParentsFilteredUntilNodes(b *testing.B) {
sel.ParentsFilteredUntilNodes(":nth-child(1)", nodes...)
}
}
b.Logf("ParentsFilteredUntilNodes=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkSiblings(b *testing.B) {
@@ -312,7 +348,9 @@ func BenchmarkSiblings(b *testing.B) {
sel.Siblings()
}
}
b.Logf("Siblings=%d", n)
if n != 293 {
b.Fatalf("want 293, got %d", n)
}
}
func BenchmarkSiblingsFiltered(b *testing.B) {
@@ -328,7 +366,9 @@ func BenchmarkSiblingsFiltered(b *testing.B) {
sel.SiblingsFiltered("[class]")
}
}
b.Logf("SiblingsFiltered=%d", n)
if n != 46 {
b.Fatalf("want 46, got %d", n)
}
}
func BenchmarkNext(b *testing.B) {
@@ -344,7 +384,9 @@ func BenchmarkNext(b *testing.B) {
sel.Next()
}
}
b.Logf("Next=%d", n)
if n != 49 {
b.Fatalf("want 49, got %d", n)
}
}
func BenchmarkNextFiltered(b *testing.B) {
@@ -360,7 +402,9 @@ func BenchmarkNextFiltered(b *testing.B) {
sel.NextFiltered("[class]")
}
}
b.Logf("NextFiltered=%d", n)
if n != 6 {
b.Fatalf("want 6, got %d", n)
}
}
func BenchmarkNextAll(b *testing.B) {
@@ -376,7 +420,9 @@ func BenchmarkNextAll(b *testing.B) {
sel.NextAll()
}
}
b.Logf("NextAll=%d", n)
if n != 234 {
b.Fatalf("want 234, got %d", n)
}
}
func BenchmarkNextAllFiltered(b *testing.B) {
@@ -392,7 +438,9 @@ func BenchmarkNextAllFiltered(b *testing.B) {
sel.NextAllFiltered("[class]")
}
}
b.Logf("NextAllFiltered=%d", n)
if n != 33 {
b.Fatalf("want 33, got %d", n)
}
}
func BenchmarkPrev(b *testing.B) {
@@ -408,7 +456,9 @@ func BenchmarkPrev(b *testing.B) {
sel.Prev()
}
}
b.Logf("Prev=%d", n)
if n != 49 {
b.Fatalf("want 49, got %d", n)
}
}
func BenchmarkPrevFiltered(b *testing.B) {
@@ -426,7 +476,9 @@ func BenchmarkPrevFiltered(b *testing.B) {
}
// There is one more Prev li with a class, compared to Next li with a class
// (confirmed by looking at the HTML, this is ok)
b.Logf("PrevFiltered=%d", n)
if n != 7 {
b.Fatalf("want 7, got %d", n)
}
}
func BenchmarkPrevAll(b *testing.B) {
@@ -442,7 +494,9 @@ func BenchmarkPrevAll(b *testing.B) {
sel.PrevAll()
}
}
b.Logf("PrevAll=%d", n)
if n != 78 {
b.Fatalf("want 78, got %d", n)
}
}
func BenchmarkPrevAllFiltered(b *testing.B) {
@@ -458,7 +512,9 @@ func BenchmarkPrevAllFiltered(b *testing.B) {
sel.PrevAllFiltered("[class]")
}
}
b.Logf("PrevAllFiltered=%d", n)
if n != 6 {
b.Fatalf("want 6, got %d", n)
}
}
func BenchmarkNextUntil(b *testing.B) {
@@ -474,7 +530,9 @@ func BenchmarkNextUntil(b *testing.B) {
sel.NextUntil(":nth-child(4)")
}
}
b.Logf("NextUntil=%d", n)
if n != 84 {
b.Fatalf("want 84, got %d", n)
}
}
func BenchmarkNextUntilSelection(b *testing.B) {
@@ -491,7 +549,9 @@ func BenchmarkNextUntilSelection(b *testing.B) {
sel.NextUntilSelection(sel2)
}
}
b.Logf("NextUntilSelection=%d", n)
if n != 42 {
b.Fatalf("want 42, got %d", n)
}
}
func BenchmarkNextUntilNodes(b *testing.B) {
@@ -509,7 +569,9 @@ func BenchmarkNextUntilNodes(b *testing.B) {
sel.NextUntilNodes(nodes...)
}
}
b.Logf("NextUntilNodes=%d", n)
if n != 12 {
b.Fatalf("want 12, got %d", n)
}
}
func BenchmarkPrevUntil(b *testing.B) {
@@ -525,7 +587,9 @@ func BenchmarkPrevUntil(b *testing.B) {
sel.PrevUntil(":nth-child(4)")
}
}
b.Logf("PrevUntil=%d", n)
if n != 238 {
b.Fatalf("want 238, got %d", n)
}
}
func BenchmarkPrevUntilSelection(b *testing.B) {
@@ -542,7 +606,9 @@ func BenchmarkPrevUntilSelection(b *testing.B) {
sel.PrevUntilSelection(sel2)
}
}
b.Logf("PrevUntilSelection=%d", n)
if n != 49 {
b.Fatalf("want 49, got %d", n)
}
}
func BenchmarkPrevUntilNodes(b *testing.B) {
@@ -560,7 +626,9 @@ func BenchmarkPrevUntilNodes(b *testing.B) {
sel.PrevUntilNodes(nodes...)
}
}
b.Logf("PrevUntilNodes=%d", n)
if n != 11 {
b.Fatalf("want 11, got %d", n)
}
}
func BenchmarkNextFilteredUntil(b *testing.B) {
@@ -576,7 +644,9 @@ func BenchmarkNextFilteredUntil(b *testing.B) {
sel.NextFilteredUntil("p", "div")
}
}
b.Logf("NextFilteredUntil=%d", n)
if n != 22 {
b.Fatalf("want 22, got %d", n)
}
}
func BenchmarkNextFilteredUntilSelection(b *testing.B) {
@@ -593,7 +663,9 @@ func BenchmarkNextFilteredUntilSelection(b *testing.B) {
sel.NextFilteredUntilSelection("p", sel2)
}
}
b.Logf("NextFilteredUntilSelection=%d", n)
if n != 22 {
b.Fatalf("want 22, got %d", n)
}
}
func BenchmarkNextFilteredUntilNodes(b *testing.B) {
@@ -611,7 +683,9 @@ func BenchmarkNextFilteredUntilNodes(b *testing.B) {
sel.NextFilteredUntilNodes("p", nodes...)
}
}
b.Logf("NextFilteredUntilNodes=%d", n)
if n != 22 {
b.Fatalf("want 22, got %d", n)
}
}
func BenchmarkPrevFilteredUntil(b *testing.B) {
@@ -627,7 +701,9 @@ func BenchmarkPrevFilteredUntil(b *testing.B) {
sel.PrevFilteredUntil("p", "div")
}
}
b.Logf("PrevFilteredUntil=%d", n)
if n != 20 {
b.Fatalf("want 20, got %d", n)
}
}
func BenchmarkPrevFilteredUntilSelection(b *testing.B) {
@@ -644,7 +720,9 @@ func BenchmarkPrevFilteredUntilSelection(b *testing.B) {
sel.PrevFilteredUntilSelection("p", sel2)
}
}
b.Logf("PrevFilteredUntilSelection=%d", n)
if n != 20 {
b.Fatalf("want 20, got %d", n)
}
}
func BenchmarkPrevFilteredUntilNodes(b *testing.B) {
@@ -662,7 +740,9 @@ func BenchmarkPrevFilteredUntilNodes(b *testing.B) {
sel.PrevFilteredUntilNodes("p", nodes...)
}
}
b.Logf("PrevFilteredUntilNodes=%d", n)
if n != 20 {
b.Fatalf("want 20, got %d", n)
}
}
func BenchmarkClosest(b *testing.B) {
@@ -678,7 +758,9 @@ func BenchmarkClosest(b *testing.B) {
sel.Closest(".pvk-content")
}
}
b.Logf("Closest=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkClosestSelection(b *testing.B) {
@@ -695,7 +777,9 @@ func BenchmarkClosestSelection(b *testing.B) {
sel.ClosestSelection(sel2)
}
}
b.Logf("ClosestSelection=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkClosestNodes(b *testing.B) {
@@ -712,5 +796,7 @@ func BenchmarkClosestNodes(b *testing.B) {
sel.ClosestNodes(nodes...)
}
}
b.Logf("ClosestNodes=%d", n)
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2012-2014, Martin Angers & Contributors
// Copyright (c) 2012-2016, Martin Angers & Contributors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
+5 -5
View File
@@ -4,16 +4,16 @@
The `go.net/html` package used by `goquery` requires that the html document is UTF-8 encoded. When you know the encoding of the html page is not UTF-8, you can use the `iconv` package to convert it to UTF-8 (there are various implementation of the `iconv` API, see [godoc.org][iconv] for other options):
```
```bash
$ go get -u github.com/djimenez/iconv-go
```
and then:
```
```golang
// Load the URL
res, err := http.Get(url)
if e != nil {
if err != nil {
// handle error
}
defer res.Body.Close()
@@ -51,7 +51,7 @@ You can find a code example using `otto` [in this gist][exotto]. Thanks to githu
If all you need is a normal `for` loop over all nodes in the current selection, where `Map/Each`-style iteration is not necessary, you can use the following:
```
```golang
sel := Doc().Find(".selector")
for i := range sel.Nodes {
single := sel.Eq(i)
@@ -65,4 +65,4 @@ Thanks to github user @jmoiron.
[otto]: https://github.com/robertkrimen/otto
[exotto]: https://gist.github.com/cryptix/87127f76a94183747b53
[iconv]: http://godoc.org/?q=iconv
[text]: http://godoc.org/code.google.com/p/go.text/encoding
[text]: https://godoc.org/golang.org/x/text/encoding
+60 -10
View File
@@ -1,26 +1,37 @@
package goquery
package goquery_test
import (
"fmt"
"log"
"net/http"
"os"
"strings"
// In real use, this import would be required (not in this example, since it
// is part of the goquery package)
//"github.com/PuerkitoBio/goquery"
"github.com/PuerkitoBio/goquery"
)
// This example scrapes the reviews shown on the home page of metalsucks.net.
func ExampleScrape_MetalSucks() {
// Load the HTML document (in real use, the type would be *goquery.Document)
doc, err := NewDocument("http://metalsucks.net")
func Example() {
// Request the HTML page.
res, err := http.Get("http://metalsucks.net")
if err != nil {
log.Fatal(err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatalf("status code error: %d %s", res.StatusCode, res.Status)
}
// Load the HTML document
doc, err := goquery.NewDocumentFromReader(res.Body)
if err != nil {
log.Fatal(err)
}
// Find the review items (the type of the Selection would be *goquery.Selection)
doc.Find(".reviews-wrap article .review-rhs").Each(func(i int, s *Selection) {
// Find the review items
doc.Find(".sidebar-reviews article .content-block").Each(func(i int, s *goquery.Selection) {
// For each item found, get the band and title
band := s.Find("h3").Text()
band := s.Find("a").Text()
title := s.Find("i").Text()
fmt.Printf("Review %d: %s - %s\n", i, band, title)
})
@@ -30,3 +41,42 @@ func ExampleScrape_MetalSucks() {
// xOutput: voluntarily fail the Example output.
}
// This example shows how to use NewDocumentFromReader from a file.
func ExampleNewDocumentFromReader_file() {
// create from a file
f, err := os.Open("some/file.html")
if err != nil {
log.Fatal(err)
}
defer f.Close()
doc, err := goquery.NewDocumentFromReader(f)
if err != nil {
log.Fatal(err)
}
// use the goquery document...
_ = doc.Find("h1")
}
// This example shows how to use NewDocumentFromReader from a string.
func ExampleNewDocumentFromReader_string() {
// create from a string
data := `
<html>
<head>
<title>My document</title>
</head>
<body>
<h1>Header</h1>
</body>
</html>`
doc, err := goquery.NewDocumentFromReader(strings.NewReader(data))
if err != nil {
log.Fatal(err)
}
header := doc.Find("h1").Text()
fmt.Println(header)
// Output: Header
}
+27 -6
View File
@@ -1,16 +1,13 @@
package goquery
import (
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
import "golang.org/x/net/html"
// Add adds the selector string's matching nodes to those in the current
// selection and returns a new Selection object.
// The selector string is run in the context of the document of the current
// Selection object.
func (s *Selection) Add(selector string) *Selection {
return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, cascadia.MustCompile(selector))...)
return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, compileMatcher(selector))...)
}
// AddMatcher adds the matcher's matching nodes to those in the current
@@ -38,12 +35,36 @@ func (s *Selection) Union(sel *Selection) *Selection {
// AddNodes adds the specified nodes to those in the
// current selection and returns a new Selection object.
func (s *Selection) AddNodes(nodes ...*html.Node) *Selection {
return pushStack(s, appendWithoutDuplicates(s.Nodes, nodes))
return pushStack(s, appendWithoutDuplicates(s.Nodes, nodes, nil))
}
// AndSelf adds the previous set of elements on the stack to the current set.
// It returns a new Selection object containing the current Selection combined
// with the previous one.
// Deprecated: This function has been deprecated and is now an alias for AddBack().
func (s *Selection) AndSelf() *Selection {
return s.AddBack()
}
// AddBack adds the previous set of elements on the stack to the current set.
// It returns a new Selection object containing the current Selection combined
// with the previous one.
func (s *Selection) AddBack() *Selection {
return s.AddSelection(s.prevSel)
}
// AddBackFiltered reduces the previous set of elements on the stack to those that
// match the selector string, and adds them to the current set.
// It returns a new Selection object containing the current Selection combined
// with the filtered previous one
func (s *Selection) AddBackFiltered(selector string) *Selection {
return s.AddSelection(s.prevSel.Filter(selector))
}
// AddBackMatcher reduces the previous set of elements on the stack to those that match
// the mateher, and adds them to the curernt set.
// It returns a new Selection object containing the current Selection combined
// with the filtered previous one
func (s *Selection) AddBackMatcher(m Matcher) *Selection {
return s.AddSelection(s.prevSel.FilterMatcher(m))
}
+50
View File
@@ -9,6 +9,16 @@ func TestAdd(t *testing.T) {
assertLength(t, sel.Nodes, 19)
}
func TestAddInvalid(t *testing.T) {
sel1 := Doc().Find("div.row-fluid")
sel2 := sel1.Add("")
assertLength(t, sel1.Nodes, 9)
assertLength(t, sel2.Nodes, 9)
if sel1 == sel2 {
t.Errorf("selections should not be the same")
}
}
func TestAddRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Add("a").End()
@@ -56,6 +66,24 @@ func TestAddNodesRollback(t *testing.T) {
assertEqual(t, sel, sel2)
}
func TestAddNodesBig(t *testing.T) {
doc := DocW()
sel := doc.Find("li")
assertLength(t, sel.Nodes, 373)
sel2 := doc.Find("xyz")
assertLength(t, sel2.Nodes, 0)
nodes := sel.Nodes
sel2 = sel2.AddNodes(nodes...)
assertLength(t, sel2.Nodes, 373)
nodes2 := append(nodes, nodes...)
sel2 = sel2.End().AddNodes(nodes2...)
assertLength(t, sel2.Nodes, 373)
nodes3 := append(nodes2, nodes...)
sel2 = sel2.End().AddNodes(nodes3...)
assertLength(t, sel2.Nodes, 373)
}
func TestAndSelf(t *testing.T) {
sel := Doc().Find(".span12").Last().AndSelf()
assertLength(t, sel.Nodes, 2)
@@ -66,3 +94,25 @@ func TestAndSelfRollback(t *testing.T) {
sel2 := sel.Find("a").AndSelf().End().End()
assertEqual(t, sel, sel2)
}
func TestAddBack(t *testing.T) {
sel := Doc().Find(".span12").Last().AddBack()
assertLength(t, sel.Nodes, 2)
}
func TestAddBackRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Find("a").AddBack().End().End()
assertEqual(t, sel, sel2)
}
func TestAddBackFiltered(t *testing.T) {
sel := Doc().Find(".span12, .footer").Find("h1").AddBackFiltered(".footer")
assertLength(t, sel.Nodes, 2)
}
func TestAddBackFilteredRollback(t *testing.T) {
sel := Doc().Find(".span12, .footer")
sel2 := sel.Find("h1").AddBackFiltered(".footer").End().End()
assertEqual(t, sel, sel2)
}
+16 -9
View File
@@ -1,19 +1,16 @@
package goquery
import (
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
import "golang.org/x/net/html"
// Filter reduces the set of matched elements to those that match the selector string.
// It returns a new Selection object for this subset of matching elements.
func (s *Selection) Filter(selector string) *Selection {
return s.FilterMatcher(cascadia.MustCompile(selector))
return s.FilterMatcher(compileMatcher(selector))
}
// FilterMatcher reduces the set of matched elements to those that match
// the given matcher.
// It returns a new Selection object for this subset of matching elements.
// the given matcher. It returns a new Selection object for this subset
// of matching elements.
func (s *Selection) FilterMatcher(m Matcher) *Selection {
return pushStack(s, winnow(s, m, true))
}
@@ -21,7 +18,7 @@ func (s *Selection) FilterMatcher(m Matcher) *Selection {
// Not removes elements from the Selection that match the selector string.
// It returns a new Selection object with the matching elements removed.
func (s *Selection) Not(selector string) *Selection {
return s.NotMatcher(cascadia.MustCompile(selector))
return s.NotMatcher(compileMatcher(selector))
}
// NotMatcher removes elements from the Selection that match the given matcher.
@@ -142,8 +139,18 @@ func winnow(sel *Selection, m Matcher, keep bool) []*html.Node {
// Filter based on an array of nodes, and the indicator to keep (Filter) or
// to get rid of (Not) the matching elements.
func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node {
if len(nodes)+len(sel.Nodes) < minNodesForSet {
return grep(sel, func(i int, s *Selection) bool {
return isInSlice(nodes, s.Get(0)) == keep
})
}
set := make(map[*html.Node]bool)
for _, n := range nodes {
set[n] = true
}
return grep(sel, func(i int, s *Selection) bool {
return isInSlice(nodes, s.Get(0)) == keep
return set[s.Get(0)] == keep
})
}
+15
View File
@@ -14,6 +14,11 @@ func TestFilterNone(t *testing.T) {
assertLength(t, sel.Nodes, 0)
}
func TestFilterInvalid(t *testing.T) {
sel := Doc().Find(".span12").Filter("")
assertLength(t, sel.Nodes, 0)
}
func TestFilterRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Filter(".alert").End()
@@ -74,6 +79,11 @@ func TestNot(t *testing.T) {
assertLength(t, sel.Nodes, 1)
}
func TestNotInvalid(t *testing.T) {
sel := Doc().Find(".span12").Not("")
assertLength(t, sel.Nodes, 2)
}
func TestNotRollback(t *testing.T) {
sel := Doc().Find(".span12")
sel2 := sel.Not(".alert").End()
@@ -145,6 +155,11 @@ func TestHas(t *testing.T) {
// Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content
}
func TestHasInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").Has("")
assertLength(t, sel.Nodes, 0)
}
func TestHasRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.Has(".center-content").End()
+8
View File
@@ -0,0 +1,8 @@
module github.com/PuerkitoBio/goquery
require (
github.com/andybalholm/cascadia v1.1.0
golang.org/x/net v0.0.0-20200202094626-16171245cfb2
)
go 1.13
+8
View File
@@ -0,0 +1,8 @@
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+8 -2
View File
@@ -1,7 +1,10 @@
package goquery
// Each iterates over a Selection object, executing a function for each
// matched element. It returns the current Selection object.
// matched element. It returns the current Selection object. The function
// f is called for each element in the selection with the index of the
// element in that selection starting at 0, and a *Selection that contains
// only that element.
func (s *Selection) Each(f func(int, *Selection)) *Selection {
for i, n := range s.Nodes {
f(i, newSingleSelection(n, s.document))
@@ -23,7 +26,10 @@ func (s *Selection) EachWithBreak(f func(int, *Selection) bool) *Selection {
}
// Map passes each element in the current matched set through a function,
// producing a slice of string holding the returned values.
// producing a slice of string holding the returned values. The function
// f is called for each element in the selection with the index of the
// element in that selection starting at 0, and a *Selection that contains
// only that element.
func (s *Selection) Map(f func(int, *Selection) string) (result []string) {
for i, n := range s.Nodes {
result = append(result, f(i, newSingleSelection(n, s.document)))
+152 -30
View File
@@ -3,7 +3,6 @@ package goquery
import (
"strings"
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
@@ -15,7 +14,7 @@ import (
//
// This follows the same rules as Selection.Append.
func (s *Selection) After(selector string) *Selection {
return s.AfterMatcher(cascadia.MustCompile(selector))
return s.AfterMatcher(compileMatcher(selector))
}
// AfterMatcher applies the matcher from the root document and inserts the matched elements
@@ -40,8 +39,14 @@ func (s *Selection) AfterSelection(sel *Selection) *Selection {
// AfterHtml parses the html and inserts it after the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) AfterHtml(html string) *Selection {
return s.AfterNodes(parseHtml(html)...)
func (s *Selection) AfterHtml(htmlStr string) *Selection {
return s.eachNodeHtml(htmlStr, true, func(node *html.Node, nodes []*html.Node) {
for _, n := range nodes {
if node.Parent != nil {
node.Parent.InsertBefore(n, node.NextSibling)
}
}
})
}
// AfterNodes inserts the nodes after each element in the set of matched elements.
@@ -66,7 +71,7 @@ func (s *Selection) AfterNodes(ns ...*html.Node) *Selection {
// appended to all target locations except the last one, which will be moved
// as noted in (2).
func (s *Selection) Append(selector string) *Selection {
return s.AppendMatcher(cascadia.MustCompile(selector))
return s.AppendMatcher(compileMatcher(selector))
}
// AppendMatcher appends the elements specified by the matcher to the end of each element
@@ -86,8 +91,12 @@ func (s *Selection) AppendSelection(sel *Selection) *Selection {
}
// AppendHtml parses the html and appends it to the set of matched elements.
func (s *Selection) AppendHtml(html string) *Selection {
return s.AppendNodes(parseHtml(html)...)
func (s *Selection) AppendHtml(htmlStr string) *Selection {
return s.eachNodeHtml(htmlStr, false, func(node *html.Node, nodes []*html.Node) {
for _, n := range nodes {
node.AppendChild(n)
}
})
}
// AppendNodes appends the specified nodes to each node in the set of matched elements.
@@ -103,7 +112,7 @@ func (s *Selection) AppendNodes(ns ...*html.Node) *Selection {
//
// This follows the same rules as Selection.Append.
func (s *Selection) Before(selector string) *Selection {
return s.BeforeMatcher(cascadia.MustCompile(selector))
return s.BeforeMatcher(compileMatcher(selector))
}
// BeforeMatcher inserts the matched elements before each element in the set of matched elements.
@@ -124,8 +133,14 @@ func (s *Selection) BeforeSelection(sel *Selection) *Selection {
// BeforeHtml parses the html and inserts it before the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) BeforeHtml(html string) *Selection {
return s.BeforeNodes(parseHtml(html)...)
func (s *Selection) BeforeHtml(htmlStr string) *Selection {
return s.eachNodeHtml(htmlStr, true, func(node *html.Node, nodes []*html.Node) {
for _, n := range nodes {
if node.Parent != nil {
node.Parent.InsertBefore(n, node)
}
}
})
}
// BeforeNodes inserts the nodes before each element in the set of matched elements.
@@ -165,7 +180,7 @@ func (s *Selection) Empty() *Selection {
// Prepend prepends the elements specified by the selector to each element in
// the set of matched elements, following the same rules as Append.
func (s *Selection) Prepend(selector string) *Selection {
return s.PrependMatcher(cascadia.MustCompile(selector))
return s.PrependMatcher(compileMatcher(selector))
}
// PrependMatcher prepends the elements specified by the matcher to each
@@ -185,8 +200,12 @@ func (s *Selection) PrependSelection(sel *Selection) *Selection {
}
// PrependHtml parses the html and prepends it to the set of matched elements.
func (s *Selection) PrependHtml(html string) *Selection {
return s.PrependNodes(parseHtml(html)...)
func (s *Selection) PrependHtml(htmlStr string) *Selection {
return s.eachNodeHtml(htmlStr, false, func(node *html.Node, nodes []*html.Node) {
for _, n := range nodes {
node.InsertBefore(n, node.FirstChild)
}
})
}
// PrependNodes prepends the specified nodes to each node in the set of
@@ -213,14 +232,19 @@ func (s *Selection) Remove() *Selection {
return s
}
// RemoveFiltered removes the set of matched elements by selector.
// It returns the Selection of removed nodes.
// RemoveFiltered removes from the current set of matched elements those that
// match the selector filter. It returns the Selection of removed nodes.
//
// For example if the selection s contains "<h1>", "<h2>" and "<h3>"
// and s.RemoveFiltered("h2") is called, only the "<h2>" node is removed
// (and returned), while "<h1>" and "<h3>" are kept in the document.
func (s *Selection) RemoveFiltered(selector string) *Selection {
return s.RemoveMatcher(cascadia.MustCompile(selector))
return s.RemoveMatcher(compileMatcher(selector))
}
// RemoveMatcher removes the set of matched elements.
// It returns the Selection of removed nodes.
// RemoveMatcher removes from the current set of matched elements those that
// match the Matcher filter. It returns the Selection of removed nodes.
// See RemoveFiltered for additional information.
func (s *Selection) RemoveMatcher(m Matcher) *Selection {
return s.FilterMatcher(m).Remove()
}
@@ -231,7 +255,7 @@ func (s *Selection) RemoveMatcher(m Matcher) *Selection {
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWith(selector string) *Selection {
return s.ReplaceWithMatcher(cascadia.MustCompile(selector))
return s.ReplaceWithMatcher(compileMatcher(selector))
}
// ReplaceWithMatcher replaces each element in the set of matched elements with
@@ -257,8 +281,15 @@ func (s *Selection) ReplaceWithSelection(sel *Selection) *Selection {
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWithHtml(html string) *Selection {
return s.ReplaceWithNodes(parseHtml(html)...)
func (s *Selection) ReplaceWithHtml(htmlStr string) *Selection {
s.eachNodeHtml(htmlStr, true, func(node *html.Node, nodes []*html.Node) {
for _, n := range nodes {
if node.Parent != nil {
node.Parent.InsertBefore(n, node.NextSibling)
}
}
})
return s.Remove()
}
// ReplaceWithNodes replaces each element in the set of matched elements with
@@ -271,6 +302,27 @@ func (s *Selection) ReplaceWithNodes(ns ...*html.Node) *Selection {
return s.Remove()
}
// SetHtml sets the html content of each element in the selection to
// specified html string.
func (s *Selection) SetHtml(htmlStr string) *Selection {
for _, context := range s.Nodes {
for c := context.FirstChild; c != nil; c = context.FirstChild {
context.RemoveChild(c)
}
}
return s.eachNodeHtml(htmlStr, true, func(node *html.Node, nodes []*html.Node) {
for _, n := range nodes {
node.AppendChild(n)
}
})
}
// SetText sets the content of each element in the selection to specified content.
// The provided text string is escaped.
func (s *Selection) SetText(text string) *Selection {
return s.SetHtml(html.EscapeString(text))
}
// Unwrap removes the parents of the set of matched elements, leaving the matched
// elements (and their siblings, if any) in their place.
// It returns the original selection.
@@ -293,7 +345,7 @@ func (s *Selection) Unwrap() *Selection {
//
// It returns the original set of elements.
func (s *Selection) Wrap(selector string) *Selection {
return s.WrapMatcher(cascadia.MustCompile(selector))
return s.WrapMatcher(compileMatcher(selector))
}
// WrapMatcher wraps each element in the set of matched elements inside the
@@ -318,8 +370,23 @@ func (s *Selection) WrapSelection(sel *Selection) *Selection {
// most child of the given HTML.
//
// It returns the original set of elements.
func (s *Selection) WrapHtml(html string) *Selection {
return s.wrapNodes(parseHtml(html)...)
func (s *Selection) WrapHtml(htmlStr string) *Selection {
nodesMap := make(map[html.NodeType][]*html.Node)
for _, context := range s.Nodes {
var parent *html.Node
if context.Parent != nil {
parent = context.Parent
} else {
parent = &html.Node{Type: html.ElementNode}
}
nodes, found := nodesMap[parent.Type]
if !found {
nodes = parseHtmlWithContext(htmlStr, parent)
nodesMap[parent.Type] = nodes
}
newSingleSelection(context, s.document).wrapAllNodes(cloneNodes(nodes)...)
}
return s
}
// WrapNode wraps each element in the set of matched elements inside the inner-
@@ -345,7 +412,7 @@ func (s *Selection) wrapNodes(ns ...*html.Node) *Selection {
//
// It returns the original set of elements.
func (s *Selection) WrapAll(selector string) *Selection {
return s.WrapAllMatcher(cascadia.MustCompile(selector))
return s.WrapAllMatcher(compileMatcher(selector))
}
// WrapAllMatcher wraps a single HTML structure, matched by the given Matcher,
@@ -371,8 +438,18 @@ func (s *Selection) WrapAllSelection(sel *Selection) *Selection {
// document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllHtml(html string) *Selection {
return s.wrapAllNodes(parseHtml(html)...)
func (s *Selection) WrapAllHtml(htmlStr string) *Selection {
var context *html.Node
var nodes []*html.Node
if len(s.Nodes) > 0 {
context = s.Nodes[0]
if context.Parent != nil {
nodes = parseHtmlWithContext(htmlStr, context)
} else {
nodes = parseHtml(htmlStr)
}
}
return s.wrapAllNodes(nodes...)
}
func (s *Selection) wrapAllNodes(ns ...*html.Node) *Selection {
@@ -415,7 +492,7 @@ func (s *Selection) WrapAllNode(n *html.Node) *Selection {
//
// It returns the original set of elements.
func (s *Selection) WrapInner(selector string) *Selection {
return s.WrapInnerMatcher(cascadia.MustCompile(selector))
return s.WrapInnerMatcher(compileMatcher(selector))
}
// WrapInnerMatcher wraps an HTML structure, matched by the given selector,
@@ -441,8 +518,17 @@ func (s *Selection) WrapInnerSelection(sel *Selection) *Selection {
// cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInnerHtml(html string) *Selection {
return s.wrapInnerNodes(parseHtml(html)...)
func (s *Selection) WrapInnerHtml(htmlStr string) *Selection {
nodesMap := make(map[html.NodeType][]*html.Node)
for _, context := range s.Nodes {
nodes, found := nodesMap[context.Type]
if !found {
nodes = parseHtmlWithContext(htmlStr, context)
nodesMap[context.Type] = nodes
}
newSingleSelection(context, s.document).wrapInnerNodes(cloneNodes(nodes)...)
}
return s
}
// WrapInnerNode wraps an HTML structure, matched by the given selector, around
@@ -482,6 +568,16 @@ func parseHtml(h string) []*html.Node {
return nodes
}
func parseHtmlWithContext(h string, context *html.Node) []*html.Node {
// Errors are only returned when the io.Reader returns any error besides
// EOF, but strings.Reader never will
nodes, err := html.ParseFragment(strings.NewReader(h), context)
if err != nil {
panic("goquery: failed to parse HTML: " + err.Error())
}
return nodes
}
// Get the first child that is an ElementNode
func getFirstChildEl(n *html.Node) *html.Node {
c := n.FirstChild
@@ -549,3 +645,29 @@ func (s *Selection) manipulateNodes(ns []*html.Node, reverse bool,
return s
}
// eachNodeHtml parses the given html string and inserts the resulting nodes in the dom with the mergeFn.
// The parsed nodes are inserted for each element of the selection.
// isParent can be used to indicate that the elements of the selection should be treated as the parent for the parsed html.
// A cache is used to avoid parsing the html multiple times should the elements of the selection result in the same context.
func (s *Selection) eachNodeHtml(htmlStr string, isParent bool, mergeFn func(n *html.Node, nodes []*html.Node)) *Selection {
// cache to avoid parsing the html for the same context multiple times
nodeCache := make(map[string][]*html.Node)
var context *html.Node
for _, n := range s.Nodes {
if isParent {
context = n.Parent
} else {
context = n
}
if context != nil {
nodes, found := nodeCache[nodeName(context)]
if !found {
nodes = parseHtmlWithContext(htmlStr, context)
nodeCache[nodeName(context)] = nodes
}
mergeFn(n, cloneNodes(nodes))
}
}
return s
}
+236
View File
@@ -56,6 +56,25 @@ func TestAfterHtml(t *testing.T) {
printSel(t, doc.Selection)
}
func TestAfterHtmlContext(t *testing.T) {
doc := loadString(t, `
<html>
<body>
<table>
<tr>
<td>Before1</td>
</tr>
<tr>
<td>Before2</td>
</tr>
</table>
</body>
</html>`)
doc.Find("table tr td").AfterHtml("<td>Test</td>")
assertLength(t, doc.Find("table tr td").Nodes, 4)
printSel(t, doc.Selection)
}
func TestAppend(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").Append("#nf6")
@@ -113,6 +132,27 @@ func TestAppendHtml(t *testing.T) {
printSel(t, doc.Selection)
}
func TestAppendHtmlContext(t *testing.T) {
doc := loadString(t, `
<html>
<body>
<table>
<tr>
<td>Before1</td>
</tr>
<tr>
<td>Before2</td>
</tr>
</table>
</body>
</html>`)
doc.Find("table tr").AppendHtml("<td class='new-node'>new node</td>")
assertLength(t, doc.Find("table td").Nodes, 4)
assertClass(t, doc.Find("table td").Last(), "new-node")
printSel(t, doc.Selection)
}
func TestBefore(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").Before("#nf6")
@@ -151,6 +191,27 @@ func TestBeforeHtml(t *testing.T) {
printSel(t, doc.Selection)
}
func TestBeforeHtmlContext(t *testing.T) {
doc := loadString(t, `
<html>
<body>
<table>
<tr>
<td>Before1</td>
</tr>
<tr>
<td>Before2</td>
</tr>
</table>
</body>
</html>`)
doc.Find("table tr td:first-child").BeforeHtml("<td class='new-node'>new node</td>")
assertLength(t, doc.Find("table td").Nodes, 4)
assertClass(t, doc.Find("table td").First(), "new-node")
printSel(t, doc.Selection)
}
func TestEmpty(t *testing.T) {
doc := Doc2Clone()
s := doc.Find("#main").Empty()
@@ -218,6 +279,27 @@ func TestPrependHtml(t *testing.T) {
printSel(t, doc.Selection)
}
func TestPrependHtmlContext(t *testing.T) {
doc := loadString(t, `
<html>
<body>
<table>
<tr>
<td>Before1</td>
</tr>
<tr>
<td>Before2</td>
</tr>
</table>
</body>
</html>`)
doc.Find("table tr").PrependHtml("<td class='c1'>new node</td><td class='c2'>other new node</td>")
assertLength(t, doc.Find("table td").Nodes, 6)
assertClass(t, doc.Find("table tr td").First(), "c1")
printSel(t, doc.Selection)
}
func TestRemove(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").Remove()
@@ -278,6 +360,109 @@ func TestReplaceWithHtml(t *testing.T) {
printSel(t, doc.Selection)
}
func TestReplaceWithHtmlContext(t *testing.T) {
doc := loadString(t, `
<html>
<body>
<table>
<tr>
<th>Before1</th>
</tr>
<tr>
<th>Before2</th>
</tr>
</table>
</body>
</html>`)
doc.Find("table th").ReplaceWithHtml("<td>Test</td><td>Replace</td>")
assertLength(t, doc.Find("table th").Nodes, 0)
assertLength(t, doc.Find("table tr td").Nodes, 4)
printSel(t, doc.Selection)
}
func TestSetHtml(t *testing.T) {
doc := Doc2Clone()
q := doc.Find("#main, #foot")
q.SetHtml(`<div id="replace">test</div>`)
assertLength(t, doc.Find("#replace").Nodes, 2)
assertLength(t, doc.Find("#main, #foot").Nodes, 2)
if q.Text() != "testtest" {
t.Errorf("Expected text to be %v, found %v", "testtest", q.Text())
}
printSel(t, doc.Selection)
}
func TestSetHtmlNoMatch(t *testing.T) {
doc := Doc2Clone()
q := doc.Find("#notthere")
q.SetHtml(`<div id="replace">test</div>`)
assertLength(t, doc.Find("#replace").Nodes, 0)
printSel(t, doc.Selection)
}
func TestSetHtmlEmpty(t *testing.T) {
doc := Doc2Clone()
q := doc.Find("#main")
q.SetHtml(``)
assertLength(t, doc.Find("#main").Nodes, 1)
assertLength(t, doc.Find("#main").Children().Nodes, 0)
printSel(t, doc.Selection)
}
func TestSetHtmlContext(t *testing.T) {
doc := loadString(t, `
<html>
<body>
<table>
<tr>
<th>Before1</th>
</tr>
<tr>
<th>Before2</th>
</tr>
</table>
</body>
</html>`)
doc.Find("table tr").SetHtml("<td class='c1'>Test</td><td class='c2'>Again</td>")
assertLength(t, doc.Find("table th").Nodes, 0)
assertLength(t, doc.Find("table td").Nodes, 4)
assertLength(t, doc.Find("table t2").Nodes, 2)
printSel(t, doc.Selection)
}
func TestSetText(t *testing.T) {
doc := Doc2Clone()
q := doc.Find("#main, #foot")
repl := "<div id=\"replace\">test</div>"
q.SetText(repl)
assertLength(t, doc.Find("#replace").Nodes, 0)
assertLength(t, doc.Find("#main, #foot").Nodes, 2)
if q.Text() != (repl + repl) {
t.Errorf("Expected text to be %v, found %v", (repl + repl), q.Text())
}
h, err := q.Html()
if err != nil {
t.Errorf("Error: %v", err)
}
esc := "&lt;div id=&#34;replace&#34;&gt;test&lt;/div&gt;"
if h != esc {
t.Errorf("Expected html to be %v, found %v", esc, h)
}
printSel(t, doc.Selection)
}
func TestReplaceWithSelection(t *testing.T) {
doc := Doc2Clone()
sel := doc.Find("#nf6").ReplaceWithSelection(doc.Find("#nf5"))
@@ -451,3 +636,54 @@ func TestWrapInnerHtml(t *testing.T) {
printSel(t, doc.Selection)
}
func TestParsingRespectsVaryingContext(t *testing.T) {
docA := loadString(t, `
<html>
<body>
<a class="x"></a>
</body>
</html>`)
docTable := loadString(t, `
<html>
<body>
<table class="x"></table>
</body>
</html>`)
docBoth := loadString(t, `
<html>
<body>
<table class="x"></table>
<a class="x"></a>
</body>
</html>`)
sA := docA.Find(".x").AppendHtml("<tr><td>Hello</td></tr>")
sTable := docTable.Find(".x").AppendHtml("<tr><td>Hello</td></tr>")
sBoth := docBoth.Find(".x").AppendHtml("<tr><td>Hello</td></tr>")
printSel(t, docA.Selection)
printSel(t, docTable.Selection)
printSel(t, docBoth.Selection)
oA, _ := sA.Html()
oTable, _ := sTable.Html()
if oA == oTable {
t.Errorf("Expected inner html of <a> and <table> to not be equal, but got %s and %s", oA, oTable)
}
oBothTable, _ := sBoth.First().Html()
if oBothTable != oTable {
t.Errorf("Expected inner html of <table> and <table> in doc containing both tags to be equal, but got %s and %s",
oTable,
oBothTable)
}
oBothA, _ := sBoth.Last().Html()
if oBothA != oA {
t.Errorf("Expected inner html of <a> and <a> in doc containing both tags to be equal, but got %s and %s",
oA,
oBothA)
}
}
+27 -27
View File
@@ -1,37 +1,37 @@
#!/bin/sh
# Copyright 2012 The Go Authors. All rights reserved.
# Use of this source code is governed by a BSD-style
# license that can be found in the LICENSE file.
# git gofmt pre-commit hook
#
# To use, store as .git/hooks/pre-commit inside your repository and make sure
# it has execute permissions.
#
# This script does not handle file names that contain spaces.
echo ">>> golint"
for dir in $(go list ./... | grep -v /vendor/)
do
golint "${dir}"
done
echo "<<< golint"
echo
# golint is purely informational, it doesn't fail with exit code != 0 if it finds something,
# because it may find a lot of false positives. Just print out its result for information.
echo "lint result (informational only):"
golint .
echo ">>> go vet"
go vet $(go list ./... | grep -v /vendor/)
echo "<<< go vet"
echo
# go vet returns 1 if an error was found. Exit the hook with this exit code.
go vet ./...
vetres=$?
echo ">>> gosimple"
gosimple $(go list ./... | grep -v /vendor/)
echo "<<< gosimple"
echo
# Check for gofmt problems and report if any.
gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$')
gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$' | grep -v /vendor/)
[ -z "$gofiles" ] && echo "EXIT $vetres" && exit $vetres
unformatted=$(gofmt -l $gofiles)
[ -z "$unformatted" ] && echo "EXIT $vetres" && exit $vetres
if [ -n "$gofiles" ]; then
unformatted=$(gofmt -l $gofiles)
# Some files are not gofmt'd. Print message and fail.
if [ -n "$unformatted" ]; then
# Some files are not gofmt'd.
echo >&2 "Go files must be formatted with gofmt. Please run:"
for fn in $unformatted; do
echo >&2 " gofmt -w $PWD/$fn"
done
fi
fi
echo
echo >&2 "Go files must be formatted with gofmt. Please run:"
for fn in $unformatted; do
echo >&2 " gofmt -w $PWD/$fn"
done
echo "EXIT 1"
exit 1
+18 -21
View File
@@ -63,9 +63,22 @@ func (s *Selection) Text() string {
var buf bytes.Buffer
// Slightly optimized vs calling Each: no single selection object created
for _, n := range s.Nodes {
buf.WriteString(getNodeText(n))
var f func(*html.Node)
f = func(n *html.Node) {
if n.Type == html.TextNode {
// Keep newlines and spaces, like jQuery
buf.WriteString(n.Data)
}
if n.FirstChild != nil {
for c := n.FirstChild; c != nil; c = c.NextSibling {
f(c)
}
}
}
for _, n := range s.Nodes {
f(n)
}
return buf.String()
}
@@ -112,7 +125,7 @@ func (s *Selection) AddClass(class ...string) *Selection {
for _, n := range s.Nodes {
curClasses, attr := getClassesAndAttr(n, true)
for _, newClass := range tcls {
if strings.Index(curClasses, " "+newClass+" ") == -1 {
if !strings.Contains(curClasses, " "+newClass+" ") {
curClasses += newClass + " "
}
}
@@ -129,7 +142,7 @@ func (s *Selection) HasClass(class string) bool {
class = " " + class + " "
for _, n := range s.Nodes {
classes, _ := getClassesAndAttr(n, false)
if strings.Index(classes, class) > -1 {
if strings.Contains(classes, class) {
return true
}
}
@@ -179,7 +192,7 @@ func (s *Selection) ToggleClass(class ...string) *Selection {
for _, n := range s.Nodes {
classes, attr := getClassesAndAttr(n, true)
for _, tcl := range tcls {
if strings.Index(classes, " "+tcl+" ") != -1 {
if strings.Contains(classes, " "+tcl+" ") {
classes = strings.Replace(classes, " "+tcl+" ", " ", -1)
} else {
classes += tcl + " "
@@ -192,22 +205,6 @@ func (s *Selection) ToggleClass(class ...string) *Selection {
return s
}
// Get the specified node's text content.
func getNodeText(node *html.Node) string {
if node.Type == html.TextNode {
// Keep newlines and spaces, like jQuery
return node.Data
} else if node.FirstChild != nil {
var buf bytes.Buffer
for c := node.FirstChild; c != nil; c = c.NextSibling {
buf.WriteString(getNodeText(c))
}
return buf.String()
}
return ""
}
func getAttributePtr(attrName string, n *html.Node) *html.Attribute {
if n == nil {
return nil
+2 -9
View File
@@ -1,18 +1,11 @@
package goquery
import (
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
import "golang.org/x/net/html"
// Is checks the current matched set of elements against a selector and
// returns true if at least one of these elements matches.
func (s *Selection) Is(selector string) bool {
if len(s.Nodes) > 0 {
return s.IsMatcher(cascadia.MustCompile(selector))
}
return false
return s.IsMatcher(compileMatcher(selector))
}
// IsMatcher checks the current matched set of elements against a matcher and
+7
View File
@@ -11,6 +11,13 @@ func TestIs(t *testing.T) {
}
}
func TestIsInvalid(t *testing.T) {
sel := Doc().Find(".footer p:nth-child(1)")
if sel.Is("") {
t.Error("Is should not succeed with invalid selector string")
}
}
func TestIsPositional(t *testing.T) {
sel := Doc().Find(".footer p:nth-child(2)")
if !sel.Is("p:nth-child(2)") {
+31 -29
View File
@@ -1,9 +1,6 @@
package goquery
import (
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
import "golang.org/x/net/html"
type siblingType int
@@ -24,7 +21,7 @@ const (
// elements, filtered by a selector. It returns a new Selection object
// containing these matched elements.
func (s *Selection) Find(selector string) *Selection {
return pushStack(s, findWithMatcher(s.Nodes, cascadia.MustCompile(selector)))
return pushStack(s, findWithMatcher(s.Nodes, compileMatcher(selector)))
}
// FindMatcher gets the descendants of each element in the current set of matched
@@ -93,7 +90,7 @@ func (s *Selection) Children() *Selection {
// filtered by the specified selector. It returns a new
// Selection object containing these elements.
func (s *Selection) ChildrenFiltered(selector string) *Selection {
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), cascadia.MustCompile(selector))
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), compileMatcher(selector))
}
// ChildrenMatcher gets the child elements of each element in the Selection,
@@ -112,7 +109,7 @@ func (s *Selection) Parent() *Selection {
// ParentFiltered gets the parent of each element in the Selection filtered by a
// selector. It returns a new Selection object containing the matched elements.
func (s *Selection) ParentFiltered(selector string) *Selection {
return filterAndPush(s, getParentNodes(s.Nodes), cascadia.MustCompile(selector))
return filterAndPush(s, getParentNodes(s.Nodes), compileMatcher(selector))
}
// ParentMatcher gets the parent of each element in the Selection filtered by a
@@ -124,7 +121,7 @@ func (s *Selection) ParentMatcher(m Matcher) *Selection {
// Closest gets the first element that matches the selector by testing the
// element itself and traversing up through its ancestors in the DOM tree.
func (s *Selection) Closest(selector string) *Selection {
cs := cascadia.MustCompile(selector)
cs := compileMatcher(selector)
return s.ClosestMatcher(cs)
}
@@ -146,11 +143,15 @@ func (s *Selection) ClosestMatcher(m Matcher) *Selection {
// ClosestNodes gets the first element that matches one of the nodes by testing the
// element itself and traversing up through its ancestors in the DOM tree.
func (s *Selection) ClosestNodes(nodes ...*html.Node) *Selection {
set := make(map[*html.Node]bool)
for _, n := range nodes {
set[n] = true
}
return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
// For each node in the selection, test the node itself, then each parent
// until a match is found.
for ; n != nil; n = n.Parent {
if isInSlice(nodes, n) {
if set[n] {
return []*html.Node{n}
}
}
@@ -177,7 +178,7 @@ func (s *Selection) Parents() *Selection {
// ParentsFiltered gets the ancestors of each element in the current
// Selection. It returns a new Selection object with the matched elements.
func (s *Selection) ParentsFiltered(selector string) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), cascadia.MustCompile(selector))
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), compileMatcher(selector))
}
// ParentsMatcher gets the ancestors of each element in the current
@@ -190,7 +191,7 @@ func (s *Selection) ParentsMatcher(m Matcher) *Selection {
// not including the element matched by the selector. It returns a new Selection
// object containing the matched elements.
func (s *Selection) ParentsUntil(selector string) *Selection {
return pushStack(s, getParentsNodes(s.Nodes, cascadia.MustCompile(selector), nil))
return pushStack(s, getParentsNodes(s.Nodes, compileMatcher(selector), nil))
}
// ParentsUntilMatcher gets the ancestors of each element in the Selection, up to but
@@ -221,7 +222,7 @@ func (s *Selection) ParentsUntilNodes(nodes ...*html.Node) *Selection {
// results based on a selector string. It returns a new Selection
// object containing the matched elements.
func (s *Selection) ParentsFilteredUntil(filterSelector, untilSelector string) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, cascadia.MustCompile(untilSelector), nil), cascadia.MustCompile(filterSelector))
return filterAndPush(s, getParentsNodes(s.Nodes, compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
}
// ParentsFilteredUntilMatcher is like ParentsUntilMatcher, with the option to filter the
@@ -234,7 +235,7 @@ func (s *Selection) ParentsFilteredUntilMatcher(filter, until Matcher) *Selectio
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) ParentsFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
return s.ParentsMatcherUntilSelection(cascadia.MustCompile(filterSelector), sel)
return s.ParentsMatcherUntilSelection(compileMatcher(filterSelector), sel)
}
// ParentsMatcherUntilSelection is like ParentsUntilSelection, with the
@@ -251,7 +252,7 @@ func (s *Selection) ParentsMatcherUntilSelection(filter Matcher, sel *Selection)
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) ParentsFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), cascadia.MustCompile(filterSelector))
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), compileMatcher(filterSelector))
}
// ParentsMatcherUntilNodes is like ParentsUntilNodes, with the
@@ -271,7 +272,7 @@ func (s *Selection) Siblings() *Selection {
// filtered by a selector. It returns a new Selection object containing the
// matched elements.
func (s *Selection) SiblingsFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), cascadia.MustCompile(selector))
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), compileMatcher(selector))
}
// SiblingsMatcher gets the siblings of each element in the Selection
@@ -291,7 +292,7 @@ func (s *Selection) Next() *Selection {
// Selection filtered by a selector. It returns a new Selection object
// containing the matched elements.
func (s *Selection) NextFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), cascadia.MustCompile(selector))
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), compileMatcher(selector))
}
// NextMatcher gets the immediately following sibling of each element in the
@@ -311,7 +312,7 @@ func (s *Selection) NextAll() *Selection {
// Selection filtered by a selector. It returns a new Selection object
// containing the matched elements.
func (s *Selection) NextAllFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), cascadia.MustCompile(selector))
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), compileMatcher(selector))
}
// NextAllMatcher gets all the following siblings of each element in the
@@ -331,7 +332,7 @@ func (s *Selection) Prev() *Selection {
// Selection filtered by a selector. It returns a new Selection object
// containing the matched elements.
func (s *Selection) PrevFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), cascadia.MustCompile(selector))
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), compileMatcher(selector))
}
// PrevMatcher gets the immediately preceding sibling of each element in the
@@ -351,7 +352,7 @@ func (s *Selection) PrevAll() *Selection {
// Selection filtered by a selector. It returns a new Selection object
// containing the matched elements.
func (s *Selection) PrevAllFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), cascadia.MustCompile(selector))
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), compileMatcher(selector))
}
// PrevAllMatcher gets all the preceding siblings of each element in the
@@ -366,7 +367,7 @@ func (s *Selection) PrevAllMatcher(m Matcher) *Selection {
// object containing the matched elements.
func (s *Selection) NextUntil(selector string) *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
cascadia.MustCompile(selector), nil))
compileMatcher(selector), nil))
}
// NextUntilMatcher gets all following siblings of each element up to but not
@@ -400,7 +401,7 @@ func (s *Selection) NextUntilNodes(nodes ...*html.Node) *Selection {
// object containing the matched elements.
func (s *Selection) PrevUntil(selector string) *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
cascadia.MustCompile(selector), nil))
compileMatcher(selector), nil))
}
// PrevUntilMatcher gets all preceding siblings of each element up to but not
@@ -434,7 +435,7 @@ func (s *Selection) PrevUntilNodes(nodes ...*html.Node) *Selection {
// It returns a new Selection object containing the matched elements.
func (s *Selection) NextFilteredUntil(filterSelector, untilSelector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
cascadia.MustCompile(untilSelector), nil), cascadia.MustCompile(filterSelector))
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
}
// NextFilteredUntilMatcher is like NextUntilMatcher, with the option to filter
@@ -449,7 +450,7 @@ func (s *Selection) NextFilteredUntilMatcher(filter, until Matcher) *Selection {
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) NextFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
return s.NextMatcherUntilSelection(cascadia.MustCompile(filterSelector), sel)
return s.NextMatcherUntilSelection(compileMatcher(filterSelector), sel)
}
// NextMatcherUntilSelection is like NextUntilSelection, with the
@@ -467,7 +468,7 @@ func (s *Selection) NextMatcherUntilSelection(filter Matcher, sel *Selection) *S
// Selection object containing the matched elements.
func (s *Selection) NextFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
nil, nodes), cascadia.MustCompile(filterSelector))
nil, nodes), compileMatcher(filterSelector))
}
// NextMatcherUntilNodes is like NextUntilNodes, with the
@@ -483,7 +484,7 @@ func (s *Selection) NextMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *
// It returns a new Selection object containing the matched elements.
func (s *Selection) PrevFilteredUntil(filterSelector, untilSelector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
cascadia.MustCompile(untilSelector), nil), cascadia.MustCompile(filterSelector))
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
}
// PrevFilteredUntilMatcher is like PrevUntilMatcher, with the option to filter
@@ -498,7 +499,7 @@ func (s *Selection) PrevFilteredUntilMatcher(filter, until Matcher) *Selection {
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) PrevFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
return s.PrevMatcherUntilSelection(cascadia.MustCompile(filterSelector), sel)
return s.PrevMatcherUntilSelection(compileMatcher(filterSelector), sel)
}
// PrevMatcherUntilSelection is like PrevUntilSelection, with the
@@ -516,7 +517,7 @@ func (s *Selection) PrevMatcherUntilSelection(filter Matcher, sel *Selection) *S
// Selection object containing the matched elements.
func (s *Selection) PrevFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
nil, nodes), cascadia.MustCompile(filterSelector))
nil, nodes), compileMatcher(filterSelector))
}
// PrevMatcherUntilNodes is like PrevUntilNodes, with the
@@ -687,10 +688,11 @@ func getParentNodes(nodes []*html.Node) []*html.Node {
// Returns an array of nodes mapped by calling the callback function once for
// each node in the source nodes.
func mapNodes(nodes []*html.Node, f func(int, *html.Node) []*html.Node) (result []*html.Node) {
set := make(map[*html.Node]bool)
for i, n := range nodes {
if vals := f(i, n); len(vals) > 0 {
result = appendWithoutDuplicates(result, vals)
result = appendWithoutDuplicates(result, vals, set)
}
}
return
return result
}
+99 -3
View File
@@ -21,9 +21,19 @@ func TestFindNotSelf(t *testing.T) {
assertLength(t, sel.Nodes, 0)
}
func TestFindInvalidSelector(t *testing.T) {
defer assertPanic(t)
Doc().Find(":+ ^")
func TestFindInvalid(t *testing.T) {
sel := Doc().Find(":+ ^")
assertLength(t, sel.Nodes, 0)
}
func TestFindBig(t *testing.T) {
doc := DocW()
sel := doc.Find("li")
assertLength(t, sel.Nodes, 373)
sel2 := doc.Find("span")
assertLength(t, sel2.Nodes, 448)
sel3 := sel.FindSelection(sel2)
assertLength(t, sel3.Nodes, 248)
}
func TestChainedFind(t *testing.T) {
@@ -31,6 +41,11 @@ func TestChainedFind(t *testing.T) {
assertLength(t, sel.Nodes, 4)
}
func TestChainedFindInvalid(t *testing.T) {
sel := Doc().Find("div.hero-unit").Find("")
assertLength(t, sel.Nodes, 0)
}
func TestChildren(t *testing.T) {
sel := Doc().Find(".pvk-content").Children()
assertLength(t, sel.Nodes, 5)
@@ -58,6 +73,11 @@ func TestChildrenFiltered(t *testing.T) {
assertLength(t, sel.Nodes, 1)
}
func TestChildrenFilteredInvalid(t *testing.T) {
sel := Doc().Find(".pvk-content").ChildrenFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestChildrenFilteredRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.ChildrenFiltered(".hero-unit").End()
@@ -69,6 +89,11 @@ func TestContentsFiltered(t *testing.T) {
assertLength(t, sel.Nodes, 1)
}
func TestContentsFilteredInvalid(t *testing.T) {
sel := Doc().Find(".pvk-content").ContentsFiltered("~")
assertLength(t, sel.Nodes, 0)
}
func TestContentsFilteredRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.ContentsFiltered(".hero-unit").End()
@@ -102,6 +127,11 @@ func TestParentFiltered(t *testing.T) {
assertClass(t, sel, "hero-unit")
}
func TestParentFilteredInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestParentFilteredRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ParentFiltered(".hero-unit").End()
@@ -130,6 +160,11 @@ func TestParentsFiltered(t *testing.T) {
assertLength(t, sel.Nodes, 1)
}
func TestParentsFilteredInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestParentsFilteredRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ParentsFiltered("body").End()
@@ -141,6 +176,11 @@ func TestParentsUntil(t *testing.T) {
assertLength(t, sel.Nodes, 6)
}
func TestParentsUntilInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsUntil("")
assertLength(t, sel.Nodes, 8)
}
func TestParentsUntilRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ParentsUntil("body").End()
@@ -180,6 +220,11 @@ func TestParentsFilteredUntil(t *testing.T) {
assertLength(t, sel.Nodes, 2)
}
func TestParentsFilteredUntilInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsFilteredUntil("", "")
assertLength(t, sel.Nodes, 0)
}
func TestParentsFilteredUntilRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ParentsFilteredUntil(".pvk-content", "body").End()
@@ -240,6 +285,11 @@ func TestSiblingsFiltered(t *testing.T) {
assertLength(t, sel.Nodes, 3)
}
func TestSiblingsFilteredInvalid(t *testing.T) {
sel := Doc().Find(".pvk-gutter").SiblingsFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestSiblingsFilteredRollback(t *testing.T) {
sel := Doc().Find(".pvk-gutter")
sel2 := sel.SiblingsFiltered(".pvk-content").End()
@@ -272,6 +322,11 @@ func TestNextFiltered(t *testing.T) {
assertLength(t, sel.Nodes, 2)
}
func TestNextFilteredInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").NextFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestNextFilteredRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.NextFiltered("div").End()
@@ -310,6 +365,11 @@ func TestPrevFiltered(t *testing.T) {
assertLength(t, sel.Nodes, 5)
}
func TestPrevFilteredInvalid(t *testing.T) {
sel := Doc().Find(".row-fluid").PrevFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestPrevFilteredRollback(t *testing.T) {
sel := Doc().Find(".row-fluid")
sel2 := sel.PrevFiltered(".row-fluid").End()
@@ -342,6 +402,11 @@ func TestNextAllFiltered(t *testing.T) {
assertLength(t, sel.Nodes, 2)
}
func TestNextAllFilteredInvalid(t *testing.T) {
sel := Doc().Find("#cf2 .row-fluid").NextAllFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestNextAllFilteredRollback(t *testing.T) {
sel := Doc().Find("#cf2 .row-fluid")
sel2 := sel.NextAllFiltered("[ng-cloak]").End()
@@ -380,6 +445,11 @@ func TestPrevAllFiltered(t *testing.T) {
assertLength(t, sel.Nodes, 3)
}
func TestPrevAllFilteredInvalid(t *testing.T) {
sel := Doc().Find(".pvk-gutter").PrevAllFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestPrevAllFilteredRollback(t *testing.T) {
sel := Doc().Find(".pvk-gutter")
sel2 := sel.PrevAllFiltered(".pvk-content").End()
@@ -392,6 +462,11 @@ func TestNextUntil(t *testing.T) {
assertSelectionIs(t, sel, "h4")
}
func TestNextUntilInvalid(t *testing.T) {
sel := Doc().Find(".alert a").NextUntil("")
assertLength(t, sel.Nodes, 2)
}
func TestNextUntil2(t *testing.T) {
sel := Doc().Find("#cf2-1").NextUntil("[ng-cloak]")
assertLength(t, sel.Nodes, 1)
@@ -446,6 +521,11 @@ func TestPrevUntil(t *testing.T) {
assertSelectionIs(t, sel, "h4")
}
func TestPrevUntilInvalid(t *testing.T) {
sel := Doc().Find(".alert p").PrevUntil("")
assertLength(t, sel.Nodes, 2)
}
func TestPrevUntil2(t *testing.T) {
sel := Doc().Find("[ng-cloak]").PrevUntil(":not([ng-cloak])")
assertLength(t, sel.Nodes, 1)
@@ -500,6 +580,11 @@ func TestNextFilteredUntil(t *testing.T) {
assertSelectionIs(t, sel, "#n3", "#n5", "#nf3", "#nf5")
}
func TestNextFilteredUntilInvalid(t *testing.T) {
sel := Doc2().Find(".two").NextFilteredUntil("", "")
assertLength(t, sel.Nodes, 0)
}
func TestNextFilteredUntilRollback(t *testing.T) {
sel := Doc2().Find(".two")
sel2 := sel.NextFilteredUntil(".even", ".six").End()
@@ -542,6 +627,11 @@ func TestPrevFilteredUntil(t *testing.T) {
assertSelectionIs(t, sel, "#n4", "#n2", "#nf4", "#nf2")
}
func TestPrevFilteredUntilInvalid(t *testing.T) {
sel := Doc2().Find(".five").PrevFilteredUntil("", "")
assertLength(t, sel.Nodes, 0)
}
func TestPrevFilteredUntilRollback(t *testing.T) {
sel := Doc2().Find(".four")
sel2 := sel.PrevFilteredUntil(".odd", ".one").End()
@@ -598,6 +688,12 @@ func TestClosestNone(t *testing.T) {
assertLength(t, sel2.Nodes, 0)
}
func TestClosestInvalid(t *testing.T) {
sel := Doc().Find("h4")
sel2 := sel.Closest("")
assertLength(t, sel2.Nodes, 0)
}
func TestClosestMany(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.Closest(".pvk-content")
+29 -3
View File
@@ -6,6 +6,8 @@ import (
"net/http"
"net/url"
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
@@ -29,6 +31,10 @@ func NewDocumentFromNode(root *html.Node) *Document {
// NewDocument is a Document constructor that takes a string URL as argument.
// It loads the specified document, parses it, and stores the root Document
// node, ready to be manipulated.
//
// Deprecated: Use the net/http standard library package to make the request
// and validate the response before calling goquery.NewDocumentFromReader
// with the response's body.
func NewDocument(url string) (*Document, error) {
// Load the URL
res, e := http.Get(url)
@@ -38,10 +44,10 @@ func NewDocument(url string) (*Document, error) {
return NewDocumentFromResponse(res)
}
// NewDocumentFromReader returns a Document from a generic reader.
// NewDocumentFromReader returns a Document from an io.Reader.
// It returns an error as second value if the reader's data cannot be parsed
// as html. It does *not* check if the reader is also an io.Closer, so the
// provided reader is never closed by this call, it is the responsibility
// as html. It does not check if the reader is also an io.Closer, the
// provided reader is never closed by this call. It is the responsibility
// of the caller to close it if required.
func NewDocumentFromReader(r io.Reader) (*Document, error) {
root, e := html.Parse(r)
@@ -54,6 +60,8 @@ func NewDocumentFromReader(r io.Reader) (*Document, error) {
// NewDocumentFromResponse is another Document constructor that takes an http response as argument.
// It loads the specified response's document, parses it, and stores the root Document
// node, ready to be manipulated. The response's body is closed on return.
//
// Deprecated: Use goquery.NewDocumentFromReader with the response's body.
func NewDocumentFromResponse(res *http.Response) (*Document, error) {
if res == nil {
return nil, errors.New("Response is nil")
@@ -113,3 +121,21 @@ type Matcher interface {
MatchAll(*html.Node) []*html.Node
Filter([]*html.Node) []*html.Node
}
// compileMatcher compiles the selector string s and returns
// the corresponding Matcher. If s is an invalid selector string,
// it returns a Matcher that fails all matches.
func compileMatcher(s string) Matcher {
cs, err := cascadia.Compile(s)
if err != nil {
return invalidMatcher{}
}
return cs
}
// invalidMatcher is a Matcher that always fails to match.
type invalidMatcher struct{}
func (invalidMatcher) Match(n *html.Node) bool { return false }
func (invalidMatcher) MatchAll(n *html.Node) []*html.Node { return nil }
func (invalidMatcher) Filter(ns []*html.Node) []*html.Node { return nil }
+27 -9
View File
@@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"os"
"strings"
"testing"
"golang.org/x/net/html"
@@ -22,45 +23,42 @@ func Doc() *Document {
}
return doc
}
func DocClone() *Document {
return CloneDocument(Doc())
}
func Doc2() *Document {
if doc2 == nil {
doc2 = loadDoc("page2.html")
}
return doc2
}
func Doc2Clone() *Document {
return CloneDocument(Doc2())
}
func Doc3() *Document {
if doc3 == nil {
doc3 = loadDoc("page3.html")
}
return doc3
}
func Doc3Clone() *Document {
return CloneDocument(Doc3())
}
func DocB() *Document {
if docB == nil {
docB = loadDoc("gotesting.html")
}
return docB
}
func DocBClone() *Document {
return CloneDocument(DocB())
}
func DocW() *Document {
if docW == nil {
docW = loadDoc("gowiki.html")
}
return docW
}
func DocWClone() *Document {
return CloneDocument(DocW())
}
func assertLength(t *testing.T, nodes []*html.Node, length int) {
if len(nodes) != length {
@@ -123,6 +121,14 @@ func loadDoc(page string) *Document {
return NewDocumentFromNode(node)
}
func loadString(t *testing.T, doc string) *Document {
d, err := NewDocumentFromReader(strings.NewReader(doc))
if err != nil {
t.Error("Failed to parse test document")
}
return d
}
func TestNewDocument(t *testing.T) {
if f, e := os.Open("./testdata/page.html"); e != nil {
t.Error(e.Error())
@@ -190,3 +196,15 @@ func TestNewDocumentFromResponseNil(t *testing.T) {
t.Error("Expected error, got none")
}
}
func TestIssue103(t *testing.T) {
d, err := NewDocumentFromReader(strings.NewReader("<html><title>Scientists Stored These Images in DNA—Then Flawlessly Retrieved Them</title></html>"))
if err != nil {
t.Error(err)
}
text := d.Find("title").Text()
for i, r := range text {
t.Logf("%d: %d - %q\n", i, r, string(r))
}
t.Log(text)
}
+49 -20
View File
@@ -6,6 +6,11 @@ import (
"golang.org/x/net/html"
)
// used to determine if a set (map[*html.Node]bool) should be used
// instead of iterating over a slice. The set uses more memory and
// is slower than slice iteration for small N.
const minNodesForSet = 1000
var nodeNames = []string{
html.ErrorNode: "#error",
html.TextNode: "#text",
@@ -20,23 +25,33 @@ var nodeNames = []string{
// Go's net/html package defines the following node types, listed with
// the corresponding returned value from this function:
//
// ErrorNode : #error
// TextNode : #text
// DocumentNode : #document
// ElementNode : the element's tag name
// CommentNode : #comment
// DoctypeNode : the name of the document type
// ErrorNode : #error
// TextNode : #text
// DocumentNode : #document
// ElementNode : the element's tag name
// CommentNode : #comment
// DoctypeNode : the name of the document type
//
func NodeName(s *Selection) string {
if s.Length() == 0 {
return ""
}
switch n := s.Get(0); n.Type {
return nodeName(s.Get(0))
}
// nodeName returns the node name of the given html node.
// See NodeName for additional details on behaviour.
func nodeName(node *html.Node) string {
if node == nil {
return ""
}
switch node.Type {
case html.ElementNode, html.DoctypeNode:
return n.Data
return node.Data
default:
if n.Type >= 0 && int(n.Type) < len(nodeNames) {
return nodeNames[n.Type]
if node.Type >= 0 && int(node.Type) < len(nodeNames) {
return nodeNames[node.Type]
}
return ""
}
@@ -62,13 +77,6 @@ func OuterHtml(s *Selection) (string, error) {
return buf.String(), nil
}
func getChildren(n *html.Node) (result []*html.Node) {
for c := n.FirstChild; c != nil; c = c.NextSibling {
result = append(result, c)
}
return result
}
// Loop through all container nodes to search for the target node.
func sliceContains(container []*html.Node, contained *html.Node) bool {
for _, n := range container {
@@ -112,11 +120,32 @@ func indexInSlice(slice []*html.Node, node *html.Node) int {
// Appends the new nodes to the target slice, making sure no duplicate is added.
// There is no check to the original state of the target slice, so it may still
// contain duplicates. The target slice is returned because append() may create
// a new underlying array.
func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node) []*html.Node {
// a new underlying array. If targetSet is nil, a local set is created with the
// target if len(target) + len(nodes) is greater than minNodesForSet.
func appendWithoutDuplicates(target []*html.Node, nodes []*html.Node, targetSet map[*html.Node]bool) []*html.Node {
// if there are not that many nodes, don't use the map, faster to just use nested loops
// (unless a non-nil targetSet is passed, in which case the caller knows better).
if targetSet == nil && len(target)+len(nodes) < minNodesForSet {
for _, n := range nodes {
if !isInSlice(target, n) {
target = append(target, n)
}
}
return target
}
// if a targetSet is passed, then assume it is reliable, otherwise create one
// and initialize it with the current target contents.
if targetSet == nil {
targetSet = make(map[*html.Node]bool, len(target))
for _, n := range target {
targetSet[n] = true
}
}
for _, n := range nodes {
if !isInSlice(target, n) {
if !targetSet[n] {
target = append(target, n)
targetSet[n] = true
}
}
+1 -1
View File
@@ -77,7 +77,7 @@ func TestNodeNameMultiSel(t *testing.T) {
sort.Strings(in)
sort.Strings(out)
if !reflect.DeepEqual(in, out) {
t.Error("want %v, got %v")
t.Errorf("want %v, got %v", in, out)
}
}