Add code and dependencies

This commit is contained in:
Gabriel Augendre 2018-11-18 15:32:28 +01:00
parent 5708f479a2
commit d0ded4fd02
576 changed files with 370172 additions and 23 deletions

View file

@ -5,4 +5,4 @@
youtubebeat: youtubebeat:
# Defines how often an event is sent to the output # Defines how often an event is sent to the output
period: 1s period: 1s
start_url: "https://www.youtube.com/watch?v=SmBCZgcGlKk" start_id: "SmBCZgcGlKk"

View file

@ -2,8 +2,23 @@
title: youtubebeat title: youtubebeat
description: description:
fields: fields:
- name: counter - name: url
type: string
required: true
description: >
Video's full URL
- name: title
type: string
required: true
description: >
Video title
- name: views
type: long type: long
required: true required: true
description: > description: >
PLEASE UPDATE DOCUMENTATION Video's view count
- name: date
type: string
required: true
description: >
Video's publish date

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -2,6 +2,9 @@ package beater
import ( import (
"fmt" "fmt"
"github.com/gocolly/colly"
"strconv"
"strings"
"time" "time"
"github.com/elastic/beats/libbeat/beat" "github.com/elastic/beats/libbeat/beat"
@ -32,6 +35,54 @@ func New(b *beat.Beat, cfg *common.Config) (beat.Beater, error) {
return bt, nil return bt, nil
} }
func scrapeVideos(startId string, fieldsToSend chan common.MapStr) {
const BaseUrl = "https://www.youtube.com"
const BaseSuffix = "/watch?v="
videoCollector := colly.NewCollector(
colly.AllowedDomains("youtube.com", "www.youtube.com"),
colly.Async(true),
colly.MaxDepth(10),
)
videoCollector.OnHTML("body", func(e *colly.HTMLElement) {
url := e.Request.URL.String()
isPaid := e.ChildAttr("meta[itemprop=\"paid\"]", "content")
if isPaid == "True" {
logp.Err("Not parsing video because of isPaid" + url)
return
}
title := e.ChildAttr("meta[itemprop=\"name\"]", "content")
if title == "YouTube" {
logp.Err("Not parsing video because of title " + url)
return
}
views, err := strconv.ParseInt(e.ChildAttr("meta[itemprop=\"interactionCount\"]", "content"), 10, 64)
if err != nil {
logp.Err("Can't parse view count for URL " + url)
return
}
date := e.ChildAttr("meta[itemprop=\"datePublished\"]", "content")
fields := common.MapStr{
"url": url,
"title": title,
"views": views,
"date": date,
}
fieldsToSend <- fields
})
videoCollector.OnHTML("a[href]", func(e *colly.HTMLElement) {
href := e.Attr("href")
if strings.HasPrefix(href, BaseSuffix) {
e.Request.Visit(BaseUrl + href)
}
})
videoCollector.Visit(BaseUrl + BaseSuffix + startId)
videoCollector.Wait()
}
// Run starts youtubebeat. // Run starts youtubebeat.
func (bt *Youtubebeat) Run(b *beat.Beat) error { func (bt *Youtubebeat) Run(b *beat.Beat) error {
logp.Info("youtubebeat is running! Hit CTRL-C to stop it.") logp.Info("youtubebeat is running! Hit CTRL-C to stop it.")
@ -42,25 +93,24 @@ func (bt *Youtubebeat) Run(b *beat.Beat) error {
return err return err
} }
fieldsToSend := make(chan common.MapStr)
go scrapeVideos(bt.config.StartId, fieldsToSend)
ticker := time.NewTicker(bt.config.Period) ticker := time.NewTicker(bt.config.Period)
counter := 1
for { for {
select { select {
case <-bt.done: case <-bt.done:
return nil return nil
case <-ticker.C: case <-ticker.C:
} }
fields := <-fieldsToSend
fields["type"] = b.Info.Name
event := beat.Event{ event := beat.Event{
Timestamp: time.Now(), Timestamp: time.Now(),
Fields: common.MapStr{ Fields: fields,
"type": b.Info.Name,
"counter": counter,
},
} }
bt.client.Publish(event) bt.client.Publish(event)
logp.Info("Event sent") logp.Info("Event sent")
counter++
} }
} }

View file

@ -7,10 +7,10 @@ import "time"
type Config struct { type Config struct {
Period time.Duration `config:"period"` Period time.Duration `config:"period"`
StartUrl string `config:"start_url"` StartId string `config:"start_id"`
} }
var DefaultConfig = Config{ var DefaultConfig = Config{
Period: 1 * time.Second, Period: 1 * time.Second,
StartUrl: "https://www.youtube.com/watch?v=SmBCZgcGlKk", StartId: "SmBCZgcGlKk",
} }

1
data/meta.json Normal file
View file

@ -0,0 +1 @@
{"uuid":"a5fddc65-b03a-4cde-b7a9-f884d277841d"}

View file

@ -431,14 +431,50 @@ Kubernetes container image
None None
*`counter`*:: *`url`*::
+
--
type: string
required: True
Video's full URL
--
*`title`*::
+
--
type: string
required: True
Video title
--
*`views`*::
+ +
-- --
type: long type: long
required: True required: True
PLEASE UPDATE DOCUMENTATION Video's view count
--
*`date`*::
+
--
type: string
required: True
Video's publish date
-- --

View file

@ -2,11 +2,26 @@
title: youtubebeat title: youtubebeat
description: description:
fields: fields:
- name: counter - name: url
type: string
required: true
description: >
Video's full URL
- name: title
type: string
required: true
description: >
Video title
- name: views
type: long type: long
required: true required: true
description: > description: >
PLEASE UPDATE DOCUMENTATION Video's view count
- name: date
type: string
required: true
description: >
Video's publish date
- key: beat - key: beat
title: Beat title: Beat

View file

@ -31,5 +31,5 @@ func init() {
// Asset returns asset data // Asset returns asset data
func Asset() string { func Asset() string {
return "eJy0WFtz2zYTffev2MnT983InMZuPB09dOrankbTOPE09rOyAlYkahJgcLGs/voOQPBmUpQcqy8ZE5dzzgK7Z6GcwiNt57BVzroVrQjtCYAVNqeXg5wM06K0Qsn5CcBaUM6N/wvgFCQWNAemnLSkwxiA3ZY0h1zJNA5o+u6EJj4Hqx3FwS4s/BoHAe4+3Vx+vYGHu+vL+xu4/nL1cHvz+f7yfvHl88lJFN1X+/tQZsS7UtKikAaYKgolw76oH/AJRY6rnEBIwDwHeiJpg3aTdMLsxekBEv/ndAz3GYUNoNZgMwoKwZDkQqZhIFcpFGQMpmQSWHRWhW3CNFCGrBfo55mSa5E6jZ4O1iKnmR/3k2jhCXPnd4IzxAOmsP5TKtsFC1sgU8ZGprj+XgWqno6ZnwtD3/zntwZHhYh360qGh1Yz7j+4Rhsa0GSdlsRhtQ1UqiRPI1MwW2OpACVhkwmWtcI7Z6edlEKmI2qsKOgfJQ9QU6/8L9U8kTZCyf1i4sI6rUI6h8tPSXopxMFmwlSpnPRT991vPhRjsSjf9eqUo6WpOl0rXaDtraNnLEpfepcudcbC2YXN4Oyn9xczeH82P/8w/3CenJ+fHXa6QRJsqkSmWIa+QDQxpTls0LTxvQjKYmqmWS71SliNehvWVqfF0FtByPeSdHVRKHn4sBqlQWbb+6jO6QVx5Q69c1Srv4nVtVZ9LKuZR9pulObTQhuvcoZ0W1PeoCqyFwpIa9U33FQrV06T3PhNtQOyitHnL3Iu/FrMQci18pXN0AT/CjwmqZOhY/5dNdHMmvFak6Vn2xncIauVFnGSAQFTfIjeaTEHoXuQIbTHGkD37+wg9CpNYotiuXK87VFX/hNKrZ4EJx+mRY4Wx9vWbZyFtVZFhdRsNf6uWgtCzpdhwbKG9CsZGaP0zi7mlyZhV1LDvixsYnuq93OnvfUVJnCnjBE+cUNPMoCaPOAMUkYzUBq4SIXFXDFCmezUJqSxKBktxZ7SWcSFsLiuJfkmAgWyTMiXpTvGsL8zNRzdvn4YS1yw7ORZc872LCmIC1dMs99WECHFXkcenzkiF3a77LS8RoEzp4TGnr5ne4y0AwShI4q22wlTyRGmbXMTKRe8sbnVRkqcOX0+PPXiFq/lD6XSnKpK282uKd3bav8Ka/bFFwudK/YY6idW+nX9PQJezYGxaL395jkx37NDmVdzvmZNprRdVh1gDmvMjb80lCxTuuY7bap8x4u8kQWj/WGXj8eeQDoR/G2e+CDFd0ctIAg+5uoNXTHWPl7F2M2LAFe/TqMA/5BYOZFbUHJKSscMflDJVcPpsaa4clxRbgZsvbcETL8n9mhZhJOoeJqk9cncpuzH6msEZOEfA51E9V1uYD1tbvrxvZkZuV+Xl2+/k4/xZ8XwNo6U6ZVBjCQ5apYJS8w6fYQYenDwP0rSBJ5/uVhe/DwD1MUMypLNoBCl+f9QijJJmaP1T/q3KfnyFWqgqIGRtMrMwK2ctG4GGyG52uwQ0f/F8+MaIs4oxxoLkW/fTFHBxCA18QztDDitBMoZrDXRyvCpaEU5kNAbmmD/JIz1hra4O0XONRlDZkhQIHtbkDVNhppvUFNLNgNnHOb5Fm4vr7oaah95dCvSkiyZ1k3+7I6N0LbzzTO4/6ZtQaHrJdNtsd2014B6ouFVNlQqfoT20DmBUvHK20ap3FutqcN0pzg8LK6HRP5fUyI7XlAt4pDM/wI76gl6xB1HeGhzPYyoQoMCyyETSqls+P+vo9F1IMc5j/lg6fCy3ttlivYIT7ZR3gr33wAAAP//UV6dfg==" return "eJy0WEtvGzcX3edXXGST7wOkQWM3RqFF0dQpGqFJG6RJtwpFXs2w5pATXtKy+usLcjgvz+jhSN0Y4uucc3lfHM/hDncL2Bnv/BrXyNwzACedwseTAolbWTlp9OIZwEaiEhR+AcxBsxIX4K2KYwC3q3AB5KzUeZqy+NVLi2IBznpMk31Q+DFNAvwlBZoXBBuvFHz++G7AEtVdkKcH2FDcS9zSgEKZswwJgMCN127AI5i7pCUvCCq/VpKKGvlZ8u7QrT+P/ZmAbo12TGoCbsrS6HguORrYPZOKrRWC1MCUArxH7aJsynrxMLAvAGTh52HxnwqMB8BswBUYFQKhFlLncUKZHEokYjlSBsvernhMUgtF6ILAsM6N3sjcWxboYCMVzsJ8WGQO7pny4SR4QhExpQtDbVwfLB6BwpBLTGn/JxOpBjpmYS1OfQnDLy2OiRbv15WNL61hPH5xrTZGYNF5q1HAehepTIWBRudAO3JYgtGwLSQvOuG9u7Nea6nzCTVOlviP0SeoaXb+l2ru0ZI0+riYtLEJqxjO0fk56iAFBbhCUh3K2TB0n/8UTCHHyur5IEV7OTuZoBtjS+YG+/CBlVVIvdc+9+Tg6sYVcPXdy5sZvLxaXL9avLrOrq+vTrvdKAm2dSBjSsOQIBa5sQK2jDr7HhnlWE6HWV7btXSW2V3cW98WZ6EUxHiv0NaOYlrEgbNME+Ou80d9T4+I6+owuEez/ht5k2v1YFWv3OFua6w4LLStVZ7QdjkVClRN9kgBWmvsQEBuja8Ok/wSDjUVkNeMIX6ZEDLsZQqk3piQ2ZxRrF+Rh7ImGHpdsq8mFbN2vtHk8MH1JvfI6qQlnGxEwI0Yo/ea2EnoAWQMHbBG0EOfnYReh0lqUVwZL7oedRuGUFlzLwUGMx0TzLHptvU+rcLGmrJGao9S8FVXgpgQq7hh1UCGnRyJjN3bxcLWLJ7KGtjHiY38SPb+3mtvQ4UZfDBEMgRu7EkEzGIAnEHOcQbGgpC5dEwZjkxne7VJTY5pjit5JHWWaSMs3zSSQhOBkvFC6sepO8VwvDO1HP2+fhpL2rDqxVl7z+4qK1FIXx5mf19DxBB7Gnl65kgl3W7Va3mtAk9zZOTmL/mRQtoDgtgRZdftJNVyJHVt7kDIxdrYerWVklbmD6eHXjoStPxqTK6wzrT97Bbzo632Y9xzzL6U6MLwu5g/KdPfNOMJ8HoNyDEXyq9SyEPPjmler4WcpcJYt6o7wAI2TFFwGtO8MLbhm7dZvufTpZUFk/1hXx1PPQFtJsV5NfGzll89doAgxVRVb+nKqfbxJMZ+XES45nWaBISHxNpL5cDoQ1J6xeAbldy2nAHrEJdia1Q0Yhu8JeDwe+KIlmW8iZqnDdoQzF3Ivq1HEyDL8BjoBWrocqPS08VmmD8amYn7aXF5vk/eps+KsTcuFOl1gZgIcmZ5IR1y5+0FbBjAwf8wyzN4+OFmdfP9DJgtZ1BVfAalrOj/YymGskoxF5705yn5409ogJIGjtoZmoFfe+38DLZSC7PdI2L4xfPtGhLOJMeGlVLtzqaoYZKRFkXB3AwEriXTM9hYxDWJQ9bKaiRhMHWA/Z0kFwra8sOcCWGRCGlMUDJ+npENTcGs2DKLHdkMPHmm1A7ev77ta2jqyJ1fo9XokLpq8lt/boK2W2+fwcM3bQcK/VpyuC12h44WoIFoeFIZqoy4QHvo3UBlRF3bJqn8uaWpx/TBCPi8fDMmCn+pYvxyRnWIY7LwBXbRGwyIe67w1OZ6GlGNBiWrxkxMa+Pi/78uRteDnOa85IOlx8sHb5dDtBd4sk3y1rj/BgAA///8ffLf"
} }

8
logs/youtubebeat Normal file
View file

@ -0,0 +1,8 @@
2018-11-18T11:09:17.508+0100 INFO instance/beat.go:616 Home path: [/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat] Config path: [/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat] Data path: [/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat/data] Logs path: [/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat/logs]
2018-11-18T11:09:17.509+0100 INFO instance/beat.go:623 Beat UUID: a5fddc65-b03a-4cde-b7a9-f884d277841d
2018-11-18T11:09:17.509+0100 INFO [beat] instance/beat.go:849 Beat info {"system_info": {"beat": {"path": {"config": "/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat", "data": "/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat/data", "home": "/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat", "logs": "/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat/logs"}, "type": "youtubebeat", "uuid": "a5fddc65-b03a-4cde-b7a9-f884d277841d"}}}
2018-11-18T11:09:17.509+0100 INFO [beat] instance/beat.go:858 Build info {"system_info": {"build": {"commit": "38fd4465358c195b17152b60e7e32e6ed0345528", "libbeat": "6.5.0", "time": "2018-11-18T10:08:58.000Z", "version": "6.5.0"}}}
2018-11-18T11:09:17.509+0100 INFO [beat] instance/beat.go:861 Go runtime info {"system_info": {"go": {"os":"darwin","arch":"amd64","max_procs":8,"version":"go1.11.2"}}}
2018-11-18T11:09:17.511+0100 INFO [beat] instance/beat.go:865 Host info {"system_info": {"host": {"architecture":"x86_64","boot_time":"2018-11-13T17:51:46.564974+01:00","name":"persephone.local","ip":["127.0.0.1/8","::1/128","fe80::1/64","fe80::1cb1:a370:e195:1b14/64","10.0.0.11/24","2a01:e34:ec0f:96a0:1c7d:59bf:1f3b:b4d9/64","2a01:e34:ec0f:96a0:bdde:b4b8:e13:d8da/64","fe80::d891:cff:fe4e:3f99/64","fe80::8aed:bb9d:8b99:8746/64","fe80::e055:800e:12a0:b862/64","fe80::4e1b:e233:d6da:218/64","fe80::aede:48ff:fe00:1122/64"],"kernel_version":"18.2.0","mac":["b6:00:34:c9:44:01","b6:00:34:c9:44:00","b6:00:34:c9:44:05","b6:00:34:c9:44:04","dc:a9:04:7d:4a:e4","0e:a9:04:7d:4a:e4","da:91:0c:4e:3f:99","b6:00:34:c9:44:01","ac:de:48:00:11:22"],"os":{"family":"darwin","platform":"darwin","name":"Mac OS X","version":"10.14.1","major":10,"minor":14,"patch":1,"build":"18B75"},"timezone":"CET","timezone_offset_sec":3600}}}
2018-11-18T11:09:17.511+0100 INFO [beat] instance/beat.go:894 Process info {"system_info": {"process": {"cwd": "/Users/gaugendre/Projects/golang/src/github.com/Crocmagnon/youtubebeat", "exe": "./youtubebeat", "name": "youtubebeat", "pid": 75764, "ppid": 74760, "start_time": "2018-11-18T11:09:17.481+0100"}}}
2018-11-18T11:09:17.511+0100 INFO instance/beat.go:302 Setup Beat: youtubebeat; Version: 6.5.0

12
vendor/github.com/PuerkitoBio/goquery/LICENSE generated vendored Normal file
View file

@ -0,0 +1,12 @@
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:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

179
vendor/github.com/PuerkitoBio/goquery/README.md generated vendored Normal file
View file

@ -0,0 +1,179 @@
# goquery - a little like that j-thing, only in Go
[![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.
Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML. See the [wiki][] for various options to do this.
Syntax-wise, it is as close as possible to jQuery, with the same function names when possible, and that warm and fuzzy chainable interface. jQuery being the ultra-popular library that it is, I felt that writing a similar HTML-manipulating library was better to follow its API than to start anew (in the same spirit as Go's `fmt` package), even though some of its methods are less than intuitive (looking at you, [index()][index]...).
## Table of Contents
* [Installation](#installation)
* [Changelog](#changelog)
* [API](#api)
* [Examples](#examples)
* [Related Projects](#related-projects)
* [Support](#support)
* [License](#license)
## Installation
Please note that because of the net/html dependency, goquery requires Go1.1+.
$ go get github.com/PuerkitoBio/goquery
(optional) To run unit tests:
$ cd $GOPATH/src/github.com/PuerkitoBio/goquery
$ go test
(optional) To run benchmarks (warning: it runs for a few minutes):
$ cd $GOPATH/src/github.com/PuerkitoBio/goquery
$ go test -bench=".*"
## Changelog
**Note that goquery's API is now stable, and will not break.**
* **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].
* **2014-11-28** : Add more manipulation functions - ReplaceWith*, Wrap* and Unwrap - thanks again to [Andrew Stone][thatguystone].
* **2014-11-07** : Add manipulation functions (thanks to [Andrew Stone][thatguystone]) and `*Matcher` functions, that receive compiled cascadia selectors instead of selector strings, thus avoiding potential panics thrown by goquery via `cascadia.MustCompile` calls. This results in better performance (selectors can be compiled once and reused) and more idiomatic error handling (you can handle cascadia's compilation errors, instead of recovering from panics, which had been bugging me for a long time). Note that the actual type expected is a `Matcher` interface, that `cascadia.Selector` implements. Other matcher implementations could be used.
* **2014-11-06** : Change import paths of net/html to golang.org/x/net/html (see https://groups.google.com/forum/#!topic/golang-nuts/eD8dh3T9yyA). Make sure to update your code to use the new import path too when you call goquery with `html.Node`s.
* **v0.3.2** : Add `NewDocumentFromReader()` (thanks jweir) which allows creating a goquery document from an io.Reader.
* **v0.3.1** : Add `NewDocumentFromResponse()` (thanks assassingj) which allows creating a goquery document from an http response.
* **v0.3.0** : Add `EachWithBreak()` which allows to break out of an `Each()` loop by returning false. This function was added instead of changing the existing `Each()` to avoid breaking compatibility.
* **v0.2.1** : Make go-getable, now that [go.net/html is Go1.0-compatible][gonet] (thanks to @matrixik for pointing this out).
* **v0.2.0** : Add support for negative indices in Slice(). **BREAKING CHANGE** `Document.Root` is removed, `Document` is now a `Selection` itself (a selection of one, the root element, just like `Document.Root` was before). Add jQuery's Closest() method.
* **v0.1.1** : Add benchmarks to use as baseline for refactorings, refactor Next...() and Prev...() methods to use the new html package's linked list features (Next/PrevSibling, FirstChild). Good performance boost (40+% in some cases).
* **v0.1.0** : Initial release.
## API
goquery exposes two structs, `Document` and `Selection`, and the `Matcher` interface. Unlike jQuery, which is loaded as part of a DOM document, and thus acts on its containing document, goquery doesn't know which HTML document to act upon. So it needs to be told, and that's what the `Document` type is for. It holds the root document node as the initial Selection value to manipulate.
jQuery often has many variants for the same function (no argument, a selector string argument, a jQuery object argument, a DOM element argument, ...). Instead of exposing the same features in goquery as a single method with variadic empty interface arguments, statically-typed signatures are used following this naming convention:
* When the jQuery equivalent can be called with no argument, it has the same name as jQuery for the no argument signature (e.g.: `Prev()`), and the version with a selector string argument is called `XxxFiltered()` (e.g.: `PrevFiltered()`)
* When the jQuery equivalent **requires** one argument, the same name as jQuery is used for the selector string version (e.g.: `Is()`)
* The signatures accepting a jQuery object as argument are defined in goquery as `XxxSelection()` and take a `*Selection` object as argument (e.g.: `FilterSelection()`)
* The signatures accepting a DOM element as argument in jQuery are defined in goquery as `XxxNodes()` and take a variadic argument of type `*html.Node` (e.g.: `FilterNodes()`)
* The signatures accepting a function as argument in jQuery are defined in goquery as `XxxFunction()` and take a function as argument (e.g.: `FilterFunction()`)
* The goquery methods that can be called with a selector string have a corresponding version that take a `Matcher` interface and are defined as `XxxMatcher()` (e.g.: `IsMatcher()`)
Utility functions that are not in jQuery but are useful in Go are implemented as functions (that take a `*Selection` as parameter), to avoid a potential naming clash on the `*Selection`'s methods (reserved for jQuery-equivalent behaviour).
The complete [godoc reference documentation can be found here][doc].
Please note that Cascadia's selectors do not necessarily match all supported selectors of jQuery (Sizzle). See the [cascadia project][cascadia] for details. 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][].
Adapted from example_test.go:
```Go
package main
import (
"fmt"
"log"
"net/http"
"github.com/PuerkitoBio/goquery"
)
func ExampleScrape() {
// 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
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)
})
}
func main() {
ExampleScrape()
}
```
## 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.
- [asciimoo/colly](https://github.com/asciimoo/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.
## 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].
[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
[doc]: http://godoc.org/github.com/PuerkitoBio/goquery
[index]: http://api.jquery.com/index/
[gonet]: https://github.com/golang/net/
[html]: http://godoc.org/golang.org/x/net/html
[wiki]: https://github.com/PuerkitoBio/goquery/wiki/Tips-and-tricks
[thatguystone]: https://github.com/thatguystone
[piotr]: https://github.com/piotrkowalczuk
[goq]: https://github.com/andrewstuart/goq

124
vendor/github.com/PuerkitoBio/goquery/array.go generated vendored Normal file
View file

@ -0,0 +1,124 @@
package goquery
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.
func (s *Selection) First() *Selection {
return s.Eq(0)
}
// Last reduces the set of matched elements to the last in the set.
// It returns a new Selection object, and an empty Selection object if
// the selection is empty.
func (s *Selection) Last() *Selection {
return s.Eq(-1)
}
// Eq reduces the set of matched elements to the one at the specified index.
// If a negative index is given, it counts backwards starting at the end of the
// set. It returns a new Selection object, and an empty Selection object if the
// index is invalid.
func (s *Selection) Eq(index int) *Selection {
if index < 0 {
index += len(s.Nodes)
}
if index >= len(s.Nodes) || index < 0 {
return newEmptySelection(s.document)
}
return s.Slice(index, index+1)
}
// Slice reduces the set of matched elements to a subset specified by a range
// 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 == ToEnd {
end = len(s.Nodes)
} else if end < 0 {
end += len(s.Nodes)
}
return pushStack(s, s.Nodes[start:end])
}
// Get retrieves the underlying node at the specified index.
// Get without parameter is not implemented, since the node array is available
// on the Selection object.
func (s *Selection) Get(index int) *html.Node {
if index < 0 {
index += len(s.Nodes) // Negative index gets from the end
}
return s.Nodes[index]
}
// Index returns the position of the first element within the Selection object
// relative to its sibling elements.
func (s *Selection) Index() int {
if len(s.Nodes) > 0 {
return newSingleSelection(s.Nodes[0], s.document).PrevAll().Length()
}
return -1
}
// IndexSelector returns the position of the first element within the
// Selection object relative to the elements matched by the selector, or -1 if
// not found.
func (s *Selection) IndexSelector(selector string) int {
if len(s.Nodes) > 0 {
sel := s.document.Find(selector)
return indexInSlice(sel.Nodes, s.Nodes[0])
}
return -1
}
// IndexMatcher returns the position of the first element within the
// Selection object relative to the elements matched by the matcher, or -1 if
// not found.
func (s *Selection) IndexMatcher(m Matcher) int {
if len(s.Nodes) > 0 {
sel := s.document.FindMatcher(m)
return indexInSlice(sel.Nodes, s.Nodes[0])
}
return -1
}
// IndexOfNode returns the position of the specified node within the Selection
// object, or -1 if not found.
func (s *Selection) IndexOfNode(node *html.Node) int {
return indexInSlice(s.Nodes, node)
}
// IndexOfSelection returns the position of the first node in the specified
// Selection object within this Selection object, or -1 if not found.
func (s *Selection) IndexOfSelection(sel *Selection) int {
if sel != nil && len(sel.Nodes) > 0 {
return indexInSlice(s.Nodes, sel.Nodes[0])
}
return -1
}

234
vendor/github.com/PuerkitoBio/goquery/array_test.go generated vendored Normal file
View file

@ -0,0 +1,234 @@
package goquery
import (
"testing"
)
func TestFirst(t *testing.T) {
sel := Doc().Find(".pvk-content").First()
assertLength(t, sel.Nodes, 1)
}
func TestFirstEmpty(t *testing.T) {
sel := Doc().Find(".pvk-zzcontentzz").First()
assertLength(t, sel.Nodes, 0)
}
func TestFirstInvalid(t *testing.T) {
sel := Doc().Find("").First()
assertLength(t, sel.Nodes, 0)
}
func TestFirstRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.First().End()
assertEqual(t, sel, sel2)
}
func TestLast(t *testing.T) {
sel := Doc().Find(".pvk-content").Last()
assertLength(t, sel.Nodes, 1)
// Should contain Footer
foot := Doc().Find(".footer")
if !sel.Contains(foot.Nodes[0]) {
t.Error("Last .pvk-content should contain .footer.")
}
}
func TestLastEmpty(t *testing.T) {
sel := Doc().Find(".pvk-zzcontentzz").Last()
assertLength(t, sel.Nodes, 0)
}
func TestLastInvalid(t *testing.T) {
sel := Doc().Find("").Last()
assertLength(t, sel.Nodes, 0)
}
func TestLastRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Last().End()
assertEqual(t, sel, sel2)
}
func TestEq(t *testing.T) {
sel := Doc().Find(".pvk-content").Eq(1)
assertLength(t, sel.Nodes, 1)
}
func TestEqNegative(t *testing.T) {
sel := Doc().Find(".pvk-content").Eq(-1)
assertLength(t, sel.Nodes, 1)
// Should contain Footer
foot := Doc().Find(".footer")
if !sel.Contains(foot.Nodes[0]) {
t.Error("Index -1 of .pvk-content should contain .footer.")
}
}
func TestEqEmpty(t *testing.T) {
sel := Doc().Find("something_random_that_does_not_exists").Eq(0)
assertLength(t, sel.Nodes, 0)
}
func TestEqInvalid(t *testing.T) {
sel := Doc().Find("").Eq(0)
assertLength(t, sel.Nodes, 0)
}
func TestEqInvalidPositive(t *testing.T) {
sel := Doc().Find(".pvk-content").Eq(3)
assertLength(t, sel.Nodes, 0)
}
func TestEqInvalidNegative(t *testing.T) {
sel := Doc().Find(".pvk-content").Eq(-4)
assertLength(t, sel.Nodes, 0)
}
func TestEqRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Eq(1).End()
assertEqual(t, sel, sel2)
}
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) {
defer assertPanic(t)
Doc().Find(".pvk-content").Slice(2, 12)
}
func TestNegativeSliceStart(t *testing.T) {
sel := Doc().Find(".container-fluid").Slice(-2, 3)
assertLength(t, sel.Nodes, 1)
assertSelectionIs(t, sel.Eq(0), "#cf3")
}
func TestNegativeSliceEnd(t *testing.T) {
sel := Doc().Find(".container-fluid").Slice(1, -1)
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel.Eq(0), "#cf2")
assertSelectionIs(t, sel.Eq(1), "#cf3")
}
func TestNegativeSliceBoth(t *testing.T) {
sel := Doc().Find(".container-fluid").Slice(-3, -1)
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel.Eq(0), "#cf2")
assertSelectionIs(t, sel.Eq(1), "#cf3")
}
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)
}
func TestSliceRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Slice(0, 2).End()
assertEqual(t, sel, sel2)
}
func TestGet(t *testing.T) {
sel := Doc().Find(".pvk-content")
node := sel.Get(1)
if sel.Nodes[1] != node {
t.Errorf("Expected node %v to be %v.", node, sel.Nodes[1])
}
}
func TestGetNegative(t *testing.T) {
sel := Doc().Find(".pvk-content")
node := sel.Get(-3)
if sel.Nodes[0] != node {
t.Errorf("Expected node %v to be %v.", node, sel.Nodes[0])
}
}
func TestGetInvalid(t *testing.T) {
defer assertPanic(t)
sel := Doc().Find(".pvk-content")
sel.Get(129)
}
func TestIndex(t *testing.T) {
sel := Doc().Find(".pvk-content")
if i := sel.Index(); i != 1 {
t.Errorf("Expected index of 1, got %v.", i)
}
}
func TestIndexSelector(t *testing.T) {
sel := Doc().Find(".hero-unit")
if i := sel.IndexSelector("div"); i != 4 {
t.Errorf("Expected index of 4, got %v.", i)
}
}
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 {
t.Errorf("Expected index of 1, got %v.", i)
}
}
func TestIndexOfNilNode(t *testing.T) {
sel := Doc().Find("div.pvk-gutter")
if i := sel.IndexOfNode(nil); i != -1 {
t.Errorf("Expected index of -1, got %v.", i)
}
}
func TestIndexOfSelection(t *testing.T) {
sel := Doc().Find("div")
sel2 := Doc().Find(".hero-unit")
if i := sel.IndexOfSelection(sel2); i != 4 {
t.Errorf("Expected index of 4, got %v.", i)
}
}

View file

@ -0,0 +1,120 @@
package goquery
import (
"testing"
)
func BenchmarkFirst(b *testing.B) {
b.StopTimer()
sel := DocB().Find("dd")
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.First()
}
}
func BenchmarkLast(b *testing.B) {
b.StopTimer()
sel := DocB().Find("dd")
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.Last()
}
}
func BenchmarkEq(b *testing.B) {
b.StopTimer()
sel := DocB().Find("dd")
j := 0
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.Eq(j)
if j++; j >= sel.Length() {
j = 0
}
}
}
func BenchmarkSlice(b *testing.B) {
b.StopTimer()
sel := DocB().Find("dd")
j := 0
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.Slice(j, j+4)
if j++; j >= (sel.Length() - 4) {
j = 0
}
}
}
func BenchmarkGet(b *testing.B) {
b.StopTimer()
sel := DocB().Find("dd")
j := 0
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.Get(j)
if j++; j >= sel.Length() {
j = 0
}
}
}
func BenchmarkIndex(b *testing.B) {
var j int
b.StopTimer()
sel := DocB().Find("#Main")
b.StartTimer()
for i := 0; i < b.N; i++ {
j = sel.Index()
}
if j != 3 {
b.Fatalf("want 3, got %d", j)
}
}
func BenchmarkIndexSelector(b *testing.B) {
var j int
b.StopTimer()
sel := DocB().Find("#manual-nav dl dd:nth-child(1)")
b.StartTimer()
for i := 0; i < b.N; i++ {
j = sel.IndexSelector("dd")
}
if j != 4 {
b.Fatalf("want 4, got %d", j)
}
}
func BenchmarkIndexOfNode(b *testing.B) {
var j int
b.StopTimer()
sel := DocB().Find("span a")
sel2 := DocB().Find("span a:nth-child(3)")
n := sel2.Get(0)
b.StartTimer()
for i := 0; i < b.N; i++ {
j = sel.IndexOfNode(n)
}
if j != 2 {
b.Fatalf("want 2, got %d", j)
}
}
func BenchmarkIndexOfSelection(b *testing.B) {
var j int
b.StopTimer()
sel := DocB().Find("span a")
sel2 := DocB().Find("span a:nth-child(3)")
b.StartTimer()
for i := 0; i < b.N; i++ {
j = sel.IndexOfSelection(sel2)
}
if j != 2 {
b.Fatalf("want 2, got %d", j)
}
}

View file

@ -0,0 +1,40 @@
package goquery
import (
"bytes"
"fmt"
"strconv"
"testing"
)
func BenchmarkMetalReviewExample(b *testing.B) {
var n int
var buf bytes.Buffer
b.StopTimer()
doc := loadDoc("metalreview.html")
b.StartTimer()
for i := 0; i < b.N; i++ {
doc.Find(".slider-row:nth-child(1) .slider-item").Each(func(i int, s *Selection) {
var band, title string
var score float64
var e error
n++
// For each item found, get the band, title and score, and print it
band = s.Find("strong").Text()
title = s.Find("em").Text()
if score, e = strconv.ParseFloat(s.Find(".score").Text(), 64); e != nil {
// Not a valid float, ignore score
if n <= 4 {
buf.WriteString(fmt.Sprintf("Review %d: %s - %s.\n", i, band, title))
}
} else {
// Print all, including score
if n <= 4 {
buf.WriteString(fmt.Sprintf("Review %d: %s - %s (%2.1f).\n", i, band, title, score))
}
}
})
}
}

View file

@ -0,0 +1,104 @@
package goquery
import (
"testing"
)
func BenchmarkAdd(b *testing.B) {
var n int
b.StopTimer()
sel := DocB().Find("dd")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Add("h2[title]").Length()
} else {
sel.Add("h2[title]")
}
}
if n != 43 {
b.Fatalf("want 43, got %d", n)
}
}
func BenchmarkAddSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocB().Find("dd")
sel2 := DocB().Find("h2[title]")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.AddSelection(sel2).Length()
} else {
sel.AddSelection(sel2)
}
}
if n != 43 {
b.Fatalf("want 43, got %d", n)
}
}
func BenchmarkAddNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocB().Find("dd")
sel2 := DocB().Find("h2[title]")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.AddNodes(nodes...).Length()
} else {
sel.AddNodes(nodes...)
}
}
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) {
var n int
b.StopTimer()
sel := DocB().Find("dd").Parent()
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.AndSelf().Length()
} else {
sel.AndSelf()
}
}
if n != 44 {
b.Fatalf("want 44, got %d", n)
}
}

View file

@ -0,0 +1,236 @@
package goquery
import (
"testing"
)
func BenchmarkFilter(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Filter(".toclevel-1").Length()
} else {
sel.Filter(".toclevel-1")
}
}
if n != 13 {
b.Fatalf("want 13, got %d", n)
}
}
func BenchmarkNot(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Not(".toclevel-2").Length()
} else {
sel.Filter(".toclevel-2")
}
}
if n != 371 {
b.Fatalf("want 371, got %d", n)
}
}
func BenchmarkFilterFunction(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
f := func(i int, s *Selection) bool {
return len(s.Get(0).Attr) > 0
}
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.FilterFunction(f).Length()
} else {
sel.FilterFunction(f)
}
}
if n != 112 {
b.Fatalf("want 112, got %d", n)
}
}
func BenchmarkNotFunction(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
f := func(i int, s *Selection) bool {
return len(s.Get(0).Attr) > 0
}
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NotFunction(f).Length()
} else {
sel.NotFunction(f)
}
}
if n != 261 {
b.Fatalf("want 261, got %d", n)
}
}
func BenchmarkFilterNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
sel2 := DocW().Find(".toclevel-2")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.FilterNodes(nodes...).Length()
} else {
sel.FilterNodes(nodes...)
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkNotNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
sel2 := DocW().Find(".toclevel-1")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NotNodes(nodes...).Length()
} else {
sel.NotNodes(nodes...)
}
}
if n != 360 {
b.Fatalf("want 360, got %d", n)
}
}
func BenchmarkFilterSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
sel2 := DocW().Find(".toclevel-2")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.FilterSelection(sel2).Length()
} else {
sel.FilterSelection(sel2)
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkNotSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
sel2 := DocW().Find(".toclevel-1")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NotSelection(sel2).Length()
} else {
sel.NotSelection(sel2)
}
}
if n != 360 {
b.Fatalf("want 360, got %d", n)
}
}
func BenchmarkHas(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Has(".editsection").Length()
} else {
sel.Has(".editsection")
}
}
if n != 13 {
b.Fatalf("want 13, got %d", n)
}
}
func BenchmarkHasNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
sel2 := DocW().Find(".tocnumber")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.HasNodes(nodes...).Length()
} else {
sel.HasNodes(nodes...)
}
}
if n != 15 {
b.Fatalf("want 15, got %d", n)
}
}
func BenchmarkHasSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
sel2 := DocW().Find(".tocnumber")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.HasSelection(sel2).Length()
} else {
sel.HasSelection(sel2)
}
}
if n != 15 {
b.Fatalf("want 15, got %d", n)
}
}
func BenchmarkEnd(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li").Has(".tocnumber")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.End().Length()
} else {
sel.End()
}
}
if n != 373 {
b.Fatalf("want 373, got %d", n)
}
}

View file

@ -0,0 +1,68 @@
package goquery
import (
"testing"
)
func BenchmarkEach(b *testing.B) {
var tmp, n int
b.StopTimer()
sel := DocW().Find("td")
f := func(i int, s *Selection) {
tmp++
}
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.Each(f)
if n == 0 {
n = tmp
}
}
if n != 59 {
b.Fatalf("want 59, got %d", n)
}
}
func BenchmarkMap(b *testing.B) {
var tmp, n int
b.StopTimer()
sel := DocW().Find("td")
f := func(i int, s *Selection) string {
tmp++
return string(tmp)
}
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.Map(f)
if n == 0 {
n = tmp
}
}
if n != 59 {
b.Fatalf("want 59, got %d", n)
}
}
func BenchmarkEachWithBreak(b *testing.B) {
var tmp, n int
b.StopTimer()
sel := DocW().Find("td")
f := func(i int, s *Selection) bool {
tmp++
return tmp < 10
}
b.StartTimer()
for i := 0; i < b.N; i++ {
tmp = 0
sel.EachWithBreak(f)
if n == 0 {
n = tmp
}
}
if n != 10 {
b.Fatalf("want 10, got %d", n)
}
}

View file

@ -0,0 +1,51 @@
package goquery
import (
"testing"
)
func BenchmarkAttr(b *testing.B) {
var s string
b.StopTimer()
sel := DocW().Find("h1")
b.StartTimer()
for i := 0; i < b.N; i++ {
s, _ = sel.Attr("id")
}
if s != "firstHeading" {
b.Fatalf("want firstHeading, got %q", s)
}
}
func BenchmarkText(b *testing.B) {
b.StopTimer()
sel := DocW().Find("h2")
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.Text()
}
}
func BenchmarkLength(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
b.StartTimer()
for i := 0; i < b.N; i++ {
n = sel.Length()
}
if n != 14 {
b.Fatalf("want 14, got %d", n)
}
}
func BenchmarkHtml(b *testing.B) {
b.StopTimer()
sel := DocW().Find("h2")
b.StartTimer()
for i := 0; i < b.N; i++ {
sel.Html()
}
}

View file

@ -0,0 +1,111 @@
package goquery
import (
"testing"
)
func BenchmarkIs(b *testing.B) {
var y bool
b.StopTimer()
sel := DocW().Find("li")
b.StartTimer()
for i := 0; i < b.N; i++ {
y = sel.Is(".toclevel-2")
}
if !y {
b.Fatal("want true")
}
}
func BenchmarkIsPositional(b *testing.B) {
var y bool
b.StopTimer()
sel := DocW().Find("li")
b.StartTimer()
for i := 0; i < b.N; i++ {
y = sel.Is("li:nth-child(2)")
}
if !y {
b.Fatal("want true")
}
}
func BenchmarkIsFunction(b *testing.B) {
var y bool
b.StopTimer()
sel := DocW().Find(".toclevel-1")
f := func(i int, s *Selection) bool {
return i == 8
}
b.StartTimer()
for i := 0; i < b.N; i++ {
y = sel.IsFunction(f)
}
if !y {
b.Fatal("want true")
}
}
func BenchmarkIsSelection(b *testing.B) {
var y bool
b.StopTimer()
sel := DocW().Find("li")
sel2 := DocW().Find(".toclevel-2")
b.StartTimer()
for i := 0; i < b.N; i++ {
y = sel.IsSelection(sel2)
}
if !y {
b.Fatal("want true")
}
}
func BenchmarkIsNodes(b *testing.B) {
var y bool
b.StopTimer()
sel := DocW().Find("li")
sel2 := DocW().Find(".toclevel-2")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
y = sel.IsNodes(nodes...)
}
if !y {
b.Fatal("want true")
}
}
func BenchmarkHasClass(b *testing.B) {
var y bool
b.StopTimer()
sel := DocW().Find("span")
b.StartTimer()
for i := 0; i < b.N; i++ {
y = sel.HasClass("official")
}
if !y {
b.Fatal("want true")
}
}
func BenchmarkContains(b *testing.B) {
var y bool
b.StopTimer()
sel := DocW().Find("span.url")
sel2 := DocW().Find("a[rel=\"nofollow\"]")
node := sel2.Nodes[0]
b.StartTimer()
for i := 0; i < b.N; i++ {
y = sel.Contains(node)
}
if !y {
b.Fatal("want true")
}
}

View file

@ -0,0 +1,802 @@
package goquery
import (
"testing"
)
func BenchmarkFind(b *testing.B) {
var n int
for i := 0; i < b.N; i++ {
if n == 0 {
n = DocB().Find("dd").Length()
} else {
DocB().Find("dd")
}
}
if n != 41 {
b.Fatalf("want 41, got %d", n)
}
}
func BenchmarkFindWithinSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("ul")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Find("a[class]").Length()
} else {
sel.Find("a[class]")
}
}
if n != 39 {
b.Fatalf("want 39, got %d", n)
}
}
func BenchmarkFindSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("ul")
sel2 := DocW().Find("span")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.FindSelection(sel2).Length()
} else {
sel.FindSelection(sel2)
}
}
if n != 73 {
b.Fatalf("want 73, got %d", n)
}
}
func BenchmarkFindNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("ul")
sel2 := DocW().Find("span")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.FindNodes(nodes...).Length()
} else {
sel.FindNodes(nodes...)
}
}
if n != 73 {
b.Fatalf("want 73, got %d", n)
}
}
func BenchmarkContents(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find(".toclevel-1")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Contents().Length()
} else {
sel.Contents()
}
}
if n != 16 {
b.Fatalf("want 16, got %d", n)
}
}
func BenchmarkContentsFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find(".toclevel-1")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ContentsFiltered("a[href=\"#Examples\"]").Length()
} else {
sel.ContentsFiltered("a[href=\"#Examples\"]")
}
}
if n != 1 {
b.Fatalf("want 1, got %d", n)
}
}
func BenchmarkChildren(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find(".toclevel-2")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Children().Length()
} else {
sel.Children()
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkChildrenFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h3")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ChildrenFiltered(".editsection").Length()
} else {
sel.ChildrenFiltered(".editsection")
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkParent(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Parent().Length()
} else {
sel.Parent()
}
}
if n != 55 {
b.Fatalf("want 55, got %d", n)
}
}
func BenchmarkParentFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ParentFiltered("ul[id]").Length()
} else {
sel.ParentFiltered("ul[id]")
}
}
if n != 4 {
b.Fatalf("want 4, got %d", n)
}
}
func BenchmarkParents(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("th a")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Parents().Length()
} else {
sel.Parents()
}
}
if n != 73 {
b.Fatalf("want 73, got %d", n)
}
}
func BenchmarkParentsFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("th a")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ParentsFiltered("tr").Length()
} else {
sel.ParentsFiltered("tr")
}
}
if n != 18 {
b.Fatalf("want 18, got %d", n)
}
}
func BenchmarkParentsUntil(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("th a")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ParentsUntil("table").Length()
} else {
sel.ParentsUntil("table")
}
}
if n != 52 {
b.Fatalf("want 52, got %d", n)
}
}
func BenchmarkParentsUntilSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("th a")
sel2 := DocW().Find("#content")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ParentsUntilSelection(sel2).Length()
} else {
sel.ParentsUntilSelection(sel2)
}
}
if n != 70 {
b.Fatalf("want 70, got %d", n)
}
}
func BenchmarkParentsUntilNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("th a")
sel2 := DocW().Find("#content")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ParentsUntilNodes(nodes...).Length()
} else {
sel.ParentsUntilNodes(nodes...)
}
}
if n != 70 {
b.Fatalf("want 70, got %d", n)
}
}
func BenchmarkParentsFilteredUntil(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find(".toclevel-1 a")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ParentsFilteredUntil(":nth-child(1)", "ul").Length()
} else {
sel.ParentsFilteredUntil(":nth-child(1)", "ul")
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkParentsFilteredUntilSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find(".toclevel-1 a")
sel2 := DocW().Find("ul")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ParentsFilteredUntilSelection(":nth-child(1)", sel2).Length()
} else {
sel.ParentsFilteredUntilSelection(":nth-child(1)", sel2)
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkParentsFilteredUntilNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find(".toclevel-1 a")
sel2 := DocW().Find("ul")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ParentsFilteredUntilNodes(":nth-child(1)", nodes...).Length()
} else {
sel.ParentsFilteredUntilNodes(":nth-child(1)", nodes...)
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkSiblings(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("ul li:nth-child(1)")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Siblings().Length()
} else {
sel.Siblings()
}
}
if n != 293 {
b.Fatalf("want 293, got %d", n)
}
}
func BenchmarkSiblingsFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("ul li:nth-child(1)")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.SiblingsFiltered("[class]").Length()
} else {
sel.SiblingsFiltered("[class]")
}
}
if n != 46 {
b.Fatalf("want 46, got %d", n)
}
}
func BenchmarkNext(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:nth-child(1)")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Next().Length()
} else {
sel.Next()
}
}
if n != 49 {
b.Fatalf("want 49, got %d", n)
}
}
func BenchmarkNextFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:nth-child(1)")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextFiltered("[class]").Length()
} else {
sel.NextFiltered("[class]")
}
}
if n != 6 {
b.Fatalf("want 6, got %d", n)
}
}
func BenchmarkNextAll(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:nth-child(3)")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextAll().Length()
} else {
sel.NextAll()
}
}
if n != 234 {
b.Fatalf("want 234, got %d", n)
}
}
func BenchmarkNextAllFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:nth-child(3)")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextAllFiltered("[class]").Length()
} else {
sel.NextAllFiltered("[class]")
}
}
if n != 33 {
b.Fatalf("want 33, got %d", n)
}
}
func BenchmarkPrev(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:last-child")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Prev().Length()
} else {
sel.Prev()
}
}
if n != 49 {
b.Fatalf("want 49, got %d", n)
}
}
func BenchmarkPrevFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:last-child")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevFiltered("[class]").Length()
} else {
sel.PrevFiltered("[class]")
}
}
// 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)
if n != 7 {
b.Fatalf("want 7, got %d", n)
}
}
func BenchmarkPrevAll(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:nth-child(4)")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevAll().Length()
} else {
sel.PrevAll()
}
}
if n != 78 {
b.Fatalf("want 78, got %d", n)
}
}
func BenchmarkPrevAllFiltered(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:nth-child(4)")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevAllFiltered("[class]").Length()
} else {
sel.PrevAllFiltered("[class]")
}
}
if n != 6 {
b.Fatalf("want 6, got %d", n)
}
}
func BenchmarkNextUntil(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:first-child")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextUntil(":nth-child(4)").Length()
} else {
sel.NextUntil(":nth-child(4)")
}
}
if n != 84 {
b.Fatalf("want 84, got %d", n)
}
}
func BenchmarkNextUntilSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
sel2 := DocW().Find("ul")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextUntilSelection(sel2).Length()
} else {
sel.NextUntilSelection(sel2)
}
}
if n != 42 {
b.Fatalf("want 42, got %d", n)
}
}
func BenchmarkNextUntilNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
sel2 := DocW().Find("p")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextUntilNodes(nodes...).Length()
} else {
sel.NextUntilNodes(nodes...)
}
}
if n != 12 {
b.Fatalf("want 12, got %d", n)
}
}
func BenchmarkPrevUntil(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("li:last-child")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevUntil(":nth-child(4)").Length()
} else {
sel.PrevUntil(":nth-child(4)")
}
}
if n != 238 {
b.Fatalf("want 238, got %d", n)
}
}
func BenchmarkPrevUntilSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
sel2 := DocW().Find("ul")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevUntilSelection(sel2).Length()
} else {
sel.PrevUntilSelection(sel2)
}
}
if n != 49 {
b.Fatalf("want 49, got %d", n)
}
}
func BenchmarkPrevUntilNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
sel2 := DocW().Find("p")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevUntilNodes(nodes...).Length()
} else {
sel.PrevUntilNodes(nodes...)
}
}
if n != 11 {
b.Fatalf("want 11, got %d", n)
}
}
func BenchmarkNextFilteredUntil(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextFilteredUntil("p", "div").Length()
} else {
sel.NextFilteredUntil("p", "div")
}
}
if n != 22 {
b.Fatalf("want 22, got %d", n)
}
}
func BenchmarkNextFilteredUntilSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
sel2 := DocW().Find("div")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextFilteredUntilSelection("p", sel2).Length()
} else {
sel.NextFilteredUntilSelection("p", sel2)
}
}
if n != 22 {
b.Fatalf("want 22, got %d", n)
}
}
func BenchmarkNextFilteredUntilNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
sel2 := DocW().Find("div")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.NextFilteredUntilNodes("p", nodes...).Length()
} else {
sel.NextFilteredUntilNodes("p", nodes...)
}
}
if n != 22 {
b.Fatalf("want 22, got %d", n)
}
}
func BenchmarkPrevFilteredUntil(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevFilteredUntil("p", "div").Length()
} else {
sel.PrevFilteredUntil("p", "div")
}
}
if n != 20 {
b.Fatalf("want 20, got %d", n)
}
}
func BenchmarkPrevFilteredUntilSelection(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
sel2 := DocW().Find("div")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevFilteredUntilSelection("p", sel2).Length()
} else {
sel.PrevFilteredUntilSelection("p", sel2)
}
}
if n != 20 {
b.Fatalf("want 20, got %d", n)
}
}
func BenchmarkPrevFilteredUntilNodes(b *testing.B) {
var n int
b.StopTimer()
sel := DocW().Find("h2")
sel2 := DocW().Find("div")
nodes := sel2.Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.PrevFilteredUntilNodes("p", nodes...).Length()
} else {
sel.PrevFilteredUntilNodes("p", nodes...)
}
}
if n != 20 {
b.Fatalf("want 20, got %d", n)
}
}
func BenchmarkClosest(b *testing.B) {
var n int
b.StopTimer()
sel := Doc().Find(".container-fluid")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.Closest(".pvk-content").Length()
} else {
sel.Closest(".pvk-content")
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkClosestSelection(b *testing.B) {
var n int
b.StopTimer()
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".pvk-content")
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ClosestSelection(sel2).Length()
} else {
sel.ClosestSelection(sel2)
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}
func BenchmarkClosestNodes(b *testing.B) {
var n int
b.StopTimer()
sel := Doc().Find(".container-fluid")
nodes := Doc().Find(".pvk-content").Nodes
b.StartTimer()
for i := 0; i < b.N; i++ {
if n == 0 {
n = sel.ClosestNodes(nodes...).Length()
} else {
sel.ClosestNodes(nodes...)
}
}
if n != 2 {
b.Fatalf("want 2, got %d", n)
}
}

123
vendor/github.com/PuerkitoBio/goquery/doc.go generated vendored Normal file
View file

@ -0,0 +1,123 @@
// 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:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation and/or
// other materials provided with the distribution.
// * Neither the name of the author nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
// WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/*
Package goquery implements features similar to jQuery, including the chainable
syntax, to manipulate and query an HTML document.
It brings a syntax and a set of features similar to jQuery to the Go language.
It is based on Go's net/html package and the CSS Selector library cascadia.
Since the net/html parser returns nodes, and not a full-featured DOM
tree, jQuery's stateful manipulation functions (like height(), css(), detach())
have been left off.
Also, because the net/html parser requires UTF-8 encoding, so does goquery: it is
the caller's responsibility to ensure that the source document provides UTF-8 encoded HTML.
See the repository's wiki for various options on how to do this.
Syntax-wise, it is as close as possible to jQuery, with the same method names when
possible, and that warm and fuzzy chainable interface. jQuery being the
ultra-popular library that it is, writing a similar HTML-manipulating
library was better to follow its API than to start anew (in the same spirit as
Go's fmt package), even though some of its methods are less than intuitive (looking
at you, index()...).
It is hosted on GitHub, along with additional documentation in the README.md
file: https://github.com/puerkitobio/goquery
Please note that because of the net/html dependency, goquery requires Go1.1+.
The various methods are split into files based on the category of behavior.
The three dots (...) indicate that various "overloads" are available.
* array.go : array-like positional manipulation of the selection.
- Eq()
- First()
- Get()
- Index...()
- Last()
- Slice()
* expand.go : methods that expand or augment the selection's set.
- Add...()
- AndSelf()
- Union(), which is an alias for AddSelection()
* filter.go : filtering methods, that reduce the selection's set.
- End()
- Filter...()
- Has...()
- Intersection(), which is an alias of FilterSelection()
- Not...()
* iteration.go : methods to loop over the selection's nodes.
- Each()
- EachWithBreak()
- Map()
* manipulation.go : methods for modifying the document
- After...()
- Append...()
- Before...()
- Clone()
- Empty()
- Prepend...()
- Remove...()
- ReplaceWith...()
- Unwrap()
- Wrap...()
- WrapAll...()
- WrapInner...()
* property.go : methods that inspect and get the node's properties values.
- Attr*(), RemoveAttr(), SetAttr()
- AddClass(), HasClass(), RemoveClass(), ToggleClass()
- Html()
- Length()
- Size(), which is an alias for Length()
- Text()
* query.go : methods that query, or reflect, a node's identity.
- Contains()
- Is...()
* traversal.go : methods to traverse the HTML document tree.
- Children...()
- Contents()
- Find...()
- Next...()
- Parent[s]...()
- Prev...()
- Siblings...()
* type.go : definition of the types exposed by goquery.
- Document
- Selection
- Matcher
* utilities.go : definition of helper functions (and not methods on a *Selection)
that are not part of jQuery, but are useful to goquery.
- NodeName
- OuterHtml
*/
package goquery

82
vendor/github.com/PuerkitoBio/goquery/example_test.go generated vendored Normal file
View file

@ -0,0 +1,82 @@
package goquery_test
import (
"fmt"
"log"
"net/http"
"os"
"strings"
"github.com/PuerkitoBio/goquery"
)
// This example scrapes the reviews shown on the home page of 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
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)
})
// To see the output of the Example while running the test suite (go test), simply
// remove the leading "x" before Output on the next line. This will cause the
// example to fail (all the "real" tests should pass).
// 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
}

70
vendor/github.com/PuerkitoBio/goquery/expand.go generated vendored Normal file
View file

@ -0,0 +1,70 @@
package goquery
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}, compileMatcher(selector))...)
}
// AddMatcher adds the matcher's matching nodes to those in the current
// selection and returns a new Selection object.
// The matcher is run in the context of the document of the current
// Selection object.
func (s *Selection) AddMatcher(m Matcher) *Selection {
return s.AddNodes(findWithMatcher([]*html.Node{s.document.rootNode}, m)...)
}
// AddSelection adds the specified Selection object's nodes to those in the
// current selection and returns a new Selection object.
func (s *Selection) AddSelection(sel *Selection) *Selection {
if sel == nil {
return s.AddNodes()
}
return s.AddNodes(sel.Nodes...)
}
// Union is an alias for AddSelection.
func (s *Selection) Union(sel *Selection) *Selection {
return s.AddSelection(sel)
}
// 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, 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))
}

118
vendor/github.com/PuerkitoBio/goquery/expand_test.go generated vendored Normal file
View file

@ -0,0 +1,118 @@
package goquery
import (
"testing"
)
func TestAdd(t *testing.T) {
sel := Doc().Find("div.row-fluid").Add("a")
assertLength(t, sel.Nodes, 19)
}
func TestAddInvalid(t *testing.T) {
sel1 := Doc().Find("div.row-fluid")
sel2 := sel1.Add("")
assertLength(t, sel1.Nodes, 9)
assertLength(t, sel2.Nodes, 9)
if sel1 == sel2 {
t.Errorf("selections should not be the same")
}
}
func TestAddRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Add("a").End()
assertEqual(t, sel, sel2)
}
func TestAddSelection(t *testing.T) {
sel := Doc().Find("div.row-fluid")
sel2 := Doc().Find("a")
sel = sel.AddSelection(sel2)
assertLength(t, sel.Nodes, 19)
}
func TestAddSelectionNil(t *testing.T) {
sel := Doc().Find("div.row-fluid")
assertLength(t, sel.Nodes, 9)
sel = sel.AddSelection(nil)
assertLength(t, sel.Nodes, 9)
}
func TestAddSelectionRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Find("a")
sel2 = sel.AddSelection(sel2).End()
assertEqual(t, sel, sel2)
}
func TestAddNodes(t *testing.T) {
sel := Doc().Find("div.pvk-gutter")
sel2 := Doc().Find(".pvk-content")
sel = sel.AddNodes(sel2.Nodes...)
assertLength(t, sel.Nodes, 9)
}
func TestAddNodesNone(t *testing.T) {
sel := Doc().Find("div.pvk-gutter").AddNodes()
assertLength(t, sel.Nodes, 6)
}
func TestAddNodesRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Find("a")
sel2 = sel.AddNodes(sel2.Nodes...).End()
assertEqual(t, sel, sel2)
}
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)
}
func TestAndSelfRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
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)
}

163
vendor/github.com/PuerkitoBio/goquery/filter.go generated vendored Normal file
View file

@ -0,0 +1,163 @@
package goquery
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(compileMatcher(selector))
}
// FilterMatcher reduces the set of matched elements to those that match
// the given matcher. It returns a new Selection object for this subset
// of matching elements.
func (s *Selection) FilterMatcher(m Matcher) *Selection {
return pushStack(s, winnow(s, m, true))
}
// Not removes elements from the Selection that match the selector string.
// It returns a new Selection object with the matching elements removed.
func (s *Selection) Not(selector string) *Selection {
return s.NotMatcher(compileMatcher(selector))
}
// NotMatcher removes elements from the Selection that match the given matcher.
// It returns a new Selection object with the matching elements removed.
func (s *Selection) NotMatcher(m Matcher) *Selection {
return pushStack(s, winnow(s, m, false))
}
// FilterFunction reduces the set of matched elements to those that pass the function's test.
// It returns a new Selection object for this subset of elements.
func (s *Selection) FilterFunction(f func(int, *Selection) bool) *Selection {
return pushStack(s, winnowFunction(s, f, true))
}
// NotFunction removes elements from the Selection that pass the function's test.
// It returns a new Selection object with the matching elements removed.
func (s *Selection) NotFunction(f func(int, *Selection) bool) *Selection {
return pushStack(s, winnowFunction(s, f, false))
}
// FilterNodes reduces the set of matched elements to those that match the specified nodes.
// It returns a new Selection object for this subset of elements.
func (s *Selection) FilterNodes(nodes ...*html.Node) *Selection {
return pushStack(s, winnowNodes(s, nodes, true))
}
// NotNodes removes elements from the Selection that match the specified nodes.
// It returns a new Selection object with the matching elements removed.
func (s *Selection) NotNodes(nodes ...*html.Node) *Selection {
return pushStack(s, winnowNodes(s, nodes, false))
}
// FilterSelection reduces the set of matched elements to those that match a
// node in the specified Selection object.
// It returns a new Selection object for this subset of elements.
func (s *Selection) FilterSelection(sel *Selection) *Selection {
if sel == nil {
return pushStack(s, winnowNodes(s, nil, true))
}
return pushStack(s, winnowNodes(s, sel.Nodes, true))
}
// NotSelection removes elements from the Selection that match a node in the specified
// Selection object. It returns a new Selection object with the matching elements removed.
func (s *Selection) NotSelection(sel *Selection) *Selection {
if sel == nil {
return pushStack(s, winnowNodes(s, nil, false))
}
return pushStack(s, winnowNodes(s, sel.Nodes, false))
}
// Intersection is an alias for FilterSelection.
func (s *Selection) Intersection(sel *Selection) *Selection {
return s.FilterSelection(sel)
}
// Has reduces the set of matched elements to those that have a descendant
// that matches the selector.
// It returns a new Selection object with the matching elements.
func (s *Selection) Has(selector string) *Selection {
return s.HasSelection(s.document.Find(selector))
}
// HasMatcher reduces the set of matched elements to those that have a descendant
// that matches the matcher.
// It returns a new Selection object with the matching elements.
func (s *Selection) HasMatcher(m Matcher) *Selection {
return s.HasSelection(s.document.FindMatcher(m))
}
// HasNodes reduces the set of matched elements to those that have a
// descendant that matches one of the nodes.
// It returns a new Selection object with the matching elements.
func (s *Selection) HasNodes(nodes ...*html.Node) *Selection {
return s.FilterFunction(func(_ int, sel *Selection) bool {
// Add all nodes that contain one of the specified nodes
for _, n := range nodes {
if sel.Contains(n) {
return true
}
}
return false
})
}
// HasSelection reduces the set of matched elements to those that have a
// descendant that matches one of the nodes of the specified Selection object.
// It returns a new Selection object with the matching elements.
func (s *Selection) HasSelection(sel *Selection) *Selection {
if sel == nil {
return s.HasNodes()
}
return s.HasNodes(sel.Nodes...)
}
// End ends the most recent filtering operation in the current chain and
// returns the set of matched elements to its previous state.
func (s *Selection) End() *Selection {
if s.prevSel != nil {
return s.prevSel
}
return newEmptySelection(s.document)
}
// Filter based on the matcher, and the indicator to keep (Filter) or
// to get rid of (Not) the matching elements.
func winnow(sel *Selection, m Matcher, keep bool) []*html.Node {
// Optimize if keep is requested
if keep {
return m.Filter(sel.Nodes)
}
// Use grep
return grep(sel, func(i int, s *Selection) bool {
return !m.Match(s.Get(0))
})
}
// Filter based on an array of nodes, and the indicator to keep (Filter) or
// to get rid of (Not) the matching elements.
func winnowNodes(sel *Selection, nodes []*html.Node, keep bool) []*html.Node {
if len(nodes)+len(sel.Nodes) < minNodesForSet {
return grep(sel, func(i int, s *Selection) bool {
return isInSlice(nodes, s.Get(0)) == keep
})
}
set := make(map[*html.Node]bool)
for _, n := range nodes {
set[n] = true
}
return grep(sel, func(i int, s *Selection) bool {
return set[s.Get(0)] == keep
})
}
// Filter based on a function test, and the indicator to keep (Filter) or
// to get rid of (Not) the matching elements.
func winnowFunction(sel *Selection, f func(int, *Selection) bool, keep bool) []*html.Node {
return grep(sel, func(i int, s *Selection) bool {
return f(i, s) == keep
})
}

206
vendor/github.com/PuerkitoBio/goquery/filter_test.go generated vendored Normal file
View file

@ -0,0 +1,206 @@
package goquery
import (
"testing"
)
func TestFilter(t *testing.T) {
sel := Doc().Find(".span12").Filter(".alert")
assertLength(t, sel.Nodes, 1)
}
func TestFilterNone(t *testing.T) {
sel := Doc().Find(".span12").Filter(".zzalert")
assertLength(t, sel.Nodes, 0)
}
func TestFilterInvalid(t *testing.T) {
sel := Doc().Find(".span12").Filter("")
assertLength(t, sel.Nodes, 0)
}
func TestFilterRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Filter(".alert").End()
assertEqual(t, sel, sel2)
}
func TestFilterFunction(t *testing.T) {
sel := Doc().Find(".pvk-content").FilterFunction(func(i int, s *Selection) bool {
return i > 0
})
assertLength(t, sel.Nodes, 2)
}
func TestFilterFunctionRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.FilterFunction(func(i int, s *Selection) bool {
return i > 0
}).End()
assertEqual(t, sel, sel2)
}
func TestFilterNode(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.FilterNodes(sel.Nodes[2])
assertLength(t, sel2.Nodes, 1)
}
func TestFilterNodeRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.FilterNodes(sel.Nodes[2]).End()
assertEqual(t, sel, sel2)
}
func TestFilterSelection(t *testing.T) {
sel := Doc().Find(".link")
sel2 := Doc().Find("a[ng-click]")
sel3 := sel.FilterSelection(sel2)
assertLength(t, sel3.Nodes, 1)
}
func TestFilterSelectionRollback(t *testing.T) {
sel := Doc().Find(".link")
sel2 := Doc().Find("a[ng-click]")
sel2 = sel.FilterSelection(sel2).End()
assertEqual(t, sel, sel2)
}
func TestFilterSelectionNil(t *testing.T) {
var sel2 *Selection
sel := Doc().Find(".link")
sel3 := sel.FilterSelection(sel2)
assertLength(t, sel3.Nodes, 0)
}
func TestNot(t *testing.T) {
sel := Doc().Find(".span12").Not(".alert")
assertLength(t, sel.Nodes, 1)
}
func TestNotInvalid(t *testing.T) {
sel := Doc().Find(".span12").Not("")
assertLength(t, sel.Nodes, 2)
}
func TestNotRollback(t *testing.T) {
sel := Doc().Find(".span12")
sel2 := sel.Not(".alert").End()
assertEqual(t, sel, sel2)
}
func TestNotNone(t *testing.T) {
sel := Doc().Find(".span12").Not(".zzalert")
assertLength(t, sel.Nodes, 2)
}
func TestNotFunction(t *testing.T) {
sel := Doc().Find(".pvk-content").NotFunction(func(i int, s *Selection) bool {
return i > 0
})
assertLength(t, sel.Nodes, 1)
}
func TestNotFunctionRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.NotFunction(func(i int, s *Selection) bool {
return i > 0
}).End()
assertEqual(t, sel, sel2)
}
func TestNotNode(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.NotNodes(sel.Nodes[2])
assertLength(t, sel2.Nodes, 2)
}
func TestNotNodeRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.NotNodes(sel.Nodes[2]).End()
assertEqual(t, sel, sel2)
}
func TestNotSelection(t *testing.T) {
sel := Doc().Find(".link")
sel2 := Doc().Find("a[ng-click]")
sel3 := sel.NotSelection(sel2)
assertLength(t, sel3.Nodes, 6)
}
func TestNotSelectionRollback(t *testing.T) {
sel := Doc().Find(".link")
sel2 := Doc().Find("a[ng-click]")
sel2 = sel.NotSelection(sel2).End()
assertEqual(t, sel, sel2)
}
func TestIntersection(t *testing.T) {
sel := Doc().Find(".pvk-gutter")
sel2 := Doc().Find("div").Intersection(sel)
assertLength(t, sel2.Nodes, 6)
}
func TestIntersectionRollback(t *testing.T) {
sel := Doc().Find(".pvk-gutter")
sel2 := Doc().Find("div")
sel2 = sel.Intersection(sel2).End()
assertEqual(t, sel, sel2)
}
func TestHas(t *testing.T) {
sel := Doc().Find(".container-fluid").Has(".center-content")
assertLength(t, sel.Nodes, 2)
// Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content
}
func TestHasInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").Has("")
assertLength(t, sel.Nodes, 0)
}
func TestHasRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.Has(".center-content").End()
assertEqual(t, sel, sel2)
}
func TestHasNodes(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".center-content")
sel = sel.HasNodes(sel2.Nodes...)
assertLength(t, sel.Nodes, 2)
// Has() returns the high-level .container-fluid div, and the one that is the immediate parent of center-content
}
func TestHasNodesRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".center-content")
sel2 = sel.HasNodes(sel2.Nodes...).End()
assertEqual(t, sel, sel2)
}
func TestHasSelection(t *testing.T) {
sel := Doc().Find("p")
sel2 := Doc().Find("small")
sel = sel.HasSelection(sel2)
assertLength(t, sel.Nodes, 1)
}
func TestHasSelectionRollback(t *testing.T) {
sel := Doc().Find("p")
sel2 := Doc().Find("small")
sel2 = sel.HasSelection(sel2).End()
assertEqual(t, sel, sel2)
}
func TestEnd(t *testing.T) {
sel := Doc().Find("p").Has("small").End()
assertLength(t, sel.Nodes, 4)
}
func TestEndToTop(t *testing.T) {
sel := Doc().Find("p").Has("small").End().End().End()
assertLength(t, sel.Nodes, 0)
}

6
vendor/github.com/PuerkitoBio/goquery/go.mod generated vendored Normal file
View file

@ -0,0 +1,6 @@
module github.com/PuerkitoBio/goquery
require (
github.com/andybalholm/cascadia v1.0.0
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a
)

5
vendor/github.com/PuerkitoBio/goquery/go.sum generated vendored Normal file
View file

@ -0,0 +1,5 @@
github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o=
github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a h1:gOpx8G595UYyvj8UK4+OFyY4rx037g3fmfhe5SasG3U=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

39
vendor/github.com/PuerkitoBio/goquery/iteration.go generated vendored Normal file
View file

@ -0,0 +1,39 @@
package goquery
// Each iterates over a Selection object, executing a function for each
// 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))
}
return s
}
// EachWithBreak iterates over a Selection object, executing a function for each
// matched element. It is identical to Each except that it is possible to break
// out of the loop by returning false in the callback function. It returns the
// current Selection object.
func (s *Selection) EachWithBreak(f func(int, *Selection) bool) *Selection {
for i, n := range s.Nodes {
if !f(i, newSingleSelection(n, s.document)) {
return s
}
}
return s
}
// Map passes each element in the current matched set through a function,
// 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)))
}
return result
}

View file

@ -0,0 +1,88 @@
package goquery
import (
"testing"
"golang.org/x/net/html"
)
func TestEach(t *testing.T) {
var cnt int
sel := Doc().Find(".hero-unit .row-fluid").Each(func(i int, n *Selection) {
cnt++
t.Logf("At index %v, node %v", i, n.Nodes[0].Data)
}).Find("a")
if cnt != 4 {
t.Errorf("Expected Each() to call function 4 times, got %v times.", cnt)
}
assertLength(t, sel.Nodes, 6)
}
func TestEachWithBreak(t *testing.T) {
var cnt int
sel := Doc().Find(".hero-unit .row-fluid").EachWithBreak(func(i int, n *Selection) bool {
cnt++
t.Logf("At index %v, node %v", i, n.Nodes[0].Data)
return false
}).Find("a")
if cnt != 1 {
t.Errorf("Expected Each() to call function 1 time, got %v times.", cnt)
}
assertLength(t, sel.Nodes, 6)
}
func TestEachEmptySelection(t *testing.T) {
var cnt int
sel := Doc().Find("zzzz")
sel.Each(func(i int, n *Selection) {
cnt++
})
if cnt > 0 {
t.Error("Expected Each() to not be called on empty Selection.")
}
sel2 := sel.Find("div")
assertLength(t, sel2.Nodes, 0)
}
func TestMap(t *testing.T) {
sel := Doc().Find(".pvk-content")
vals := sel.Map(func(i int, s *Selection) string {
n := s.Get(0)
if n.Type == html.ElementNode {
return n.Data
}
return ""
})
for _, v := range vals {
if v != "div" {
t.Error("Expected Map array result to be all 'div's.")
}
}
if len(vals) != 3 {
t.Errorf("Expected Map array result to have a length of 3, found %v.", len(vals))
}
}
func TestForRange(t *testing.T) {
sel := Doc().Find(".pvk-content")
initLen := sel.Length()
for i := range sel.Nodes {
single := sel.Eq(i)
//h, err := single.Html()
//if err != nil {
// t.Fatal(err)
//}
//fmt.Println(i, h)
if single.Length() != 1 {
t.Errorf("%d: expected length of 1, got %d", i, single.Length())
}
}
if sel.Length() != initLen {
t.Errorf("expected initial selection to still have length %d, got %d", initLen, sel.Length())
}
}

574
vendor/github.com/PuerkitoBio/goquery/manipulation.go generated vendored Normal file
View file

@ -0,0 +1,574 @@
package goquery
import (
"strings"
"golang.org/x/net/html"
)
// After applies the selector from the root document and inserts the matched elements
// after the elements in the set of matched elements.
//
// If one of the matched elements in the selection is not currently in the
// document, it's impossible to insert nodes after it, so it will be ignored.
//
// This follows the same rules as Selection.Append.
func (s *Selection) After(selector string) *Selection {
return s.AfterMatcher(compileMatcher(selector))
}
// AfterMatcher applies the matcher from the root document and inserts the matched elements
// after the elements in the set of matched elements.
//
// If one of the matched elements in the selection is not currently in the
// document, it's impossible to insert nodes after it, so it will be ignored.
//
// This follows the same rules as Selection.Append.
func (s *Selection) AfterMatcher(m Matcher) *Selection {
return s.AfterNodes(m.MatchAll(s.document.rootNode)...)
}
// AfterSelection inserts the elements in the selection after each element in the set of matched
// elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) AfterSelection(sel *Selection) *Selection {
return s.AfterNodes(sel.Nodes...)
}
// AfterHtml parses the html and inserts it after the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) AfterHtml(html string) *Selection {
return s.AfterNodes(parseHtml(html)...)
}
// AfterNodes inserts the nodes after each element in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) AfterNodes(ns ...*html.Node) *Selection {
return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) {
if sn.Parent != nil {
sn.Parent.InsertBefore(n, sn.NextSibling)
}
})
}
// Append appends the elements specified by the selector to the end of each element
// in the set of matched elements, following those rules:
//
// 1) The selector is applied to the root document.
//
// 2) Elements that are part of the document will be moved to the new location.
//
// 3) If there are multiple locations to append to, cloned nodes will be
// appended to all target locations except the last one, which will be moved
// as noted in (2).
func (s *Selection) Append(selector string) *Selection {
return s.AppendMatcher(compileMatcher(selector))
}
// AppendMatcher appends the elements specified by the matcher to the end of each element
// in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) AppendMatcher(m Matcher) *Selection {
return s.AppendNodes(m.MatchAll(s.document.rootNode)...)
}
// AppendSelection appends the elements in the selection to the end of each element
// in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) AppendSelection(sel *Selection) *Selection {
return s.AppendNodes(sel.Nodes...)
}
// AppendHtml parses the html and appends it to the set of matched elements.
func (s *Selection) AppendHtml(html string) *Selection {
return s.AppendNodes(parseHtml(html)...)
}
// AppendNodes appends the specified nodes to each node in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) AppendNodes(ns ...*html.Node) *Selection {
return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) {
sn.AppendChild(n)
})
}
// Before inserts the matched elements before each element in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) Before(selector string) *Selection {
return s.BeforeMatcher(compileMatcher(selector))
}
// BeforeMatcher inserts the matched elements before each element in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) BeforeMatcher(m Matcher) *Selection {
return s.BeforeNodes(m.MatchAll(s.document.rootNode)...)
}
// BeforeSelection inserts the elements in the selection before each element in the set of matched
// elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) BeforeSelection(sel *Selection) *Selection {
return s.BeforeNodes(sel.Nodes...)
}
// BeforeHtml parses the html and inserts it before the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) BeforeHtml(html string) *Selection {
return s.BeforeNodes(parseHtml(html)...)
}
// BeforeNodes inserts the nodes before each element in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) BeforeNodes(ns ...*html.Node) *Selection {
return s.manipulateNodes(ns, false, func(sn *html.Node, n *html.Node) {
if sn.Parent != nil {
sn.Parent.InsertBefore(n, sn)
}
})
}
// Clone creates a deep copy of the set of matched nodes. The new nodes will not be
// attached to the document.
func (s *Selection) Clone() *Selection {
ns := newEmptySelection(s.document)
ns.Nodes = cloneNodes(s.Nodes)
return ns
}
// Empty removes all children nodes from the set of matched elements.
// It returns the children nodes in a new Selection.
func (s *Selection) Empty() *Selection {
var nodes []*html.Node
for _, n := range s.Nodes {
for c := n.FirstChild; c != nil; c = n.FirstChild {
n.RemoveChild(c)
nodes = append(nodes, c)
}
}
return pushStack(s, nodes)
}
// Prepend prepends the elements specified by the selector to each element in
// the set of matched elements, following the same rules as Append.
func (s *Selection) Prepend(selector string) *Selection {
return s.PrependMatcher(compileMatcher(selector))
}
// PrependMatcher prepends the elements specified by the matcher to each
// element in the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) PrependMatcher(m Matcher) *Selection {
return s.PrependNodes(m.MatchAll(s.document.rootNode)...)
}
// PrependSelection prepends the elements in the selection to each element in
// the set of matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) PrependSelection(sel *Selection) *Selection {
return s.PrependNodes(sel.Nodes...)
}
// PrependHtml parses the html and prepends it to the set of matched elements.
func (s *Selection) PrependHtml(html string) *Selection {
return s.PrependNodes(parseHtml(html)...)
}
// PrependNodes prepends the specified nodes to each node in the set of
// matched elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) PrependNodes(ns ...*html.Node) *Selection {
return s.manipulateNodes(ns, true, func(sn *html.Node, n *html.Node) {
// sn.FirstChild may be nil, in which case this functions like
// sn.AppendChild()
sn.InsertBefore(n, sn.FirstChild)
})
}
// Remove removes the set of matched elements from the document.
// It returns the same selection, now consisting of nodes not in the document.
func (s *Selection) Remove() *Selection {
for _, n := range s.Nodes {
if n.Parent != nil {
n.Parent.RemoveChild(n)
}
}
return s
}
// RemoveFiltered removes the set of matched elements by selector.
// It returns the Selection of removed nodes.
func (s *Selection) RemoveFiltered(selector string) *Selection {
return s.RemoveMatcher(compileMatcher(selector))
}
// RemoveMatcher removes the set of matched elements.
// It returns the Selection of removed nodes.
func (s *Selection) RemoveMatcher(m Matcher) *Selection {
return s.FilterMatcher(m).Remove()
}
// ReplaceWith replaces each element in the set of matched elements with the
// nodes matched by the given selector.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWith(selector string) *Selection {
return s.ReplaceWithMatcher(compileMatcher(selector))
}
// ReplaceWithMatcher replaces each element in the set of matched elements with
// the nodes matched by the given Matcher.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWithMatcher(m Matcher) *Selection {
return s.ReplaceWithNodes(m.MatchAll(s.document.rootNode)...)
}
// ReplaceWithSelection replaces each element in the set of matched elements with
// the nodes from the given Selection.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWithSelection(sel *Selection) *Selection {
return s.ReplaceWithNodes(sel.Nodes...)
}
// ReplaceWithHtml replaces each element in the set of matched elements with
// the parsed HTML.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWithHtml(html string) *Selection {
return s.ReplaceWithNodes(parseHtml(html)...)
}
// ReplaceWithNodes replaces each element in the set of matched elements with
// the given nodes.
// It returns the removed elements.
//
// This follows the same rules as Selection.Append.
func (s *Selection) ReplaceWithNodes(ns ...*html.Node) *Selection {
s.AfterNodes(ns...)
return s.Remove()
}
// SetHtml sets the html content of each element in the selection to
// specified html string.
func (s *Selection) SetHtml(html string) *Selection {
return setHtmlNodes(s, parseHtml(html)...)
}
// SetText sets the content of each element in the selection to specified content.
// The provided text string is escaped.
func (s *Selection) SetText(text string) *Selection {
return s.SetHtml(html.EscapeString(text))
}
// Unwrap removes the parents of the set of matched elements, leaving the matched
// elements (and their siblings, if any) in their place.
// It returns the original selection.
func (s *Selection) Unwrap() *Selection {
s.Parent().Each(func(i int, ss *Selection) {
// For some reason, jquery allows unwrap to remove the <head> element, so
// allowing it here too. Same for <html>. Why it allows those elements to
// be unwrapped while not allowing body is a mystery to me.
if ss.Nodes[0].Data != "body" {
ss.ReplaceWithSelection(ss.Contents())
}
})
return s
}
// Wrap wraps each element in the set of matched elements inside the first
// element matched by the given selector. The matched child is cloned before
// being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) Wrap(selector string) *Selection {
return s.WrapMatcher(compileMatcher(selector))
}
// WrapMatcher wraps each element in the set of matched elements inside the
// first element matched by the given matcher. The matched child is cloned
// before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapMatcher(m Matcher) *Selection {
return s.wrapNodes(m.MatchAll(s.document.rootNode)...)
}
// WrapSelection wraps each element in the set of matched elements inside the
// first element in the given Selection. The element is cloned before being
// inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapSelection(sel *Selection) *Selection {
return s.wrapNodes(sel.Nodes...)
}
// WrapHtml wraps each element in the set of matched elements inside the inner-
// most child of the given HTML.
//
// It returns the original set of elements.
func (s *Selection) WrapHtml(html string) *Selection {
return s.wrapNodes(parseHtml(html)...)
}
// WrapNode wraps each element in the set of matched elements inside the inner-
// most child of the given node. The given node is copied before being inserted
// into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapNode(n *html.Node) *Selection {
return s.wrapNodes(n)
}
func (s *Selection) wrapNodes(ns ...*html.Node) *Selection {
s.Each(func(i int, ss *Selection) {
ss.wrapAllNodes(ns...)
})
return s
}
// WrapAll wraps a single HTML structure, matched by the given selector, around
// all elements in the set of matched elements. The matched child is cloned
// before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapAll(selector string) *Selection {
return s.WrapAllMatcher(compileMatcher(selector))
}
// WrapAllMatcher wraps a single HTML structure, matched by the given Matcher,
// around all elements in the set of matched elements. The matched child is
// cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllMatcher(m Matcher) *Selection {
return s.wrapAllNodes(m.MatchAll(s.document.rootNode)...)
}
// WrapAllSelection wraps a single HTML structure, the first node of the given
// Selection, around all elements in the set of matched elements. The matched
// child is cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllSelection(sel *Selection) *Selection {
return s.wrapAllNodes(sel.Nodes...)
}
// WrapAllHtml wraps the given HTML structure around all elements in the set of
// matched elements. The matched child is cloned before being inserted into the
// document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllHtml(html string) *Selection {
return s.wrapAllNodes(parseHtml(html)...)
}
func (s *Selection) wrapAllNodes(ns ...*html.Node) *Selection {
if len(ns) > 0 {
return s.WrapAllNode(ns[0])
}
return s
}
// WrapAllNode wraps the given node around the first element in the Selection,
// making all other nodes in the Selection children of the given node. The node
// is cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapAllNode(n *html.Node) *Selection {
if s.Size() == 0 {
return s
}
wrap := cloneNode(n)
first := s.Nodes[0]
if first.Parent != nil {
first.Parent.InsertBefore(wrap, first)
first.Parent.RemoveChild(first)
}
for c := getFirstChildEl(wrap); c != nil; c = getFirstChildEl(wrap) {
wrap = c
}
newSingleSelection(wrap, s.document).AppendSelection(s)
return s
}
// WrapInner wraps an HTML structure, matched by the given selector, around the
// content of element in the set of matched elements. The matched child is
// cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInner(selector string) *Selection {
return s.WrapInnerMatcher(compileMatcher(selector))
}
// WrapInnerMatcher wraps an HTML structure, matched by the given selector,
// around the content of element in the set of matched elements. The matched
// child is cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInnerMatcher(m Matcher) *Selection {
return s.wrapInnerNodes(m.MatchAll(s.document.rootNode)...)
}
// WrapInnerSelection wraps an HTML structure, matched by the given selector,
// around the content of element in the set of matched elements. The matched
// child is cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInnerSelection(sel *Selection) *Selection {
return s.wrapInnerNodes(sel.Nodes...)
}
// WrapInnerHtml wraps an HTML structure, matched by the given selector, around
// the content of element in the set of matched elements. The matched child is
// cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInnerHtml(html string) *Selection {
return s.wrapInnerNodes(parseHtml(html)...)
}
// WrapInnerNode wraps an HTML structure, matched by the given selector, around
// the content of element in the set of matched elements. The matched child is
// cloned before being inserted into the document.
//
// It returns the original set of elements.
func (s *Selection) WrapInnerNode(n *html.Node) *Selection {
return s.wrapInnerNodes(n)
}
func (s *Selection) wrapInnerNodes(ns ...*html.Node) *Selection {
if len(ns) == 0 {
return s
}
s.Each(func(i int, s *Selection) {
contents := s.Contents()
if contents.Size() > 0 {
contents.wrapAllNodes(ns...)
} else {
s.AppendNodes(cloneNode(ns[0]))
}
})
return s
}
func parseHtml(h string) []*html.Node {
// Errors are only returned when the io.Reader returns any error besides
// EOF, but strings.Reader never will
nodes, err := html.ParseFragment(strings.NewReader(h), &html.Node{Type: html.ElementNode})
if err != nil {
panic("goquery: failed to parse HTML: " + err.Error())
}
return nodes
}
func setHtmlNodes(s *Selection, ns ...*html.Node) *Selection {
for _, n := range s.Nodes {
for c := n.FirstChild; c != nil; c = n.FirstChild {
n.RemoveChild(c)
}
for _, c := range ns {
n.AppendChild(cloneNode(c))
}
}
return s
}
// Get the first child that is an ElementNode
func getFirstChildEl(n *html.Node) *html.Node {
c := n.FirstChild
for c != nil && c.Type != html.ElementNode {
c = c.NextSibling
}
return c
}
// Deep copy a slice of nodes.
func cloneNodes(ns []*html.Node) []*html.Node {
cns := make([]*html.Node, 0, len(ns))
for _, n := range ns {
cns = append(cns, cloneNode(n))
}
return cns
}
// Deep copy a node. The new node has clones of all the original node's
// children but none of its parents or siblings.
func cloneNode(n *html.Node) *html.Node {
nn := &html.Node{
Type: n.Type,
DataAtom: n.DataAtom,
Data: n.Data,
Attr: make([]html.Attribute, len(n.Attr)),
}
copy(nn.Attr, n.Attr)
for c := n.FirstChild; c != nil; c = c.NextSibling {
nn.AppendChild(cloneNode(c))
}
return nn
}
func (s *Selection) manipulateNodes(ns []*html.Node, reverse bool,
f func(sn *html.Node, n *html.Node)) *Selection {
lasti := s.Size() - 1
// net.Html doesn't provide document fragments for insertion, so to get
// things in the correct order with After() and Prepend(), the callback
// needs to be called on the reverse of the nodes.
if reverse {
for i, j := 0, len(ns)-1; i < j; i, j = i+1, j-1 {
ns[i], ns[j] = ns[j], ns[i]
}
}
for i, sn := range s.Nodes {
for _, n := range ns {
if i != lasti {
f(sn, cloneNode(n))
} else {
if n.Parent != nil {
n.Parent.RemoveChild(n)
}
f(sn, n)
}
}
}
return s
}

View file

@ -0,0 +1,513 @@
package goquery
import (
"testing"
)
const (
wrapHtml = "<div id=\"ins\">test string<div><p><em><b></b></em></p></div></div>"
)
func TestAfter(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").After("#nf6")
assertLength(t, doc.Find("#main #nf6").Nodes, 0)
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
assertLength(t, doc.Find("#main + #nf6").Nodes, 1)
printSel(t, doc.Selection)
}
func TestAfterMany(t *testing.T) {
doc := Doc2Clone()
doc.Find(".one").After("#nf6")
assertLength(t, doc.Find("#foot #nf6").Nodes, 1)
assertLength(t, doc.Find("#main #nf6").Nodes, 1)
assertLength(t, doc.Find(".one + #nf6").Nodes, 2)
printSel(t, doc.Selection)
}
func TestAfterWithRemoved(t *testing.T) {
doc := Doc2Clone()
s := doc.Find("#main").Remove()
s.After("#nf6")
assertLength(t, s.Find("#nf6").Nodes, 0)
assertLength(t, doc.Find("#nf6").Nodes, 0)
printSel(t, doc.Selection)
}
func TestAfterSelection(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").AfterSelection(doc.Find("#nf1, #nf2"))
assertLength(t, doc.Find("#main #nf1, #main #nf2").Nodes, 0)
assertLength(t, doc.Find("#foot #nf1, #foot #nf2").Nodes, 0)
assertLength(t, doc.Find("#main + #nf1, #nf1 + #nf2").Nodes, 2)
printSel(t, doc.Selection)
}
func TestAfterHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").AfterHtml("<strong>new node</strong>")
assertLength(t, doc.Find("#main + strong").Nodes, 1)
printSel(t, doc.Selection)
}
func TestAppend(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").Append("#nf6")
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
assertLength(t, doc.Find("#main #nf6").Nodes, 1)
printSel(t, doc.Selection)
}
func TestAppendBody(t *testing.T) {
doc := Doc2Clone()
doc.Find("body").Append("#nf6")
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
assertLength(t, doc.Find("#main #nf6").Nodes, 0)
assertLength(t, doc.Find("body > #nf6").Nodes, 1)
printSel(t, doc.Selection)
}
func TestAppendSelection(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").AppendSelection(doc.Find("#nf1, #nf2"))
assertLength(t, doc.Find("#foot #nf1").Nodes, 0)
assertLength(t, doc.Find("#foot #nf2").Nodes, 0)
assertLength(t, doc.Find("#main #nf1").Nodes, 1)
assertLength(t, doc.Find("#main #nf2").Nodes, 1)
printSel(t, doc.Selection)
}
func TestAppendSelectionExisting(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").AppendSelection(doc.Find("#n1, #n2"))
assertClass(t, doc.Find("#main :nth-child(1)"), "three")
assertClass(t, doc.Find("#main :nth-child(5)"), "one")
assertClass(t, doc.Find("#main :nth-child(6)"), "two")
printSel(t, doc.Selection)
}
func TestAppendClone(t *testing.T) {
doc := Doc2Clone()
doc.Find("#n1").AppendSelection(doc.Find("#nf1").Clone())
assertLength(t, doc.Find("#foot #nf1").Nodes, 1)
assertLength(t, doc.Find("#main #nf1").Nodes, 1)
printSel(t, doc.Selection)
}
func TestAppendHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("div").AppendHtml("<strong>new node</strong>")
assertLength(t, doc.Find("strong").Nodes, 14)
printSel(t, doc.Selection)
}
func TestBefore(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").Before("#nf6")
assertLength(t, doc.Find("#main #nf6").Nodes, 0)
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
assertLength(t, doc.Find("body > #nf6:first-child").Nodes, 1)
printSel(t, doc.Selection)
}
func TestBeforeWithRemoved(t *testing.T) {
doc := Doc2Clone()
s := doc.Find("#main").Remove()
s.Before("#nf6")
assertLength(t, s.Find("#nf6").Nodes, 0)
assertLength(t, doc.Find("#nf6").Nodes, 0)
printSel(t, doc.Selection)
}
func TestBeforeSelection(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").BeforeSelection(doc.Find("#nf1, #nf2"))
assertLength(t, doc.Find("#main #nf1, #main #nf2").Nodes, 0)
assertLength(t, doc.Find("#foot #nf1, #foot #nf2").Nodes, 0)
assertLength(t, doc.Find("body > #nf1:first-child, #nf1 + #nf2").Nodes, 2)
printSel(t, doc.Selection)
}
func TestBeforeHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").BeforeHtml("<strong>new node</strong>")
assertLength(t, doc.Find("body > strong:first-child").Nodes, 1)
printSel(t, doc.Selection)
}
func TestEmpty(t *testing.T) {
doc := Doc2Clone()
s := doc.Find("#main").Empty()
assertLength(t, doc.Find("#main").Children().Nodes, 0)
assertLength(t, s.Filter("div").Nodes, 6)
printSel(t, doc.Selection)
}
func TestPrepend(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").Prepend("#nf6")
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
assertLength(t, doc.Find("#main #nf6:first-child").Nodes, 1)
printSel(t, doc.Selection)
}
func TestPrependBody(t *testing.T) {
doc := Doc2Clone()
doc.Find("body").Prepend("#nf6")
assertLength(t, doc.Find("#foot #nf6").Nodes, 0)
assertLength(t, doc.Find("#main #nf6").Nodes, 0)
assertLength(t, doc.Find("body > #nf6:first-child").Nodes, 1)
printSel(t, doc.Selection)
}
func TestPrependSelection(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").PrependSelection(doc.Find("#nf1, #nf2"))
assertLength(t, doc.Find("#foot #nf1").Nodes, 0)
assertLength(t, doc.Find("#foot #nf2").Nodes, 0)
assertLength(t, doc.Find("#main #nf1:first-child").Nodes, 1)
assertLength(t, doc.Find("#main #nf2:nth-child(2)").Nodes, 1)
printSel(t, doc.Selection)
}
func TestPrependSelectionExisting(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").PrependSelection(doc.Find("#n5, #n6"))
assertClass(t, doc.Find("#main :nth-child(1)"), "five")
assertClass(t, doc.Find("#main :nth-child(2)"), "six")
assertClass(t, doc.Find("#main :nth-child(5)"), "three")
assertClass(t, doc.Find("#main :nth-child(6)"), "four")
printSel(t, doc.Selection)
}
func TestPrependClone(t *testing.T) {
doc := Doc2Clone()
doc.Find("#n1").PrependSelection(doc.Find("#nf1").Clone())
assertLength(t, doc.Find("#foot #nf1:first-child").Nodes, 1)
assertLength(t, doc.Find("#main #nf1:first-child").Nodes, 1)
printSel(t, doc.Selection)
}
func TestPrependHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("div").PrependHtml("<strong>new node</strong>")
assertLength(t, doc.Find("strong:first-child").Nodes, 14)
printSel(t, doc.Selection)
}
func TestRemove(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").Remove()
assertLength(t, doc.Find("#foot #nf1").Nodes, 0)
printSel(t, doc.Selection)
}
func TestRemoveAll(t *testing.T) {
doc := Doc2Clone()
doc.Find("*").Remove()
assertLength(t, doc.Find("*").Nodes, 0)
printSel(t, doc.Selection)
}
func TestRemoveRoot(t *testing.T) {
doc := Doc2Clone()
doc.Find("html").Remove()
assertLength(t, doc.Find("html").Nodes, 0)
printSel(t, doc.Selection)
}
func TestRemoveFiltered(t *testing.T) {
doc := Doc2Clone()
nf6 := doc.Find("#nf6")
s := doc.Find("div").RemoveFiltered("#nf6")
assertLength(t, doc.Find("#nf6").Nodes, 0)
assertLength(t, s.Nodes, 1)
if nf6.Nodes[0] != s.Nodes[0] {
t.Error("Removed node does not match original")
}
printSel(t, doc.Selection)
}
func TestReplaceWith(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf6").ReplaceWith("#main")
assertLength(t, doc.Find("#foot #main:last-child").Nodes, 1)
printSel(t, doc.Selection)
doc.Find("#foot").ReplaceWith("#main")
assertLength(t, doc.Find("#foot").Nodes, 0)
assertLength(t, doc.Find("#main").Nodes, 1)
printSel(t, doc.Selection)
}
func TestReplaceWithHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main, #foot").ReplaceWithHtml("<div id=\"replace\"></div>")
assertLength(t, doc.Find("#replace").Nodes, 2)
printSel(t, doc.Selection)
}
func 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 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"))
assertSelectionIs(t, sel, "#nf6")
assertLength(t, doc.Find("#nf6").Nodes, 0)
assertLength(t, doc.Find("#nf5").Nodes, 1)
printSel(t, doc.Selection)
}
func TestUnwrap(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf5").Unwrap()
assertLength(t, doc.Find("#foot").Nodes, 0)
assertLength(t, doc.Find("body > #nf1").Nodes, 1)
assertLength(t, doc.Find("body > #nf5").Nodes, 1)
printSel(t, doc.Selection)
doc = Doc2Clone()
doc.Find("#nf5, #n1").Unwrap()
assertLength(t, doc.Find("#foot").Nodes, 0)
assertLength(t, doc.Find("#main").Nodes, 0)
assertLength(t, doc.Find("body > #n1").Nodes, 1)
assertLength(t, doc.Find("body > #nf5").Nodes, 1)
printSel(t, doc.Selection)
}
func TestUnwrapBody(t *testing.T) {
doc := Doc2Clone()
doc.Find("#main").Unwrap()
assertLength(t, doc.Find("body").Nodes, 1)
assertLength(t, doc.Find("body > #main").Nodes, 1)
printSel(t, doc.Selection)
}
func TestUnwrapHead(t *testing.T) {
doc := Doc2Clone()
doc.Find("title").Unwrap()
assertLength(t, doc.Find("head").Nodes, 0)
assertLength(t, doc.Find("head > title").Nodes, 0)
assertLength(t, doc.Find("title").Nodes, 1)
printSel(t, doc.Selection)
}
func TestUnwrapHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("head").Unwrap()
assertLength(t, doc.Find("html").Nodes, 0)
assertLength(t, doc.Find("html head").Nodes, 0)
assertLength(t, doc.Find("head").Nodes, 1)
printSel(t, doc.Selection)
}
func TestWrap(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").Wrap("#nf2")
nf1 := doc.Find("#foot #nf2 #nf1")
assertLength(t, nf1.Nodes, 1)
nf2 := doc.Find("#nf2")
assertLength(t, nf2.Nodes, 2)
printSel(t, doc.Selection)
}
func TestWrapEmpty(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").Wrap("#doesnt-exist")
origHtml, _ := Doc2().Html()
newHtml, _ := doc.Html()
if origHtml != newHtml {
t.Error("Expected the two documents to be identical.")
}
printSel(t, doc.Selection)
}
func TestWrapHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find(".odd").WrapHtml(wrapHtml)
nf2 := doc.Find("#ins #nf2")
assertLength(t, nf2.Nodes, 1)
printSel(t, doc.Selection)
}
func TestWrapSelection(t *testing.T) {
doc := Doc2Clone()
doc.Find("#nf1").WrapSelection(doc.Find("#nf2"))
nf1 := doc.Find("#foot #nf2 #nf1")
assertLength(t, nf1.Nodes, 1)
nf2 := doc.Find("#nf2")
assertLength(t, nf2.Nodes, 2)
printSel(t, doc.Selection)
}
func TestWrapAll(t *testing.T) {
doc := Doc2Clone()
doc.Find(".odd").WrapAll("#nf1")
nf1 := doc.Find("#main #nf1")
assertLength(t, nf1.Nodes, 1)
sel := nf1.Find("#n2 ~ #n4 ~ #n6 ~ #nf2 ~ #nf4 ~ #nf6")
assertLength(t, sel.Nodes, 1)
printSel(t, doc.Selection)
}
func TestWrapAllHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find(".odd").WrapAllHtml(wrapHtml)
nf1 := doc.Find("#main div#ins div p em b #n2 ~ #n4 ~ #n6 ~ #nf2 ~ #nf4 ~ #nf6")
assertLength(t, nf1.Nodes, 1)
printSel(t, doc.Selection)
}
func TestWrapInnerNoContent(t *testing.T) {
doc := Doc2Clone()
doc.Find(".one").WrapInner(".two")
twos := doc.Find(".two")
assertLength(t, twos.Nodes, 4)
assertLength(t, doc.Find(".one .two").Nodes, 2)
printSel(t, doc.Selection)
}
func TestWrapInnerWithContent(t *testing.T) {
doc := Doc3Clone()
doc.Find(".one").WrapInner(".two")
twos := doc.Find(".two")
assertLength(t, twos.Nodes, 4)
assertLength(t, doc.Find(".one .two").Nodes, 2)
printSel(t, doc.Selection)
}
func TestWrapInnerNoWrapper(t *testing.T) {
doc := Doc2Clone()
doc.Find(".one").WrapInner(".not-exist")
twos := doc.Find(".two")
assertLength(t, twos.Nodes, 2)
assertLength(t, doc.Find(".one").Nodes, 2)
assertLength(t, doc.Find(".one .two").Nodes, 0)
printSel(t, doc.Selection)
}
func TestWrapInnerHtml(t *testing.T) {
doc := Doc2Clone()
doc.Find("#foot").WrapInnerHtml(wrapHtml)
foot := doc.Find("#foot div#ins div p em b #nf1 ~ #nf2 ~ #nf3")
assertLength(t, foot.Nodes, 1)
printSel(t, doc.Selection)
}

275
vendor/github.com/PuerkitoBio/goquery/property.go generated vendored Normal file
View file

@ -0,0 +1,275 @@
package goquery
import (
"bytes"
"regexp"
"strings"
"golang.org/x/net/html"
)
var rxClassTrim = regexp.MustCompile("[\t\r\n]")
// Attr gets the specified attribute's value for the first element in the
// Selection. To get the value for each element individually, use a looping
// construct such as Each or Map method.
func (s *Selection) Attr(attrName string) (val string, exists bool) {
if len(s.Nodes) == 0 {
return
}
return getAttributeValue(attrName, s.Nodes[0])
}
// AttrOr works like Attr but returns default value if attribute is not present.
func (s *Selection) AttrOr(attrName, defaultValue string) string {
if len(s.Nodes) == 0 {
return defaultValue
}
val, exists := getAttributeValue(attrName, s.Nodes[0])
if !exists {
return defaultValue
}
return val
}
// RemoveAttr removes the named attribute from each element in the set of matched elements.
func (s *Selection) RemoveAttr(attrName string) *Selection {
for _, n := range s.Nodes {
removeAttr(n, attrName)
}
return s
}
// SetAttr sets the given attribute on each element in the set of matched elements.
func (s *Selection) SetAttr(attrName, val string) *Selection {
for _, n := range s.Nodes {
attr := getAttributePtr(attrName, n)
if attr == nil {
n.Attr = append(n.Attr, html.Attribute{Key: attrName, Val: val})
} else {
attr.Val = val
}
}
return s
}
// Text gets the combined text contents of each element in the set of matched
// elements, including their descendants.
func (s *Selection) Text() string {
var buf bytes.Buffer
// Slightly optimized vs calling Each: no single selection object created
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()
}
// Size is an alias for Length.
func (s *Selection) Size() int {
return s.Length()
}
// Length returns the number of elements in the Selection object.
func (s *Selection) Length() int {
return len(s.Nodes)
}
// Html gets the HTML contents of the first element in the set of matched
// elements. It includes text and comment nodes.
func (s *Selection) Html() (ret string, e error) {
// Since there is no .innerHtml, the HTML content must be re-created from
// the nodes using html.Render.
var buf bytes.Buffer
if len(s.Nodes) > 0 {
for c := s.Nodes[0].FirstChild; c != nil; c = c.NextSibling {
e = html.Render(&buf, c)
if e != nil {
return
}
}
ret = buf.String()
}
return
}
// AddClass adds the given class(es) to each element in the set of matched elements.
// Multiple class names can be specified, separated by a space or via multiple arguments.
func (s *Selection) AddClass(class ...string) *Selection {
classStr := strings.TrimSpace(strings.Join(class, " "))
if classStr == "" {
return s
}
tcls := getClassesSlice(classStr)
for _, n := range s.Nodes {
curClasses, attr := getClassesAndAttr(n, true)
for _, newClass := range tcls {
if !strings.Contains(curClasses, " "+newClass+" ") {
curClasses += newClass + " "
}
}
setClasses(n, attr, curClasses)
}
return s
}
// HasClass determines whether any of the matched elements are assigned the
// given class.
func (s *Selection) HasClass(class string) bool {
class = " " + class + " "
for _, n := range s.Nodes {
classes, _ := getClassesAndAttr(n, false)
if strings.Contains(classes, class) {
return true
}
}
return false
}
// RemoveClass removes the given class(es) from each element in the set of matched elements.
// Multiple class names can be specified, separated by a space or via multiple arguments.
// If no class name is provided, all classes are removed.
func (s *Selection) RemoveClass(class ...string) *Selection {
var rclasses []string
classStr := strings.TrimSpace(strings.Join(class, " "))
remove := classStr == ""
if !remove {
rclasses = getClassesSlice(classStr)
}
for _, n := range s.Nodes {
if remove {
removeAttr(n, "class")
} else {
classes, attr := getClassesAndAttr(n, true)
for _, rcl := range rclasses {
classes = strings.Replace(classes, " "+rcl+" ", " ", -1)
}
setClasses(n, attr, classes)
}
}
return s
}
// ToggleClass adds or removes the given class(es) for each element in the set of matched elements.
// Multiple class names can be specified, separated by a space or via multiple arguments.
func (s *Selection) ToggleClass(class ...string) *Selection {
classStr := strings.TrimSpace(strings.Join(class, " "))
if classStr == "" {
return s
}
tcls := getClassesSlice(classStr)
for _, n := range s.Nodes {
classes, attr := getClassesAndAttr(n, true)
for _, tcl := range tcls {
if strings.Contains(classes, " "+tcl+" ") {
classes = strings.Replace(classes, " "+tcl+" ", " ", -1)
} else {
classes += tcl + " "
}
}
setClasses(n, attr, classes)
}
return s
}
func getAttributePtr(attrName string, n *html.Node) *html.Attribute {
if n == nil {
return nil
}
for i, a := range n.Attr {
if a.Key == attrName {
return &n.Attr[i]
}
}
return nil
}
// Private function to get the specified attribute's value from a node.
func getAttributeValue(attrName string, n *html.Node) (val string, exists bool) {
if a := getAttributePtr(attrName, n); a != nil {
val = a.Val
exists = true
}
return
}
// Get and normalize the "class" attribute from the node.
func getClassesAndAttr(n *html.Node, create bool) (classes string, attr *html.Attribute) {
// Applies only to element nodes
if n.Type == html.ElementNode {
attr = getAttributePtr("class", n)
if attr == nil && create {
n.Attr = append(n.Attr, html.Attribute{
Key: "class",
Val: "",
})
attr = &n.Attr[len(n.Attr)-1]
}
}
if attr == nil {
classes = " "
} else {
classes = rxClassTrim.ReplaceAllString(" "+attr.Val+" ", " ")
}
return
}
func getClassesSlice(classes string) []string {
return strings.Split(rxClassTrim.ReplaceAllString(" "+classes+" ", " "), " ")
}
func removeAttr(n *html.Node, attrName string) {
for i, a := range n.Attr {
if a.Key == attrName {
n.Attr[i], n.Attr[len(n.Attr)-1], n.Attr =
n.Attr[len(n.Attr)-1], html.Attribute{}, n.Attr[:len(n.Attr)-1]
return
}
}
}
func setClasses(n *html.Node, attr *html.Attribute, classes string) {
classes = strings.TrimSpace(classes)
if classes == "" {
removeAttr(n, "class")
return
}
attr.Val = classes
}

252
vendor/github.com/PuerkitoBio/goquery/property_test.go generated vendored Normal file
View file

@ -0,0 +1,252 @@
package goquery
import (
"regexp"
"strings"
"testing"
)
func TestAttrExists(t *testing.T) {
if val, ok := Doc().Find("a").Attr("href"); !ok {
t.Error("Expected a value for the href attribute.")
} else {
t.Logf("Href of first anchor: %v.", val)
}
}
func TestAttrOr(t *testing.T) {
if val := Doc().Find("a").AttrOr("fake-attribute", "alternative"); val != "alternative" {
t.Error("Expected an alternative value for 'fake-attribute' attribute.")
} else {
t.Logf("Value returned for not existing attribute: %v.", val)
}
if val := Doc().Find("zz").AttrOr("fake-attribute", "alternative"); val != "alternative" {
t.Error("Expected an alternative value for 'fake-attribute' on an empty selection.")
} else {
t.Logf("Value returned for empty selection: %v.", val)
}
}
func TestAttrNotExist(t *testing.T) {
if val, ok := Doc().Find("div.row-fluid").Attr("href"); ok {
t.Errorf("Expected no value for the href attribute, got %v.", val)
}
}
func TestRemoveAttr(t *testing.T) {
sel := Doc2Clone().Find("div")
sel.RemoveAttr("id")
_, ok := sel.Attr("id")
if ok {
t.Error("Expected there to be no id attributes set")
}
}
func TestSetAttr(t *testing.T) {
sel := Doc2Clone().Find("#main")
sel.SetAttr("id", "not-main")
val, ok := sel.Attr("id")
if !ok {
t.Error("Expected an id attribute on main")
}
if val != "not-main" {
t.Errorf("Expected an attribute id to be not-main, got %s", val)
}
}
func TestSetAttr2(t *testing.T) {
sel := Doc2Clone().Find("#main")
sel.SetAttr("foo", "bar")
val, ok := sel.Attr("foo")
if !ok {
t.Error("Expected an 'foo' attribute on main")
}
if val != "bar" {
t.Errorf("Expected an attribute 'foo' to be 'bar', got '%s'", val)
}
}
func TestText(t *testing.T) {
txt := Doc().Find("h1").Text()
if strings.Trim(txt, " \n\r\t") != "Provok.in" {
t.Errorf("Expected text to be Provok.in, found %s.", txt)
}
}
func TestText2(t *testing.T) {
txt := Doc().Find(".hero-unit .container-fluid .row-fluid:nth-child(1)").Text()
if ok, e := regexp.MatchString(`^\s+Provok\.in\s+Prove your point.\s+$`, txt); !ok || e != nil {
t.Errorf("Expected text to be Provok.in Prove your point., found %s.", txt)
if e != nil {
t.Logf("Error: %s.", e.Error())
}
}
}
func TestText3(t *testing.T) {
txt := Doc().Find(".pvk-gutter").First().Text()
// There's an &nbsp; character in there...
if ok, e := regexp.MatchString(`^[\s\x{00A0}]+$`, txt); !ok || e != nil {
t.Errorf("Expected spaces, found <%v>.", txt)
if e != nil {
t.Logf("Error: %s.", e.Error())
}
}
}
func TestHtml(t *testing.T) {
txt, e := Doc().Find("h1").Html()
if e != nil {
t.Errorf("Error: %s.", e)
}
if ok, e := regexp.MatchString(`^\s*<a href="/">Provok<span class="green">\.</span><span class="red">i</span>n</a>\s*$`, txt); !ok || e != nil {
t.Errorf("Unexpected HTML content, found %s.", txt)
if e != nil {
t.Logf("Error: %s.", e.Error())
}
}
}
func TestNbsp(t *testing.T) {
src := `<p>Some&nbsp;text</p>`
d, err := NewDocumentFromReader(strings.NewReader(src))
if err != nil {
t.Fatal(err)
}
txt := d.Find("p").Text()
ix := strings.Index(txt, "\u00a0")
if ix != 4 {
t.Errorf("Text: expected a non-breaking space at index 4, got %d", ix)
}
h, err := d.Find("p").Html()
if err != nil {
t.Fatal(err)
}
ix = strings.Index(h, "\u00a0")
if ix != 4 {
t.Errorf("Html: expected a non-breaking space at index 4, got %d", ix)
}
}
func TestAddClass(t *testing.T) {
sel := Doc2Clone().Find("#main")
sel.AddClass("main main main")
// Make sure that class was only added once
if a, ok := sel.Attr("class"); !ok || a != "main" {
t.Error("Expected #main to have class main")
}
}
func TestAddClassSimilar(t *testing.T) {
sel := Doc2Clone().Find("#nf5")
sel.AddClass("odd")
assertClass(t, sel, "odd")
assertClass(t, sel, "odder")
printSel(t, sel.Parent())
}
func TestAddEmptyClass(t *testing.T) {
sel := Doc2Clone().Find("#main")
sel.AddClass("")
// Make sure that class was only added once
if a, ok := sel.Attr("class"); ok {
t.Errorf("Expected #main to not to have a class, have: %s", a)
}
}
func TestAddClasses(t *testing.T) {
sel := Doc2Clone().Find("#main")
sel.AddClass("a b")
// Make sure that class was only added once
if !sel.HasClass("a") || !sel.HasClass("b") {
t.Errorf("#main does not have classes")
}
}
func TestHasClass(t *testing.T) {
sel := Doc().Find("div")
if !sel.HasClass("span12") {
t.Error("Expected at least one div to have class span12.")
}
}
func TestHasClassNone(t *testing.T) {
sel := Doc().Find("h2")
if sel.HasClass("toto") {
t.Error("Expected h1 to have no class.")
}
}
func TestHasClassNotFirst(t *testing.T) {
sel := Doc().Find(".alert")
if !sel.HasClass("alert-error") {
t.Error("Expected .alert to also have class .alert-error.")
}
}
func TestRemoveClass(t *testing.T) {
sel := Doc2Clone().Find("#nf1")
sel.RemoveClass("one row")
if !sel.HasClass("even") || sel.HasClass("one") || sel.HasClass("row") {
classes, _ := sel.Attr("class")
t.Error("Expected #nf1 to have class even, has ", classes)
}
}
func TestRemoveClassSimilar(t *testing.T) {
sel := Doc2Clone().Find("#nf5, #nf6")
assertLength(t, sel.Nodes, 2)
sel.RemoveClass("odd")
assertClass(t, sel.Eq(0), "odder")
printSel(t, sel)
}
func TestRemoveAllClasses(t *testing.T) {
sel := Doc2Clone().Find("#nf1")
sel.RemoveClass()
if a, ok := sel.Attr("class"); ok {
t.Error("All classes were not removed, has ", a)
}
sel = Doc2Clone().Find("#main")
sel.RemoveClass()
if a, ok := sel.Attr("class"); ok {
t.Error("All classes were not removed, has ", a)
}
}
func TestToggleClass(t *testing.T) {
sel := Doc2Clone().Find("#nf1")
sel.ToggleClass("one")
if sel.HasClass("one") {
t.Error("Expected #nf1 to not have class one")
}
sel.ToggleClass("one")
if !sel.HasClass("one") {
t.Error("Expected #nf1 to have class one")
}
sel.ToggleClass("one even row")
if a, ok := sel.Attr("class"); ok {
t.Errorf("Expected #nf1 to have no classes, have %q", a)
}
}

49
vendor/github.com/PuerkitoBio/goquery/query.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
package goquery
import "golang.org/x/net/html"
// Is checks the current matched set of elements against a selector and
// returns true if at least one of these elements matches.
func (s *Selection) Is(selector string) bool {
return s.IsMatcher(compileMatcher(selector))
}
// IsMatcher checks the current matched set of elements against a matcher and
// returns true if at least one of these elements matches.
func (s *Selection) IsMatcher(m Matcher) bool {
if len(s.Nodes) > 0 {
if len(s.Nodes) == 1 {
return m.Match(s.Nodes[0])
}
return len(m.Filter(s.Nodes)) > 0
}
return false
}
// IsFunction checks the current matched set of elements against a predicate and
// returns true if at least one of these elements matches.
func (s *Selection) IsFunction(f func(int, *Selection) bool) bool {
return s.FilterFunction(f).Length() > 0
}
// IsSelection checks the current matched set of elements against a Selection object
// and returns true if at least one of these elements matches.
func (s *Selection) IsSelection(sel *Selection) bool {
return s.FilterSelection(sel).Length() > 0
}
// IsNodes checks the current matched set of elements against the specified nodes
// and returns true if at least one of these elements matches.
func (s *Selection) IsNodes(nodes ...*html.Node) bool {
return s.FilterNodes(nodes...).Length() > 0
}
// Contains returns true if the specified Node is within,
// at any depth, one of the nodes in the Selection object.
// It is NOT inclusive, to behave like jQuery's implementation, and
// unlike Javascript's .contains, so if the contained
// node is itself in the selection, it returns false.
func (s *Selection) Contains(n *html.Node) bool {
return sliceContains(s.Nodes, n)
}

103
vendor/github.com/PuerkitoBio/goquery/query_test.go generated vendored Normal file
View file

@ -0,0 +1,103 @@
package goquery
import (
"testing"
)
func TestIs(t *testing.T) {
sel := Doc().Find(".footer p:nth-child(1)")
if !sel.Is("p") {
t.Error("Expected .footer p:nth-child(1) to be p.")
}
}
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)") {
t.Error("Expected .footer p:nth-child(2) to be p:nth-child(2).")
}
}
func TestIsPositionalNot(t *testing.T) {
sel := Doc().Find(".footer p:nth-child(1)")
if sel.Is("p:nth-child(2)") {
t.Error("Expected .footer p:nth-child(1) NOT to be p:nth-child(2).")
}
}
func TestIsFunction(t *testing.T) {
ok := Doc().Find("div").IsFunction(func(i int, s *Selection) bool {
return s.HasClass("container-fluid")
})
if !ok {
t.Error("Expected some div to have a container-fluid class.")
}
}
func TestIsFunctionRollback(t *testing.T) {
ok := Doc().Find("div").IsFunction(func(i int, s *Selection) bool {
return s.HasClass("container-fluid")
})
if !ok {
t.Error("Expected some div to have a container-fluid class.")
}
}
func TestIsSelection(t *testing.T) {
sel := Doc().Find("div")
sel2 := Doc().Find(".pvk-gutter")
if !sel.IsSelection(sel2) {
t.Error("Expected some div to have a pvk-gutter class.")
}
}
func TestIsSelectionNot(t *testing.T) {
sel := Doc().Find("div")
sel2 := Doc().Find("a")
if sel.IsSelection(sel2) {
t.Error("Expected some div NOT to be an anchor.")
}
}
func TestIsNodes(t *testing.T) {
sel := Doc().Find("div")
sel2 := Doc().Find(".footer")
if !sel.IsNodes(sel2.Nodes[0]) {
t.Error("Expected some div to have a footer class.")
}
}
func TestDocContains(t *testing.T) {
sel := Doc().Find("h1")
if !Doc().Contains(sel.Nodes[0]) {
t.Error("Expected document to contain H1 tag.")
}
}
func TestSelContains(t *testing.T) {
sel := Doc().Find(".row-fluid")
sel2 := Doc().Find("a[ng-click]")
if !sel.Contains(sel2.Nodes[0]) {
t.Error("Expected .row-fluid to contain a[ng-click] tag.")
}
}
func TestSelNotContains(t *testing.T) {
sel := Doc().Find("a.link")
sel2 := Doc().Find("span")
if sel.Contains(sel2.Nodes[0]) {
t.Error("Expected a.link to NOT contain span tag.")
}
}

View file

@ -0,0 +1,855 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>testing - The Go Programming Language</title>
<link type="text/css" rel="stylesheet" href="/doc/style.css">
<script type="text/javascript" src="/doc/godocs.js"></script>
<link rel="search" type="application/opensearchdescription+xml" title="godoc" href="/opensearch.xml" />
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(["_setAccount", "UA-11222381-2"]);
_gaq.push(["_trackPageview"]);
</script>
</head>
<body>
<div id="topbar"><div class="container wide">
<form method="GET" action="/search">
<div id="menu">
<a href="/doc/">Documents</a>
<a href="/ref/">References</a>
<a href="/pkg/">Packages</a>
<a href="/project/">The Project</a>
<a href="/help/">Help</a>
<input type="text" id="search" name="q" class="inactive" value="Search">
</div>
<div id="heading"><a href="/">The Go Programming Language</a></div>
</form>
</div></div>
<div id="page" class="wide">
<div id="plusone"><g:plusone size="small" annotation="none"></g:plusone></div>
<h1>Package testing</h1>
<div id="nav"></div>
<!--
Copyright 2009 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.
-->
<div id="short-nav">
<dl>
<dd><code>import "testing"</code></dd>
</dl>
<dl>
<dd><a href="#overview" class="overviewLink">Overview</a></dd>
<dd><a href="#index">Index</a></dd>
<dd><a href="#subdirectories">Subdirectories</a></dd>
</dl>
</div>
<!-- The package's Name is printed as title by the top-level template -->
<div id="overview" class="toggleVisible">
<div class="collapsed">
<h2 class="toggleButton" title="Click to show Overview section">Overview ▹</h2>
</div>
<div class="expanded">
<h2 class="toggleButton" title="Click to hide Overview section">Overview ▾</h2>
<p>
Package testing provides support for automated testing of Go packages.
It is intended to be used in concert with the &ldquo;go test&rdquo; command, which automates
execution of any function of the form
</p>
<pre>func TestXxx(*testing.T)
</pre>
<p>
where Xxx can be any alphanumeric string (but the first letter must not be in
[a-z]) and serves to identify the test routine.
These TestXxx routines should be declared within the package they are testing.
</p>
<p>
Functions of the form
</p>
<pre>func BenchmarkXxx(*testing.B)
</pre>
<p>
are considered benchmarks, and are executed by the &#34;go test&#34; command when
the -test.bench flag is provided.
</p>
<p>
A sample benchmark function looks like this:
</p>
<pre>func BenchmarkHello(b *testing.B) {
for i := 0; i &lt; b.N; i++ {
fmt.Sprintf(&#34;hello&#34;)
}
}
</pre>
<p>
The benchmark package will vary b.N until the benchmark function lasts
long enough to be timed reliably. The output
</p>
<pre>testing.BenchmarkHello 10000000 282 ns/op
</pre>
<p>
means that the loop ran 10000000 times at a speed of 282 ns per loop.
</p>
<p>
If a benchmark needs some expensive setup before running, the timer
may be stopped:
</p>
<pre>func BenchmarkBigLen(b *testing.B) {
b.StopTimer()
big := NewBig()
b.StartTimer()
for i := 0; i &lt; b.N; i++ {
big.Len()
}
}
</pre>
<p>
The package also runs and verifies example code. Example functions may
include a concluding comment that begins with &#34;Output:&#34; and is compared with
the standard output of the function when the tests are run, as in these
examples of an example:
</p>
<pre>func ExampleHello() {
fmt.Println(&#34;hello&#34;)
// Output: hello
}
func ExampleSalutations() {
fmt.Println(&#34;hello, and&#34;)
fmt.Println(&#34;goodbye&#34;)
// Output:
// hello, and
// goodbye
}
</pre>
<p>
Example functions without output comments are compiled but not executed.
</p>
<p>
The naming convention to declare examples for a function F, a type T and
method M on type T are:
</p>
<pre>func ExampleF() { ... }
func ExampleT() { ... }
func ExampleT_M() { ... }
</pre>
<p>
Multiple example functions for a type/function/method may be provided by
appending a distinct suffix to the name. The suffix must start with a
lower-case letter.
</p>
<pre>func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }
</pre>
<p>
The entire test file is presented as the example when it contains a single
example function, at least one other function, type, variable, or constant
declaration, and no test or benchmark functions.
</p>
</div>
</div>
<h2 id="index">Index</h2>
<!-- Table of contents for API; must be named manual-nav to turn off auto nav. -->
<div id="manual-nav">
<dl>
<dd><a href="#Main">func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample)</a></dd>
<dd><a href="#RunBenchmarks">func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark)</a></dd>
<dd><a href="#RunExamples">func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool)</a></dd>
<dd><a href="#RunTests">func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool)</a></dd>
<dd><a href="#Short">func Short() bool</a></dd>
<dd><a href="#B">type B</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.Error">func (c *B) Error(args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.Errorf">func (c *B) Errorf(format string, args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.Fail">func (c *B) Fail()</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.FailNow">func (c *B) FailNow()</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.Failed">func (c *B) Failed() bool</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.Fatal">func (c *B) Fatal(args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.Fatalf">func (c *B) Fatalf(format string, args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.Log">func (c *B) Log(args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.Logf">func (c *B) Logf(format string, args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.ResetTimer">func (b *B) ResetTimer()</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.SetBytes">func (b *B) SetBytes(n int64)</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.StartTimer">func (b *B) StartTimer()</a></dd>
<dd>&nbsp; &nbsp; <a href="#B.StopTimer">func (b *B) StopTimer()</a></dd>
<dd><a href="#BenchmarkResult">type BenchmarkResult</a></dd>
<dd>&nbsp; &nbsp; <a href="#Benchmark">func Benchmark(f func(b *B)) BenchmarkResult</a></dd>
<dd>&nbsp; &nbsp; <a href="#BenchmarkResult.NsPerOp">func (r BenchmarkResult) NsPerOp() int64</a></dd>
<dd>&nbsp; &nbsp; <a href="#BenchmarkResult.String">func (r BenchmarkResult) String() string</a></dd>
<dd><a href="#InternalBenchmark">type InternalBenchmark</a></dd>
<dd><a href="#InternalExample">type InternalExample</a></dd>
<dd><a href="#InternalTest">type InternalTest</a></dd>
<dd><a href="#T">type T</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Error">func (c *T) Error(args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Errorf">func (c *T) Errorf(format string, args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Fail">func (c *T) Fail()</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.FailNow">func (c *T) FailNow()</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Failed">func (c *T) Failed() bool</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Fatal">func (c *T) Fatal(args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Fatalf">func (c *T) Fatalf(format string, args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Log">func (c *T) Log(args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Logf">func (c *T) Logf(format string, args ...interface{})</a></dd>
<dd>&nbsp; &nbsp; <a href="#T.Parallel">func (t *T) Parallel()</a></dd>
</dl>
<h4>Package files</h4>
<p>
<span style="font-size:90%">
<a href="/src/pkg/testing/benchmark.go">benchmark.go</a>
<a href="/src/pkg/testing/example.go">example.go</a>
<a href="/src/pkg/testing/testing.go">testing.go</a>
</span>
</p>
<h2 id="Main">func <a href="/src/pkg/testing/testing.go?s=9750:9890#L268">Main</a></h2>
<pre>func Main(matchString func(pat, str string) (bool, error), tests []InternalTest, benchmarks []InternalBenchmark, examples []InternalExample)</pre>
<p>
An internal function but exported because it is cross-package; part of the implementation
of the &#34;go test&#34; command.
</p>
<h2 id="RunBenchmarks">func <a href="/src/pkg/testing/benchmark.go?s=5365:5464#L207">RunBenchmarks</a></h2>
<pre>func RunBenchmarks(matchString func(pat, str string) (bool, error), benchmarks []InternalBenchmark)</pre>
<p>
An internal function but exported because it is cross-package; part of the implementation
of the &#34;go test&#34; command.
</p>
<h2 id="RunExamples">func <a href="/src/pkg/testing/example.go?s=314:417#L12">RunExamples</a></h2>
<pre>func RunExamples(matchString func(pat, str string) (bool, error), examples []InternalExample) (ok bool)</pre>
<h2 id="RunTests">func <a href="/src/pkg/testing/testing.go?s=10486:10580#L297">RunTests</a></h2>
<pre>func RunTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ok bool)</pre>
<h2 id="Short">func <a href="/src/pkg/testing/testing.go?s=4859:4876#L117">Short</a></h2>
<pre>func Short() bool</pre>
<p>
Short reports whether the -test.short flag is set.
</p>
<h2 id="B">type <a href="/src/pkg/testing/benchmark.go?s=743:872#L17">B</a></h2>
<pre>type B struct {
N int
<span class="comment">// contains filtered or unexported fields</span>
}</pre>
<p>
B is a type passed to Benchmark functions to manage benchmark
timing and to specify the number of iterations to run.
</p>
<h3 id="B.Error">func (*B) <a href="/src/pkg/testing/testing.go?s=8110:8153#L209">Error</a></h3>
<pre>func (c *B) Error(args ...interface{})</pre>
<p>
Error is equivalent to Log() followed by Fail().
</p>
<h3 id="B.Errorf">func (*B) <a href="/src/pkg/testing/testing.go?s=8253:8312#L215">Errorf</a></h3>
<pre>func (c *B) Errorf(format string, args ...interface{})</pre>
<p>
Errorf is equivalent to Logf() followed by Fail().
</p>
<h3 id="B.Fail">func (*B) <a href="/src/pkg/testing/testing.go?s=6270:6293#L163">Fail</a></h3>
<pre>func (c *B) Fail()</pre>
<p>
Fail marks the function as having failed but continues execution.
</p>
<h3 id="B.FailNow">func (*B) <a href="/src/pkg/testing/testing.go?s=6548:6574#L170">FailNow</a></h3>
<pre>func (c *B) FailNow()</pre>
<p>
FailNow marks the function as having failed and stops its execution.
Execution will continue at the next test or benchmark.
</p>
<h3 id="B.Failed">func (*B) <a href="/src/pkg/testing/testing.go?s=6366:6396#L166">Failed</a></h3>
<pre>func (c *B) Failed() bool</pre>
<p>
Failed returns whether the function has failed.
</p>
<h3 id="B.Fatal">func (*B) <a href="/src/pkg/testing/testing.go?s=8420:8463#L221">Fatal</a></h3>
<pre>func (c *B) Fatal(args ...interface{})</pre>
<p>
Fatal is equivalent to Log() followed by FailNow().
</p>
<h3 id="B.Fatalf">func (*B) <a href="/src/pkg/testing/testing.go?s=8569:8628#L227">Fatalf</a></h3>
<pre>func (c *B) Fatalf(format string, args ...interface{})</pre>
<p>
Fatalf is equivalent to Logf() followed by FailNow().
</p>
<h3 id="B.Log">func (*B) <a href="/src/pkg/testing/testing.go?s=7763:7804#L202">Log</a></h3>
<pre>func (c *B) Log(args ...interface{})</pre>
<p>
Log formats its arguments using default formatting, analogous to Println(),
and records the text in the error log.
</p>
<h3 id="B.Logf">func (*B) <a href="/src/pkg/testing/testing.go?s=7959:8016#L206">Logf</a></h3>
<pre>func (c *B) Logf(format string, args ...interface{})</pre>
<p>
Logf formats its arguments according to the format, analogous to Printf(),
and records the text in the error log.
</p>
<h3 id="B.ResetTimer">func (*B) <a href="/src/pkg/testing/benchmark.go?s=1503:1527#L48">ResetTimer</a></h3>
<pre>func (b *B) ResetTimer()</pre>
<p>
ResetTimer sets the elapsed benchmark time to zero.
It does not affect whether the timer is running.
</p>
<h3 id="B.SetBytes">func (*B) <a href="/src/pkg/testing/benchmark.go?s=1728:1757#L57">SetBytes</a></h3>
<pre>func (b *B) SetBytes(n int64)</pre>
<p>
SetBytes records the number of bytes processed in a single operation.
If this is called, the benchmark will report ns/op and MB/s.
</p>
<h3 id="B.StartTimer">func (*B) <a href="/src/pkg/testing/benchmark.go?s=1047:1071#L29">StartTimer</a></h3>
<pre>func (b *B) StartTimer()</pre>
<p>
StartTimer starts timing a test. This function is called automatically
before a benchmark starts, but it can also used to resume timing after
a call to StopTimer.
</p>
<h3 id="B.StopTimer">func (*B) <a href="/src/pkg/testing/benchmark.go?s=1288:1311#L39">StopTimer</a></h3>
<pre>func (b *B) StopTimer()</pre>
<p>
StopTimer stops timing a test. This can be used to pause the timer
while performing complex initialization that you don&#39;t
want to measure.
</p>
<h2 id="BenchmarkResult">type <a href="/src/pkg/testing/benchmark.go?s=4206:4391#L165">BenchmarkResult</a></h2>
<pre>type BenchmarkResult struct {
N int <span class="comment">// The number of iterations.</span>
T time.Duration <span class="comment">// The total time taken.</span>
Bytes int64 <span class="comment">// Bytes processed in one iteration.</span>
}</pre>
<p>
The results of a benchmark run.
</p>
<h3 id="Benchmark">func <a href="/src/pkg/testing/benchmark.go?s=7545:7589#L275">Benchmark</a></h3>
<pre>func Benchmark(f func(b *B)) BenchmarkResult</pre>
<p>
Benchmark benchmarks a single function. Useful for creating
custom benchmarks that do not use the &#34;go test&#34; command.
</p>
<h3 id="BenchmarkResult.NsPerOp">func (BenchmarkResult) <a href="/src/pkg/testing/benchmark.go?s=4393:4433#L171">NsPerOp</a></h3>
<pre>func (r BenchmarkResult) NsPerOp() int64</pre>
<h3 id="BenchmarkResult.String">func (BenchmarkResult) <a href="/src/pkg/testing/benchmark.go?s=4677:4717#L185">String</a></h3>
<pre>func (r BenchmarkResult) String() string</pre>
<h2 id="InternalBenchmark">type <a href="/src/pkg/testing/benchmark.go?s=555:618#L10">InternalBenchmark</a></h2>
<pre>type InternalBenchmark struct {
Name string
F func(b *B)
}</pre>
<p>
An internal type but exported because it is cross-package; part of the implementation
of the &#34;go test&#34; command.
</p>
<h2 id="InternalExample">type <a href="/src/pkg/testing/example.go?s=236:312#L6">InternalExample</a></h2>
<pre>type InternalExample struct {
Name string
F func()
Output string
}</pre>
<h2 id="InternalTest">type <a href="/src/pkg/testing/testing.go?s=9065:9121#L241">InternalTest</a></h2>
<pre>type InternalTest struct {
Name string
F func(*T)
}</pre>
<p>
An internal type but exported because it is cross-package; part of the implementation
of the &#34;go test&#34; command.
</p>
<h2 id="T">type <a href="/src/pkg/testing/testing.go?s=6070:6199#L156">T</a></h2>
<pre>type T struct {
<span class="comment">// contains filtered or unexported fields</span>
}</pre>
<p>
T is a type passed to Test functions to manage test state and support formatted test logs.
Logs are accumulated during execution and dumped to standard error when done.
</p>
<h3 id="T.Error">func (*T) <a href="/src/pkg/testing/testing.go?s=8110:8153#L209">Error</a></h3>
<pre>func (c *T) Error(args ...interface{})</pre>
<p>
Error is equivalent to Log() followed by Fail().
</p>
<h3 id="T.Errorf">func (*T) <a href="/src/pkg/testing/testing.go?s=8253:8312#L215">Errorf</a></h3>
<pre>func (c *T) Errorf(format string, args ...interface{})</pre>
<p>
Errorf is equivalent to Logf() followed by Fail().
</p>
<h3 id="T.Fail">func (*T) <a href="/src/pkg/testing/testing.go?s=6270:6293#L163">Fail</a></h3>
<pre>func (c *T) Fail()</pre>
<p>
Fail marks the function as having failed but continues execution.
</p>
<h3 id="T.FailNow">func (*T) <a href="/src/pkg/testing/testing.go?s=6548:6574#L170">FailNow</a></h3>
<pre>func (c *T) FailNow()</pre>
<p>
FailNow marks the function as having failed and stops its execution.
Execution will continue at the next test or benchmark.
</p>
<h3 id="T.Failed">func (*T) <a href="/src/pkg/testing/testing.go?s=6366:6396#L166">Failed</a></h3>
<pre>func (c *T) Failed() bool</pre>
<p>
Failed returns whether the function has failed.
</p>
<h3 id="T.Fatal">func (*T) <a href="/src/pkg/testing/testing.go?s=8420:8463#L221">Fatal</a></h3>
<pre>func (c *T) Fatal(args ...interface{})</pre>
<p>
Fatal is equivalent to Log() followed by FailNow().
</p>
<h3 id="T.Fatalf">func (*T) <a href="/src/pkg/testing/testing.go?s=8569:8628#L227">Fatalf</a></h3>
<pre>func (c *T) Fatalf(format string, args ...interface{})</pre>
<p>
Fatalf is equivalent to Logf() followed by FailNow().
</p>
<h3 id="T.Log">func (*T) <a href="/src/pkg/testing/testing.go?s=7763:7804#L202">Log</a></h3>
<pre>func (c *T) Log(args ...interface{})</pre>
<p>
Log formats its arguments using default formatting, analogous to Println(),
and records the text in the error log.
</p>
<h3 id="T.Logf">func (*T) <a href="/src/pkg/testing/testing.go?s=7959:8016#L206">Logf</a></h3>
<pre>func (c *T) Logf(format string, args ...interface{})</pre>
<p>
Logf formats its arguments according to the format, analogous to Printf(),
and records the text in the error log.
</p>
<h3 id="T.Parallel">func (*T) <a href="/src/pkg/testing/testing.go?s=8809:8831#L234">Parallel</a></h3>
<pre>func (t *T) Parallel()</pre>
<p>
Parallel signals that this test is to be run in parallel with (and only with)
other parallel tests in this CPU group.
</p>
</div>
<h2 id="subdirectories">Subdirectories</h2>
<table class="dir">
<tr>
<th>Name</th>
<th>&nbsp;&nbsp;&nbsp;&nbsp;</th>
<th style="text-align: left; width: auto">Synopsis</th>
</tr>
<tr>
<td><a href="..">..</a></td>
</tr>
<tr>
<td class="name"><a href="iotest">iotest</a></td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td style="width: auto">Package iotest implements Readers and Writers useful mainly for testing.</td>
</tr>
<tr>
<td class="name"><a href="quick">quick</a></td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;</td>
<td style="width: auto">Package quick implements utility functions to help with black box testing.</td>
</tr>
</table>
</div>
<div id="footer">
Build version go1.0.2.<br>
Except as <a href="http://code.google.com/policies.html#restrictions">noted</a>,
the content of this page is licensed under the
Creative Commons Attribution 3.0 License,
and code is licensed under a <a href="/LICENSE">BSD license</a>.<br>
<a href="/doc/tos.html">Terms of Service</a> |
<a href="http://www.google.com/intl/en/privacy/privacy-policy.html">Privacy Policy</a>
</div>
<script type="text/javascript">
(function() {
var ga = document.createElement("script"); ga.type = "text/javascript"; ga.async = true;
ga.src = ("https:" == document.location.protocol ? "https://ssl" : "http://www") + ".google-analytics.com/ga.js";
var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</body>
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
</html>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,413 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><meta http-equiv="X-UA-Compatible" content="IE=8" />
<meta name="keywords" content="metal, reviews, metalreview, metalreviews, heavy, rock, review, music, blogs, forums, community" />
<meta name="description" content="Critical heavy metal album and dvd reviews, written by professional writers. Large community with forums, blogs, photos and commenting system." />
<title>
Metal Reviews, News, Blogs, Interviews and Community | Metal Review
</title><link rel="stylesheet" type="text/css" href="/Content/Css/reset-fonts-grids.css" /><link rel="stylesheet" type="text/css" href="/Content/Css/base.css" /><link rel="stylesheet" type="text/css" href="/Content/Css/core.css" /><link rel="stylesheet" type="text/css" href="/Content/Css/wt-rotator.css" />
<script src="/Scripts/jquery-1.4.2.min.js" type="text/javascript"></script>
</head>
<body>
<script type="text/javascript">
var _comscore = _comscore || [];
_comscore.push({ c1: "2", c2: "9290245" });
(function () {
var s = document.createElement("script"), el = document.getElementsByTagName("script")[0]; s.async = true;
s.src = (document.location.protocol == "https:" ? "https://sb" : "http://b") + ".scorecardresearch.com/beacon.js";
el.parentNode.insertBefore(s, el);
})();
</script>
<noscript>
<img src="http://b.scorecardresearch.com/p?c1=2&c2=9290245&cv=2.0&cj=1" />
</noscript>
<div id="doc2" class="yui-t7">
<div id="hd">
<div id="main-logo"><a href="/" title="Home"><img src="/Content/Images/metal-review-logo.png" alt="Metal Review Home" border="0" /></a></div>
<div id="leaderboard-banner">
<script language="javascript" type="text/javascript"><!--
document.write('<scr' + 'ipt language="javascript1.1" src="http://adserver.adtechus.com/addyn/3.0/5110/73085/0/225/ADTECH;loc=100;target=_blank;key=key1+key2+key3+key4;grp=[group];misc=' + new Date().getTime() + '"></scri' + 'pt>');
//-->
</script>
<noscript>
<a href="http://adserver.adtechus.com/adlink/3.0/5110/73085/0/225/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" target="_blank">
<img src="http://adserver.adtechus.com/adserv/3.0/5110/73085/0/225/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" border="0" width="728" height="90" />
</a>
</noscript>
</div>
<div id="header-menu-container">
<div id="header-menu">
<a href="/reviews/browse">REVIEWS</a>
<a href="http://community2.metalreview.com/blogs/editorials/default.aspx">FEATURES</a>
<a href="/artists/browse">ARTISTS</a>
<a href="/reviews/pipeline">PIPELINE</a>
<a href="http://community2.metalreview.com/forums">FORUMS</a>
<a href="http://community2.metalreview.com/blogs/">BLOGS</a>
<a href="/aboutus">ABOUT US</a>
</div>
<div id="sign-in"><a href="https://metalreview.com/account/signin">SIGN IN</a>&nbsp;&nbsp;|&nbsp;&nbsp;<a href="/account/register">JOIN US</a></div>
</div>
</div>
<div id="bd">
<div id="yui-main">
<div class="yui-b">
<div class="yui-g">
<div class="yui-u first">
<script src="/Scripts/jquery.wt-rotator.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.wt-rotator-initialize.js" type="text/javascript"></script>
<div id="review-showcase-wrapper">
<h2 id="showcase-heading">Reviews</h2>
<div id="review-showcase">
<div class="container">
<div class="wt-rotator">
<a href="#"></a>
<div class="desc">
</div>
<div class="preloader">
</div>
<div class="c-panel">
<div class="buttons">
<div class="prev-btn">
</div>
<div class="play-btn">
</div>
<div class="next-btn">
</div>
</div>
<div class="thumbnails">
<ul>
<li><a href="artist.photo?mrx=4641" title="Serpentine Path - Serpentine Path"></a><a href="/reviews/6844/serpentine-path-serpentine-path"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6844" alt='Serpentine Path - Serpentine Path' title='Serpentine Path - Serpentine Path' />
<span class="title"><strong>Serpentine Path</strong></span><br />
Serpentine Path<br />
</p>
</li>
<li><a href="artist.photo?mrx=4635" title="Hunter's Ground - No God But the Wild"></a><a href="/reviews/6830/hunters-ground-no-god-but-the-wild"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6830" alt='Hunter's Ground - No God But the Wild' title='Hunter's Ground - No God But the Wild' />
<span class="title"><strong>Hunter's Ground</strong></span><br />
No God But the Wild<br />
</p>
</li>
<li><a href="artist.photo?mrx=1035" title="Blut Aus Nord - 777 - Cosmosophy"></a><a href="/reviews/6829/blut-aus-nord-777---cosmosophy"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6829" alt='Blut Aus Nord - 777 - Cosmosophy' title='Blut Aus Nord - 777 - Cosmosophy' />
<span class="title"><strong>Blut Aus Nord</strong></span><br />
777 - Cosmosophy<br />
<a href="/tags/10/black"><span class="tag">Black</span></a>
</p>
</li>
<li><a href="artist.photo?mrx=1217" title="Ufomammut - Oro: Opus Alter"></a><a href="/reviews/6835/ufomammut-oro--opus-alter"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6835" alt='Ufomammut - Oro: Opus Alter' title='Ufomammut - Oro: Opus Alter' />
<span class="title"><strong>Ufomammut</strong></span><br />
Oro: Opus Alter<br />
<a href="/tags/2/doom"><span class="tag">Doom</span></a>
</p>
</li>
<li><a href="artist.photo?mrx=4590" title="Resurgency - False Enlightenment"></a><a href="/reviews/6746/resurgency-false-enlightenment"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6746" alt='Resurgency - False Enlightenment' title='Resurgency - False Enlightenment' />
<span class="title"><strong>Resurgency</strong></span><br />
False Enlightenment<br />
<a href="/tags/1/death"><span class="tag">Death</span></a>
</p>
</li>
<li><a href="artist.photo?mrx=1360" title="Morgoth - Cursed to Live"></a><a href="/reviews/6800/morgoth-cursed-to-live"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6800" alt='Morgoth - Cursed to Live' title='Morgoth - Cursed to Live' />
<span class="title"><strong>Morgoth</strong></span><br />
Cursed to Live<br />
<a href="/tags/1/death"><span class="tag">Death</span></a><a href="/tags/31/live"><span class="tag">Live</span></a>
</p>
</li>
<li><a href="artist.photo?mrx=3879" title="Krallice - Years Past Matter"></a><a href="/reviews/6853/krallice-years-past-matter"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6853" alt='Krallice - Years Past Matter' title='Krallice - Years Past Matter' />
<span class="title"><strong>Krallice</strong></span><br />
Years Past Matter<br />
<a href="/tags/10/black"><span class="tag">Black</span></a>
</p>
</li>
<li><a href="artist.photo?mrx=4243" title="Murder Construct - Results"></a><a href="/reviews/6782/murder-construct-results"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6782" alt='Murder Construct - Results' title='Murder Construct - Results' />
<span class="title"><strong>Murder Construct</strong></span><br />
Results<br />
<a href="/tags/13/grindcore"><span class="tag">Grindcore</span></a>
</p>
</li>
<li><a href="artist.photo?mrx=251" title="Grave - Endless Procession of Souls"></a><a href="/reviews/6834/grave-endless-procession-of-souls"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6834" alt='Grave - Endless Procession of Souls' title='Grave - Endless Procession of Souls' />
<span class="title"><strong>Grave</strong></span><br />
Endless Procession of Souls<br />
<a href="/tags/1/death"><span class="tag">Death</span></a>
</p>
</li>
<li><a href="artist.photo?mrx=3508" title="Master - The New Elite"></a><a href="/reviews/6774/master-the-new-elite"></a>
<p style="top: 130px; left: 22px; width: 305px; height:60px;">
<img class="rotator-cover-art" src="album.cover?art=6774" alt='Master - The New Elite' title='Master - The New Elite' />
<span class="title"><strong>Master</strong></span><br />
The New Elite<br />
<a href="/tags/1/death"><span class="tag">Death</span></a>
</p>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div id="showcase-all-artist-albums">
<a href="/reviews/6844/serpentine-path-serpentine-path"><img src="album.cover?art=6844" alt="Serpentine Path - Serpentine Path" /></a><a href="/reviews/6830/hunters-ground-no-god-but-the-wild"><img src="album.cover?art=6830" alt="Hunter's Ground - No God But the Wild" /></a><a href="/reviews/6829/blut-aus-nord-777---cosmosophy"><img src="album.cover?art=6829" alt="Blut Aus Nord - 777 - Cosmosophy" /></a><a href="/reviews/6835/ufomammut-oro--opus-alter"><img src="album.cover?art=6835" alt="Ufomammut - Oro: Opus Alter" /></a><a href="/reviews/6746/resurgency-false-enlightenment"><img src="album.cover?art=6746" alt="Resurgency - False Enlightenment" /></a><a href="/reviews/6800/morgoth-cursed-to-live"><img src="album.cover?art=6800" alt="Morgoth - Cursed to Live" /></a><a href="/reviews/6853/krallice-years-past-matter"><img src="album.cover?art=6853" alt="Krallice - Years Past Matter" /></a><a href="/reviews/6782/murder-construct-results"><img src="album.cover?art=6782" alt="Murder Construct - Results" /></a><a href="/reviews/6834/grave-endless-procession-of-souls"><img src="album.cover?art=6834" alt="Grave - Endless Procession of Souls" /></a><a href="/reviews/6774/master-the-new-elite"><img src="album.cover?art=6774" alt="Master - The New Elite" /></a>
</div>
</div>
</div>
<div class="yui-u">
<div id="feature-feed">
<h2>Features</h2>
<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/08/15/corsair-interview.aspx"><span class="feature-link"><strong>Release The SkyKrakken: Corsair Interview</strong></span></a><br /><span class="publish-date">8/15/2012 by JW</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.21.25/4TMR3E1CWERK.jpg" alt="JW's Avatar" width="36px" height="40px" border="0" /></div>
<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/08/09/riffology-kreative-evolution-part-iii.aspx"><span class="feature-link"><strong>Riffology: Kreative Evolution, Part III</strong></span></a><br /><span class="publish-date">8/9/2012 by Achilles</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.21.44/4THUGH622I68.jpg" alt="Achilles's Avatar" width="40px" height="39px" border="0" /></div>
<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/08/02/reverend-s-bazaar-don-t-trend-on-me.aspx"><span class="feature-link"><strong>Reverend's Bazaar - Don't Trend On Me </strong></span></a><br /><span class="publish-date">8/2/2012 by Reverend Campbell</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.21.18/4TM06FD0ND4G.png" alt="Reverend Campbell's Avatar" width="34px" height="40px" border="0" /></div>
<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/08/01/grand-theft-metal-three-for-free.aspx"><span class="feature-link"><strong>Grand Theft Metal - Free Four All </strong></span></a><br /><span class="publish-date">8/2/2012 by Dave</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.22.16/4TKJCJQ00VFO.jpg" alt="Dave's Avatar" width="33px" height="40px" border="0" /></div>
<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/07/29/monday-with-moonspell-the-interview.aspx"><span class="feature-link"><strong>A Monday with Moonspell: The Interview</strong></span></a><br /><span class="publish-date">7/29/2012 by raetamacue</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.71.26/4TLIHKLUSXF4.jpg" alt="raetamacue's Avatar" width="37px" height="40px" border="0" /></div>
<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/07/26/riffology-kreative-evolution-part-ii.aspx"><span class="feature-link"><strong>Riffology: Kreative Evolution Part II</strong></span></a><br /><span class="publish-date">7/26/2012 by Achilles</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.21.44/4THUGH622I68.jpg" alt="Achilles's Avatar" width="40px" height="39px" border="0" /></div>
<div class="feature-feed-line"><a href="http://community2.metalreview.com/blogs/editorials/archive/2012/07/24/shadow-kingdom-records-giveaway.aspx"><span class="feature-link"><strong>WINNERS ANNOUNCED -- Shadow Kingdom Records Give...</strong></span></a><br /><span class="publish-date">7/24/2012 by Metal Review</span><img align="left" src="http://community2.metalreview.com/cfs-file.ashx/__key/CommunityServer.Components.Avatars/00.00.00.59.06/4TFD2N58B7BS.png" alt="Metal Review's Avatar" width="34px" height="40px" border="0" /></div>
<br />
<a href="http://community2.metalreview.com/blogs/editorials/default.aspx"><strong>More Editorials</strong></a>
</div>
</div>
</div>
</div>
</div>
<div class="yui-b">
<script src="/Scripts/jquery.cycle.all.min.js" type="text/javascript"></script>
<div id="slider-next-button"><img id="slider-next" src="/Content/Images/Backgrounds/rotator-next-button.png" alt="Goto Next Group" title="Goto Next Group" /></div>
<div id="slider-back-button"><img id="slider-back" src="/Content/Images/Backgrounds/rotator-back-button.png" alt="Goto Previous Group" title="Goto Previous Group" /></div>
<div id="latest-reviews-slider">
<div class="slider-row">
<div class="slider-item"><a href="/reviews/6795/midnight-complete-and-total-hell"><img src="album.cover?art=6795" alt="Midnight Complete and Total Hell" /><br /><strong>Midnight</strong><br /><em>Complete and Total Hell</em></a><div class="score">8.5</div></div>
<div class="slider-item"><a href="/reviews/6842/over-your-threshold-facticity"><img src="album.cover?art=6842" alt="Over Your Threshold Facticity" /><br /><strong>Over Your Threshold</strong><br /><em>Facticity</em></a><div class="score">6.0</div></div>
<div class="slider-item"><a href="/reviews/6813/nuclear-death-terror-chaos-reigns"><img src="album.cover?art=6813" alt="Nuclear Death Terror Chaos Reigns" /><br /><strong>Nuclear Death Terror</strong><br /><em>Chaos Reigns</em></a><div class="score">7.5</div></div>
<div class="slider-item"><a href="/reviews/6811/evoken-atra-mors"><img src="album.cover?art=6811" alt="Evoken Atra Mors" /><br /><strong>Evoken</strong><br /><em>Atra Mors</em></a><div class="score">9.5</div></div>
<div class="slider-item"><a href="/reviews/6807/blacklodge-machination"><img src="album.cover?art=6807" alt="Blacklodge MachinatioN" /><br /><strong>Blacklodge</strong><br /><em>MachinatioN</em></a><div class="score">5.5</div></div>
<div class="slider-item"><a href="/reviews/6832/prototype-catalyst"><img src="album.cover?art=6832" alt="Prototype Catalyst" /><br /><strong>Prototype</strong><br /><em>Catalyst</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6822/hypnosia-horror-infernal"><img src="album.cover?art=6822" alt="Hypnosia Horror Infernal" /><br /><strong>Hypnosia</strong><br /><em>Horror Infernal</em></a><div class="score">7.0</div></div>
<div class="slider-item"><a href="/reviews/6787/om-advaitic-songs"><img src="album.cover?art=6787" alt="OM Advaitic Songs" /><br /><strong>OM</strong><br /><em>Advaitic Songs</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6765/afgrund-the-age-of-dumb"><img src="album.cover?art=6765" alt="Afgrund The Age Of Dumb" /><br /><strong>Afgrund</strong><br /><em>The Age Of Dumb</em></a><div class="score">8.5</div></div>
<div class="slider-item"><a href="/reviews/6773/binah-hallucinating-in-resurrecture"><img src="album.cover?art=6773" alt="Binah Hallucinating in Resurrecture" /><br /><strong>Binah</strong><br /><em>Hallucinating in Resurrecture</em></a><div class="score">8.5</div></div>
</div>
<div class="slider-row">
<div class="slider-item"><a href="/reviews/6802/deiphago-satan-alpha-omega"><img src="album.cover?art=6802" alt="Deiphago Satan Alpha Omega" /><br /><strong>Deiphago</strong><br /><em>Satan Alpha Omega</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6719/conan-monnos"><img src="album.cover?art=6719" alt="Conan Monnos" /><br /><strong>Conan</strong><br /><em>Monnos</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6702/alaric-alaric-atriarch---split-lp"><img src="album.cover?art=6702" alt="Alaric Alaric/Atriarch - Split LP" /><br /><strong>Alaric</strong><br /><em>Alaric/Atriarch - Split LP</em></a><div class="score">8.5</div></div>
<div class="slider-item"><a href="/reviews/6780/coven-worship-new-gods-(reissue)"><img src="album.cover?art=6780" alt="Coven Worship New Gods (Reissue)" /><br /><strong>Coven</strong><br /><em>Worship New Gods (Reissue)</em></a><div class="score">5.0</div></div>
<div class="slider-item"><a href="/reviews/6831/the-foreshadowing-second-world"><img src="album.cover?art=6831" alt="The Foreshadowing Second World" /><br /><strong>The Foreshadowing</strong><br /><em>Second World</em></a><div class="score">5.5</div></div>
<div class="slider-item"><a href="/reviews/6815/nether-regions-into-the-breach"><img src="album.cover?art=6815" alt="Nether Regions Into The Breach" /><br /><strong>Nether Regions</strong><br /><em>Into The Breach</em></a><div class="score">7.0</div></div>
<div class="slider-item"><a href="/reviews/6824/agalloch-faustian-echoes"><img src="album.cover?art=6824" alt="Agalloch Faustian Echoes" /><br /><strong>Agalloch</strong><br /><em>Faustian Echoes</em></a><div class="score">9.0</div></div>
<div class="slider-item"><a href="/reviews/6805/a-forest-of-stars-a-shadowplay-for-yesterdays"><img src="album.cover?art=6805" alt="A Forest Of Stars A Shadowplay For Yesterdays" /><br /><strong>A Forest Of Stars</strong><br /><em>A Shadowplay For Yesterdays</em></a><div class="score">9.0</div></div>
<div class="slider-item"><a href="/reviews/6763/de-profundis-the-emptiness-within"><img src="album.cover?art=6763" alt="De Profundis The Emptiness Within" /><br /><strong>De Profundis</strong><br /><em>The Emptiness Within</em></a><div class="score">7.5</div></div>
<div class="slider-item"><a href="/reviews/6826/ozzy-osbourne-speak-of-the-devil"><img src="album.cover?art=6826" alt="Ozzy Osbourne Speak of the Devil" /><br /><strong>Ozzy Osbourne</strong><br /><em>Speak of the Devil</em></a><div class="score">7.5</div></div>
</div>
<div class="slider-row">
<div class="slider-item"><a href="/reviews/6825/testament-dark-roots-of-earth"><img src="album.cover?art=6825" alt="Testament Dark Roots of Earth" /><br /><strong>Testament</strong><br /><em>Dark Roots of Earth</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6796/eagle-twin-the-feather-tipped-the-serpents-scale"><img src="album.cover?art=6796" alt="Eagle Twin The Feather Tipped The Serpent's Scale" /><br /><strong>Eagle Twin</strong><br /><em>The Feather Tipped The Serpent's Scale</em></a><div class="score">8.5</div></div>
<div class="slider-item"><a href="/reviews/6609/king-forged-by-satans-doctrine"><img src="album.cover?art=6609" alt="King Forged by Satan's Doctrine" /><br /><strong>King</strong><br /><em>Forged by Satan's Doctrine</em></a><div class="score">5.5</div></div>
<div class="slider-item"><a href="/reviews/6798/khors-wisdom-of-centuries"><img src="album.cover?art=6798" alt="Khors Wisdom of Centuries" /><br /><strong>Khors</strong><br /><em>Wisdom of Centuries</em></a><div class="score">8.5</div></div>
<div class="slider-item"><a href="/reviews/6776/samothrace-reverence-to-stone"><img src="album.cover?art=6776" alt="Samothrace Reverence To Stone" /><br /><strong>Samothrace</strong><br /><em>Reverence To Stone</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6784/horseback-on-the-eclipse"><img src="album.cover?art=6784" alt="Horseback On the Eclipse" /><br /><strong>Horseback</strong><br /><em>On the Eclipse</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6690/incoming-cerebral-overdrive-le-stelle--a-voyage-adrift"><img src="album.cover?art=6690" alt="Incoming Cerebral Overdrive Le Stelle: A Voyage Adrift" /><br /><strong>Incoming Cerebral Overdrive</strong><br /><em>Le Stelle: A Voyage Adrift</em></a><div class="score">7.5</div></div>
<div class="slider-item"><a href="/reviews/6658/struck-by-lightning-true-predation"><img src="album.cover?art=6658" alt="Struck By Lightning True Predation" /><br /><strong>Struck By Lightning</strong><br /><em>True Predation</em></a><div class="score">7.0</div></div>
<div class="slider-item"><a href="/reviews/6772/offending-age-of-perversion"><img src="album.cover?art=6772" alt="Offending Age of Perversion" /><br /><strong>Offending</strong><br /><em>Age of Perversion</em></a><div class="score">7.5</div></div>
<div class="slider-item"><a href="/reviews/6804/king-of-asgard----to-north"><img src="album.cover?art=6804" alt="King Of Asgard ...to North" /><br /><strong>King Of Asgard</strong><br /><em>...to North</em></a><div class="score">7.5</div></div>
</div>
<div class="slider-row">
<div class="slider-item"><a href="/reviews/6783/burning-love-rotten-thing-to-say"><img src="album.cover?art=6783" alt="Burning Love Rotten Thing to Say" /><br /><strong>Burning Love</strong><br /><em>Rotten Thing to Say</em></a><div class="score">7.0</div></div>
<div class="slider-item"><a href="/reviews/6770/high-on-fire-the-art-of-self-defense-(reissue)"><img src="album.cover?art=6770" alt="High On Fire The Art Of Self Defense (Reissue)" /><br /><strong>High On Fire</strong><br /><em>The Art Of Self Defense (Reissue)</em></a><div class="score">7.5</div></div>
<div class="slider-item"><a href="/reviews/6660/horseback-half-blood"><img src="album.cover?art=6660" alt="Horseback Half Blood" /><br /><strong>Horseback</strong><br /><em>Half Blood</em></a><div class="score">6.5</div></div>
<div class="slider-item"><a href="/reviews/6732/aldebaran-embracing-the-lightless-depths"><img src="album.cover?art=6732" alt="Aldebaran Embracing the Lightless Depths" /><br /><strong>Aldebaran</strong><br /><em>Embracing the Lightless Depths</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6778/tank-war-nation"><img src="album.cover?art=6778" alt="Tank War Nation" /><br /><strong>Tank</strong><br /><em>War Nation</em></a><div class="score">6.5</div></div>
<div class="slider-item"><a href="/reviews/6793/satanic-bloodspraying-at-the-mercy-of-satan"><img src="album.cover?art=6793" alt="Satanic Bloodspraying At the Mercy of Satan" /><br /><strong>Satanic Bloodspraying</strong><br /><em>At the Mercy of Satan</em></a><div class="score">8.5</div></div>
<div class="slider-item"><a href="/reviews/6791/from-ashes-rise-rejoice-the-end---rage-of-sanity"><img src="album.cover?art=6791" alt="From Ashes Rise Rejoice The End / Rage Of Sanity" /><br /><strong>From Ashes Rise</strong><br /><em>Rejoice The End / Rage Of Sanity</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6743/ereb-altor-gastrike"><img src="album.cover?art=6743" alt="Ereb Altor Gastrike" /><br /><strong>Ereb Altor</strong><br /><em>Gastrike</em></a><div class="score">8.0</div></div>
<div class="slider-item"><a href="/reviews/6794/catheter-southwest-doom-violence"><img src="album.cover?art=6794" alt="Catheter Southwest Doom Violence" /><br /><strong>Catheter</strong><br /><em>Southwest Doom Violence</em></a><div class="score">7.0</div></div>
<div class="slider-item"><a href="/reviews/6759/power-theory-an-axe-to-grind"><img src="album.cover?art=6759" alt="Power Theory An Axe to Grind" /><br /><strong>Power Theory</strong><br /><em>An Axe to Grind</em></a><div class="score">6.0</div></div>
</div>
</div>
<script type="text/javascript">
$(document).ready(function () {
$('#latest-reviews-slider').cycle({
fx: 'scrollRight',
speed: 'fast',
timeout: 0,
next: '#slider-next-button',
prev: '#slider-back-button'
});
});
</script>
<div id="homepage-mid-horizontal-zone">
<script language="javascript" type="text/javascript" src="http://metalreview.com/bannermgr/abm.aspx?z=1"></script>
</div>
<div id="news-feed">
<h2>News</h2><div class="news-feed-line"><a href="http://www.bravewords.com/news/190057" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> CENTURIAN To Release Contra Rationem Album This Winter</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190056" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> Southwest Terror Fest 2012 - Lineup Changes Announced</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190055" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> ROB ZOMBIE Premiers The Lords Of Salem At TIFF; Q&A Video Posted</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190054" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> THIN LIZZY Keyboardist Darren Wharton's DARE - Calm Before The Storm 2 Album Details Revealed</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190053" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> Japan's LIV MOON To Release Fourth Album; Features Past/Present Members Of EUROPE, ANGRA, HAMMERFALL</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190052" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> SLASH - Sydney Show To Premier This Friday, Free And In HD; Trailer Posted</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190051" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> KHAØS - New Band Featuring Members Of OUTLOUD, TRIBAL, JORN And ELIS To Release New EP In October; Teaser Posted </strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190050" target="_blank"><span class="news-link"><strong><span class="new-news">NEW</span> RECKLESS LOVE Confirm Guests For London Residency Shows In October</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190049" target="_blank"><span class="news-link"><strong>NASHVILLE PUSSY Add Dates In France, Sweden To European Tour Schedule; Bassist Karen Cuda Sidelined With Back Injury </strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190048" target="_blank"><span class="news-link"><strong>CALIBAN Post Behind-The-Scenes Tour Footage</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190047" target="_blank"><span class="news-link"><strong>Ex-MERCYFUL FATE Drummer Kim Ruzz Forms New Band METALRUZZ</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
<div class="news-feed-line"><a href="http://www.bravewords.com/news/190046" target="_blank"><span class="news-link"><strong>GRAVE Mainman On Endless Procession Of Souls - "These Are The Most Song-Oriented Tracks Weve Done In A Long Time"</strong></span></a><br /><span class="publish-date">9/12/2012</span></div><br />
</div>
<div id="lashes-feed">
<h2>Lashes</h2>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81760"><span class="new-lash">NEW</span> <span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">45 minutes ago by Chaosjunkie</span></div>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81759"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">1 hour ago by Harry Dick Rotten</span></div>
<div class="lashes-feed-line"><a href="/reviews/6746/resurgency-false-enlightenment#81758"><span class="lashes-link"><strong>Resurgency - False Enlightenment</strong></span></a><br /><span class="publish-date">3 hours ago by Anonymous</span></div>
<div class="lashes-feed-line"><a href="/reviews/4095/witchcraft-the-alchemist#81757"><span class="lashes-link"><strong>Witchcraft - The Alchemist</strong></span></a><br /><span class="publish-date">5 hours ago by Luke_22</span></div>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81756"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">9 hours ago by chaosjunkie</span></div>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81755"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">10 hours ago by Compeller</span></div>
<div class="lashes-feed-line"><a href="/reviews/6827/manetheren-time#81754"><span class="lashes-link"><strong>Manetheren - Time</strong></span></a><br /><span class="publish-date">10 hours ago by xpmule</span></div>
<div class="lashes-feed-line"><a href="/reviews/6835/ufomammut-oro--opus-alter#81753"><span class="lashes-link"><strong>Ufomammut - Oro: Opus Alter</strong></span></a><br /><span class="publish-date">16 hours ago by Anonymous</span></div>
<div class="lashes-feed-line"><a href="/reviews/6835/ufomammut-oro--opus-alter#81752"><span class="lashes-link"><strong>Ufomammut - Oro: Opus Alter</strong></span></a><br /><span class="publish-date">17 hours ago by Harry Dick Rotten</span></div>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81751"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by Chaosjunkie</span></div>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81750"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by Anonymous</span></div>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81749"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by Anonymous</span></div>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81748"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by Anonymous</span></div>
<div class="lashes-feed-line"><a href="/reviews/6855/katatonia-dead-end-kings#81747"><span class="lashes-link"><strong>Katatonia - Dead End Kings</strong></span></a><br /><span class="publish-date">yesterday by frantic</span></div>
<div class="lashes-feed-line"><a href="/reviews/6829/blut-aus-nord-777---cosmosophy#81746"><span class="lashes-link"><strong>Blut Aus Nord - 777 - Cosmosophy</strong></span></a><br /><span class="publish-date">yesterday by Dimensional Bleedthrough</span></div>
</div>
</div>
</div>
<div id="ft">
<div id="template-footer">
<div class="left-column">
<ul>
<li><a href="/">Home</a></li>
<li><a href="/reviews/browse">Reviews</a></li>
<li><a href="/tags">Genre Tags</a></li>
<li><a href="http://community2.metalreview.com/blogs/editorials/default.aspx">Features</a></li>
<li><a href="/artists/browse">Artists</a></li>
<li><a href="/reviews/pipeline">Pipeline</a></li>
<li><a href="http://community2.metalreview.com/forums">Forums</a></li>
<li><a href="/aboutus">About Us</a></li>
</ul>
</div>
<div class="middle-column">
<ul>
<li><a href="/aboutus/disclaimer">Disclaimer</a></li>
<li><a href="/aboutus/privacypolicy">Privacy Policy</a></li>
<li><a href="/aboutus/advertising">Advertising</a></li>
<li><a href="http://community2.metalreview.com/blogs/eminor/archive/2008/10/27/write-for-metal-review.aspx">Write For Us</a></li>
<li><a href="/contactus">Contact Us</a></li>
<li><a href="/contactus">Digital Promos</a></li>
<li><a href="/contactus">Mailing Address</a></li>
</ul>
</div>
<div class="right-column">
<ul>
<li><a href="http://feeds.feedburner.com/metalreviews">Reviews RSS Feed</a></li>
<li><a href="http://twitter.com/metalreview">Twitter</a></li>
<li><a href="http://www.myspace.com/metalreviewdotcom">MySpace</a></li>
<li><a href="http://www.last.fm/group/MetalReview.com">Last.fm</a></li>
<li><a href="http://www.facebook.com/pages/MetalReviewcom/48371319443">Facebook</a></li>
</ul>
</div>
<div class="square-ad">
<!--JavaScript Tag // Tag for network 5110: Fixion Media // Website: Metalreview // Page: ROS // Placement: ROS-Middle-300 x 250 (1127996) // created at: Oct 19, 2009 6:48:27 PM-->
<script type="text/javascript" language="javascript"><!--
document.write('<scr' + 'ipt language="javascript1.1" src="http://adserver.adtechus.com/addyn/3.0/5110/1127996/0/170/ADTECH;loc=100;target=_blank;key=key1+key2+key3+key4;grp=[group];misc=' + new Date().getTime() + '"></scri' + 'pt>');
//-->
</script><noscript><a href="http://adserver.adtechus.com/adlink/3.0/5110/1127996/0/170/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" target="_blank"><img src="http://adserver.adtechus.com/adserv/3.0/5110/1127996/0/170/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" border="0" width="300" height="250"></a></noscript>
<!-- End of JavaScript Tag -->
</div>
</div>
</div>
</div>
<script type="text/javascript">
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script>
<script type="text/javascript">
var pageTracker = _gat._getTracker("UA-3455310-1");
pageTracker._initData();
pageTracker._trackPageview();
</script>
<!--JavaScript Tag // Tag for network 5110: Fixion Media // Website: Metalreview // Page: BACKGROUND ADS // Placement: BACKGROUND ADS-Top-1 x 1 (2186116) // created at: Aug 18, 2011 7:20:38 PM-->
<script language="javascript"><!--
document.write('<scr' + 'ipt language="javascript1.1" src="http://adserver.adtechus.com/addyn/3.0/5110/2186116/0/16/ADTECH;loc=100;target=_blank;key=key1+key2+key3+key4;grp=[group];misc=' + new Date().getTime() + '"></scri' + 'pt>');
//-->
</script><noscript><a href="http://adserver.adtechus.com/adlink/3.0/5110/2186116/0/16/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" target="_blank"><img src="http://adserver.adtechus.com/adserv/3.0/5110/2186116/0/16/ADTECH;loc=300;key=key1+key2+key3+key4;grp=[group]" border="0" width="1" height="1"></a></noscript>
<!-- End of JavaScript Tag -->
</body>
</html>

View file

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="en" ng-app="app">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>
Provok.in
</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Provok.in - Prove your point. State an affirmation, back it up with evidence, unveil the truth.">
<meta name="author" content="Martin Angers">
<link href="http://fonts.googleapis.com/css?family=Belgrano" rel="stylesheet" type="text/css">
<!--[if lt IE 9]><link href="http://fonts.googleapis.com/css?family=Belgrano" rel="stylesheet" type="text/css"><link href="http://fonts.googleapis.com/css?family=Belgrano:400italic" rel="stylesheet" type="text/css"><link href="http://fonts.googleapis.com/css?family=Belgrano:700" rel="stylesheet" type="text/css"><link href="http://fonts.googleapis.com/css?family=Belgrano:700italic" rel="stylesheet" type="text/css"><![endif]-->
<link href="/css/pvk.min.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container-fluid" id="cf1">
<div class="row-fluid">
<div class="pvk-gutter">
&nbsp;
</div>
<div class="pvk-content" id="pc1">
<div ng-controller="HeroCtrl" class="hero-unit">
<div class="container-fluid" id="cf2">
<div class="row-fluid" id="cf2-1">
<div class="span12">
<h1>
<a href="/">Provok<span class="green">.</span><span class="red">i</span>n</a>
</h1>
<p>
Prove your point.
</p>
</div>
</div>
<div class="row-fluid" id="cf2-2">
<div class="span12 alert alert-error">
<strong>Beta Version.</strong> Things may change. Or disappear. Or fail miserably. If it's the latter, <a href="https://github.com/PuerkitoBio/Provok.in-issues" target="_blank" class="link">please file an issue.</a>
</div>
</div>
<div ng-cloak="" ng-show="isLoggedOut() &amp;&amp; !hideLogin" class="row-fluid" id="cf2-3">
<a ng-href="{{ROUTES.login}}" class="btn btn-primary">Sign in. Painless.</a> <span>or</span> <a ng-href="{{ROUTES.help}}" class="link">learn more about provok.in.</a>
</div>
<div ng-cloak="" ng-show="isLoggedIn()" class="row-fluid logged-in-state" id="cf2-4">
<span>Welcome,</span> <a ng-href="{{ROUTES.profile}}" class="link">{{getUserName()}}</a> <span>(</span> <a ng-click="doLogout($event)" class="link">logout</a> <span>)</span>
</div>
</div>
</div>
</div>
<div class="pvk-gutter">
&nbsp;
</div>
</div>
<div class="row-fluid">
<div class="pvk-gutter">
&nbsp;
</div>
<div class="pvk-content" id="pc2">
<div class="container-fluid" id="cf3">
<div class="row-fluid">
<div ng-cloak="" view-on-display="" ng-controller="MsgCtrl" ng-class="{'displayed': blockIsDisplayed}" class="message-box">
<div ng-class="{'alert-info': isInfo, 'alert-error': !isInfo, 'displayed': isDisplayed}" class="alert">
<a ng-click="hideMessage(true, $event)" class="close">×</a>
<h4 class="alert-heading">
{{ title }}
</h4>
<p>
{{ message }}
</p>
</div>
</div>
</div>
</div>
<div class="container-fluid" id="cf4">
<div ng-controller="ShareCtrl" ng-hide="isHidden" class="row-fluid center-content"></div>
</div>
<div ng-view=""></div>
</div>
<div class="pvk-gutter">
&nbsp;
</div>
</div>
<div class="row-fluid">
<div class="pvk-gutter">
&nbsp;
</div>
<div class="pvk-content">
<div class="footer">
<p>
<a href="/" class="link">Home</a> <span>|</span> <a href="/about" class="link">About</a> <span>|</span> <a href="/help" class="link">Help</a>
</p>
<p>
<small>© 2012 Martin Angers</small>
</p>
</div>
</div>
<div class="pvk-gutter">
&nbsp;
</div>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Tests for siblings</title>
</head>
<BODY>
<div id="main">
<div id="n1" class="one even row"></div>
<div id="n2" class="two odd row"></div>
<div id="n3" class="three even row"></div>
<div id="n4" class="four odd row"></div>
<div id="n5" class="five even row"></div>
<div id="n6" class="six odd row"></div>
</div>
<div id="foot">
<div id="nf1" class="one even row"></div>
<div id="nf2" class="two odd row"></div>
<div id="nf3" class="three even row"></div>
<div id="nf4" class="four odd row"></div>
<div id="nf5" class="five even row odder"></div>
<div id="nf6" class="six odd row"></div>
</div>
</BODY>
</html>

View file

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<title>Tests for siblings</title>
</head>
<BODY>
<div id="main">
<div id="n1" class="one even row">hello</div>
<div id="n2" class="two odd row"></div>
<div id="n3" class="three even row"></div>
<div id="n4" class="four odd row"></div>
<div id="n5" class="five even row"></div>
<div id="n6" class="six odd row"></div>
</div>
<div id="foot">
<div id="nf1" class="one even row">text</div>
<div id="nf2" class="two odd row"></div>
<div id="nf3" class="three even row"></div>
<div id="nf4" class="four odd row"></div>
<div id="nf5" class="five even row odder"></div>
<div id="nf6" class="six odd row"></div>
</div>
</BODY>
</html>

698
vendor/github.com/PuerkitoBio/goquery/traversal.go generated vendored Normal file
View file

@ -0,0 +1,698 @@
package goquery
import "golang.org/x/net/html"
type siblingType int
// Sibling type, used internally when iterating over children at the same
// level (siblings) to specify which nodes are requested.
const (
siblingPrevUntil siblingType = iota - 3
siblingPrevAll
siblingPrev
siblingAll
siblingNext
siblingNextAll
siblingNextUntil
siblingAllIncludingNonElements
)
// Find gets the descendants of each element in the current set of matched
// 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, compileMatcher(selector)))
}
// FindMatcher gets the descendants of each element in the current set of matched
// elements, filtered by the matcher. It returns a new Selection object
// containing these matched elements.
func (s *Selection) FindMatcher(m Matcher) *Selection {
return pushStack(s, findWithMatcher(s.Nodes, m))
}
// FindSelection gets the descendants of each element in the current
// Selection, filtered by a Selection. It returns a new Selection object
// containing these matched elements.
func (s *Selection) FindSelection(sel *Selection) *Selection {
if sel == nil {
return pushStack(s, nil)
}
return s.FindNodes(sel.Nodes...)
}
// FindNodes gets the descendants of each element in the current
// Selection, filtered by some nodes. It returns a new Selection object
// containing these matched elements.
func (s *Selection) FindNodes(nodes ...*html.Node) *Selection {
return pushStack(s, mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
if sliceContains(s.Nodes, n) {
return []*html.Node{n}
}
return nil
}))
}
// Contents gets the children of each element in the Selection,
// including text and comment nodes. It returns a new Selection object
// containing these elements.
func (s *Selection) Contents() *Selection {
return pushStack(s, getChildrenNodes(s.Nodes, siblingAllIncludingNonElements))
}
// ContentsFiltered gets the children of each element in the Selection,
// filtered by the specified selector. It returns a new Selection
// object containing these elements. Since selectors only act on Element nodes,
// this function is an alias to ChildrenFiltered unless the selector is empty,
// in which case it is an alias to Contents.
func (s *Selection) ContentsFiltered(selector string) *Selection {
if selector != "" {
return s.ChildrenFiltered(selector)
}
return s.Contents()
}
// ContentsMatcher gets the children of each element in the Selection,
// filtered by the specified matcher. It returns a new Selection
// object containing these elements. Since matchers only act on Element nodes,
// this function is an alias to ChildrenMatcher.
func (s *Selection) ContentsMatcher(m Matcher) *Selection {
return s.ChildrenMatcher(m)
}
// Children gets the child elements of each element in the Selection.
// It returns a new Selection object containing these elements.
func (s *Selection) Children() *Selection {
return pushStack(s, getChildrenNodes(s.Nodes, siblingAll))
}
// ChildrenFiltered gets the child elements of each element in the 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), compileMatcher(selector))
}
// ChildrenMatcher gets the child elements of each element in the Selection,
// filtered by the specified matcher. It returns a new
// Selection object containing these elements.
func (s *Selection) ChildrenMatcher(m Matcher) *Selection {
return filterAndPush(s, getChildrenNodes(s.Nodes, siblingAll), m)
}
// Parent gets the parent of each element in the Selection. It returns a
// new Selection object containing the matched elements.
func (s *Selection) Parent() *Selection {
return pushStack(s, getParentNodes(s.Nodes))
}
// 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), compileMatcher(selector))
}
// ParentMatcher gets the parent of each element in the Selection filtered by a
// matcher. It returns a new Selection object containing the matched elements.
func (s *Selection) ParentMatcher(m Matcher) *Selection {
return filterAndPush(s, getParentNodes(s.Nodes), m)
}
// Closest gets the first element that matches the selector by testing the
// element itself and traversing up through its ancestors in the DOM tree.
func (s *Selection) Closest(selector string) *Selection {
cs := compileMatcher(selector)
return s.ClosestMatcher(cs)
}
// ClosestMatcher gets the first element that matches the matcher by testing the
// element itself and traversing up through its ancestors in the DOM tree.
func (s *Selection) ClosestMatcher(m Matcher) *Selection {
return pushStack(s, mapNodes(s.Nodes, func(i int, n *html.Node) []*html.Node {
// For each node in the selection, test the node itself, then each parent
// until a match is found.
for ; n != nil; n = n.Parent {
if m.Match(n) {
return []*html.Node{n}
}
}
return nil
}))
}
// 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 set[n] {
return []*html.Node{n}
}
}
return nil
}))
}
// ClosestSelection gets the first element that matches one of the nodes in the
// Selection by testing the element itself and traversing up through its ancestors
// in the DOM tree.
func (s *Selection) ClosestSelection(sel *Selection) *Selection {
if sel == nil {
return pushStack(s, nil)
}
return s.ClosestNodes(sel.Nodes...)
}
// Parents gets the ancestors of each element in the current Selection. It
// returns a new Selection object with the matched elements.
func (s *Selection) Parents() *Selection {
return pushStack(s, getParentsNodes(s.Nodes, nil, nil))
}
// ParentsFiltered gets the ancestors of each element in the current
// Selection. It returns a new Selection object with the matched elements.
func (s *Selection) ParentsFiltered(selector string) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), compileMatcher(selector))
}
// ParentsMatcher gets the ancestors of each element in the current
// Selection. It returns a new Selection object with the matched elements.
func (s *Selection) ParentsMatcher(m Matcher) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nil), m)
}
// ParentsUntil gets the ancestors of each element in the Selection, up to but
// not including the element matched by the selector. It returns a new Selection
// object containing the matched elements.
func (s *Selection) ParentsUntil(selector string) *Selection {
return pushStack(s, getParentsNodes(s.Nodes, compileMatcher(selector), nil))
}
// ParentsUntilMatcher gets the ancestors of each element in the Selection, up to but
// not including the element matched by the matcher. It returns a new Selection
// object containing the matched elements.
func (s *Selection) ParentsUntilMatcher(m Matcher) *Selection {
return pushStack(s, getParentsNodes(s.Nodes, m, nil))
}
// ParentsUntilSelection gets the ancestors of each element in the Selection,
// up to but not including the elements in the specified Selection. It returns a
// new Selection object containing the matched elements.
func (s *Selection) ParentsUntilSelection(sel *Selection) *Selection {
if sel == nil {
return s.Parents()
}
return s.ParentsUntilNodes(sel.Nodes...)
}
// ParentsUntilNodes gets the ancestors of each element in the Selection,
// up to but not including the specified nodes. It returns a
// new Selection object containing the matched elements.
func (s *Selection) ParentsUntilNodes(nodes ...*html.Node) *Selection {
return pushStack(s, getParentsNodes(s.Nodes, nil, nodes))
}
// ParentsFilteredUntil is like ParentsUntil, with the option to filter the
// results based on a selector string. It returns a new Selection
// object containing the matched elements.
func (s *Selection) ParentsFilteredUntil(filterSelector, untilSelector string) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
}
// ParentsFilteredUntilMatcher is like ParentsUntilMatcher, with the option to filter the
// results based on a matcher. It returns a new Selection object containing the matched elements.
func (s *Selection) ParentsFilteredUntilMatcher(filter, until Matcher) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, until, nil), filter)
}
// ParentsFilteredUntilSelection is like ParentsUntilSelection, with the
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) ParentsFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
return s.ParentsMatcherUntilSelection(compileMatcher(filterSelector), sel)
}
// ParentsMatcherUntilSelection is like ParentsUntilSelection, with the
// option to filter the results based on a matcher. It returns a new
// Selection object containing the matched elements.
func (s *Selection) ParentsMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
if sel == nil {
return s.ParentsMatcher(filter)
}
return s.ParentsMatcherUntilNodes(filter, sel.Nodes...)
}
// ParentsFilteredUntilNodes is like ParentsUntilNodes, with the
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) ParentsFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), compileMatcher(filterSelector))
}
// ParentsMatcherUntilNodes is like ParentsUntilNodes, with the
// option to filter the results based on a matcher. It returns a new
// Selection object containing the matched elements.
func (s *Selection) ParentsMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
return filterAndPush(s, getParentsNodes(s.Nodes, nil, nodes), filter)
}
// Siblings gets the siblings of each element in the Selection. It returns
// a new Selection object containing the matched elements.
func (s *Selection) Siblings() *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil))
}
// SiblingsFiltered gets the siblings of each element in the Selection
// filtered by a selector. It returns a new Selection object containing the
// matched elements.
func (s *Selection) SiblingsFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), compileMatcher(selector))
}
// SiblingsMatcher gets the siblings of each element in the Selection
// filtered by a matcher. It returns a new Selection object containing the
// matched elements.
func (s *Selection) SiblingsMatcher(m Matcher) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingAll, nil, nil), m)
}
// Next gets the immediately following sibling of each element in the
// Selection. It returns a new Selection object containing the matched elements.
func (s *Selection) Next() *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil))
}
// NextFiltered gets the immediately following sibling of each element in the
// Selection filtered by a selector. It returns a new Selection object
// containing the matched elements.
func (s *Selection) NextFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), compileMatcher(selector))
}
// NextMatcher gets the immediately following sibling of each element in the
// Selection filtered by a matcher. It returns a new Selection object
// containing the matched elements.
func (s *Selection) NextMatcher(m Matcher) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNext, nil, nil), m)
}
// NextAll gets all the following siblings of each element in the
// Selection. It returns a new Selection object containing the matched elements.
func (s *Selection) NextAll() *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil))
}
// NextAllFiltered gets all the following siblings of each element in the
// Selection filtered by a selector. It returns a new Selection object
// containing the matched elements.
func (s *Selection) NextAllFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), compileMatcher(selector))
}
// NextAllMatcher gets all the following siblings of each element in the
// Selection filtered by a matcher. It returns a new Selection object
// containing the matched elements.
func (s *Selection) NextAllMatcher(m Matcher) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextAll, nil, nil), m)
}
// Prev gets the immediately preceding sibling of each element in the
// Selection. It returns a new Selection object containing the matched elements.
func (s *Selection) Prev() *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil))
}
// PrevFiltered gets the immediately preceding sibling of each element in the
// Selection filtered by a selector. It returns a new Selection object
// containing the matched elements.
func (s *Selection) PrevFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), compileMatcher(selector))
}
// PrevMatcher gets the immediately preceding sibling of each element in the
// Selection filtered by a matcher. It returns a new Selection object
// containing the matched elements.
func (s *Selection) PrevMatcher(m Matcher) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrev, nil, nil), m)
}
// PrevAll gets all the preceding siblings of each element in the
// Selection. It returns a new Selection object containing the matched elements.
func (s *Selection) PrevAll() *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil))
}
// PrevAllFiltered gets all the preceding siblings of each element in the
// Selection filtered by a selector. It returns a new Selection object
// containing the matched elements.
func (s *Selection) PrevAllFiltered(selector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), compileMatcher(selector))
}
// PrevAllMatcher gets all the preceding siblings of each element in the
// Selection filtered by a matcher. It returns a new Selection object
// containing the matched elements.
func (s *Selection) PrevAllMatcher(m Matcher) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevAll, nil, nil), m)
}
// NextUntil gets all following siblings of each element up to but not
// including the element matched by the selector. It returns a new Selection
// object containing the matched elements.
func (s *Selection) NextUntil(selector string) *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
compileMatcher(selector), nil))
}
// NextUntilMatcher gets all following siblings of each element up to but not
// including the element matched by the matcher. It returns a new Selection
// object containing the matched elements.
func (s *Selection) NextUntilMatcher(m Matcher) *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
m, nil))
}
// NextUntilSelection gets all following siblings of each element up to but not
// including the element matched by the Selection. It returns a new Selection
// object containing the matched elements.
func (s *Selection) NextUntilSelection(sel *Selection) *Selection {
if sel == nil {
return s.NextAll()
}
return s.NextUntilNodes(sel.Nodes...)
}
// NextUntilNodes gets all following siblings of each element up to but not
// including the element matched by the nodes. It returns a new Selection
// object containing the matched elements.
func (s *Selection) NextUntilNodes(nodes ...*html.Node) *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingNextUntil,
nil, nodes))
}
// PrevUntil gets all preceding siblings of each element up to but not
// including the element matched by the selector. It returns a new Selection
// object containing the matched elements.
func (s *Selection) PrevUntil(selector string) *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
compileMatcher(selector), nil))
}
// PrevUntilMatcher gets all preceding siblings of each element up to but not
// including the element matched by the matcher. It returns a new Selection
// object containing the matched elements.
func (s *Selection) PrevUntilMatcher(m Matcher) *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
m, nil))
}
// PrevUntilSelection gets all preceding siblings of each element up to but not
// including the element matched by the Selection. It returns a new Selection
// object containing the matched elements.
func (s *Selection) PrevUntilSelection(sel *Selection) *Selection {
if sel == nil {
return s.PrevAll()
}
return s.PrevUntilNodes(sel.Nodes...)
}
// PrevUntilNodes gets all preceding siblings of each element up to but not
// including the element matched by the nodes. It returns a new Selection
// object containing the matched elements.
func (s *Selection) PrevUntilNodes(nodes ...*html.Node) *Selection {
return pushStack(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
nil, nodes))
}
// NextFilteredUntil is like NextUntil, with the option to filter
// the results based on a selector string.
// It returns a new Selection object containing the matched elements.
func (s *Selection) NextFilteredUntil(filterSelector, untilSelector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
}
// NextFilteredUntilMatcher is like NextUntilMatcher, with the option to filter
// the results based on a matcher.
// It returns a new Selection object containing the matched elements.
func (s *Selection) NextFilteredUntilMatcher(filter, until Matcher) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
until, nil), filter)
}
// NextFilteredUntilSelection is like NextUntilSelection, with the
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) NextFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
return s.NextMatcherUntilSelection(compileMatcher(filterSelector), sel)
}
// NextMatcherUntilSelection is like NextUntilSelection, with the
// option to filter the results based on a matcher. It returns a new
// Selection object containing the matched elements.
func (s *Selection) NextMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
if sel == nil {
return s.NextMatcher(filter)
}
return s.NextMatcherUntilNodes(filter, sel.Nodes...)
}
// NextFilteredUntilNodes is like NextUntilNodes, with the
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) NextFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
nil, nodes), compileMatcher(filterSelector))
}
// NextMatcherUntilNodes is like NextUntilNodes, with the
// option to filter the results based on a matcher. It returns a new
// Selection object containing the matched elements.
func (s *Selection) NextMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingNextUntil,
nil, nodes), filter)
}
// PrevFilteredUntil is like PrevUntil, with the option to filter
// the results based on a selector string.
// It returns a new Selection object containing the matched elements.
func (s *Selection) PrevFilteredUntil(filterSelector, untilSelector string) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
compileMatcher(untilSelector), nil), compileMatcher(filterSelector))
}
// PrevFilteredUntilMatcher is like PrevUntilMatcher, with the option to filter
// the results based on a matcher.
// It returns a new Selection object containing the matched elements.
func (s *Selection) PrevFilteredUntilMatcher(filter, until Matcher) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
until, nil), filter)
}
// PrevFilteredUntilSelection is like PrevUntilSelection, with the
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) PrevFilteredUntilSelection(filterSelector string, sel *Selection) *Selection {
return s.PrevMatcherUntilSelection(compileMatcher(filterSelector), sel)
}
// PrevMatcherUntilSelection is like PrevUntilSelection, with the
// option to filter the results based on a matcher. It returns a new
// Selection object containing the matched elements.
func (s *Selection) PrevMatcherUntilSelection(filter Matcher, sel *Selection) *Selection {
if sel == nil {
return s.PrevMatcher(filter)
}
return s.PrevMatcherUntilNodes(filter, sel.Nodes...)
}
// PrevFilteredUntilNodes is like PrevUntilNodes, with the
// option to filter the results based on a selector string. It returns a new
// Selection object containing the matched elements.
func (s *Selection) PrevFilteredUntilNodes(filterSelector string, nodes ...*html.Node) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
nil, nodes), compileMatcher(filterSelector))
}
// PrevMatcherUntilNodes is like PrevUntilNodes, with the
// option to filter the results based on a matcher. It returns a new
// Selection object containing the matched elements.
func (s *Selection) PrevMatcherUntilNodes(filter Matcher, nodes ...*html.Node) *Selection {
return filterAndPush(s, getSiblingNodes(s.Nodes, siblingPrevUntil,
nil, nodes), filter)
}
// Filter and push filters the nodes based on a matcher, and pushes the results
// on the stack, with the srcSel as previous selection.
func filterAndPush(srcSel *Selection, nodes []*html.Node, m Matcher) *Selection {
// Create a temporary Selection with the specified nodes to filter using winnow
sel := &Selection{nodes, srcSel.document, nil}
// Filter based on matcher and push on stack
return pushStack(srcSel, winnow(sel, m, true))
}
// Internal implementation of Find that return raw nodes.
func findWithMatcher(nodes []*html.Node, m Matcher) []*html.Node {
// Map nodes to find the matches within the children of each node
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
// Go down one level, becausejQuery's Find selects only within descendants
for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.ElementNode {
result = append(result, m.MatchAll(c)...)
}
}
return
})
}
// Internal implementation to get all parent nodes, stopping at the specified
// node (or nil if no stop).
func getParentsNodes(nodes []*html.Node, stopm Matcher, stopNodes []*html.Node) []*html.Node {
return mapNodes(nodes, func(i int, n *html.Node) (result []*html.Node) {
for p := n.Parent; p != nil; p = p.Parent {
sel := newSingleSelection(p, nil)
if stopm != nil {
if sel.IsMatcher(stopm) {
break
}
} else if len(stopNodes) > 0 {
if sel.IsNodes(stopNodes...) {
break
}
}
if p.Type == html.ElementNode {
result = append(result, p)
}
}
return
})
}
// Internal implementation of sibling nodes that return a raw slice of matches.
func getSiblingNodes(nodes []*html.Node, st siblingType, untilm Matcher, untilNodes []*html.Node) []*html.Node {
var f func(*html.Node) bool
// If the requested siblings are ...Until, create the test function to
// determine if the until condition is reached (returns true if it is)
if st == siblingNextUntil || st == siblingPrevUntil {
f = func(n *html.Node) bool {
if untilm != nil {
// Matcher-based condition
sel := newSingleSelection(n, nil)
return sel.IsMatcher(untilm)
} else if len(untilNodes) > 0 {
// Nodes-based condition
sel := newSingleSelection(n, nil)
return sel.IsNodes(untilNodes...)
}
return false
}
}
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
return getChildrenWithSiblingType(n.Parent, st, n, f)
})
}
// Gets the children nodes of each node in the specified slice of nodes,
// based on the sibling type request.
func getChildrenNodes(nodes []*html.Node, st siblingType) []*html.Node {
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
return getChildrenWithSiblingType(n, st, nil, nil)
})
}
// Gets the children of the specified parent, based on the requested sibling
// type, skipping a specified node if required.
func getChildrenWithSiblingType(parent *html.Node, st siblingType, skipNode *html.Node,
untilFunc func(*html.Node) bool) (result []*html.Node) {
// Create the iterator function
var iter = func(cur *html.Node) (ret *html.Node) {
// Based on the sibling type requested, iterate the right way
for {
switch st {
case siblingAll, siblingAllIncludingNonElements:
if cur == nil {
// First iteration, start with first child of parent
// Skip node if required
if ret = parent.FirstChild; ret == skipNode && skipNode != nil {
ret = skipNode.NextSibling
}
} else {
// Skip node if required
if ret = cur.NextSibling; ret == skipNode && skipNode != nil {
ret = skipNode.NextSibling
}
}
case siblingPrev, siblingPrevAll, siblingPrevUntil:
if cur == nil {
// Start with previous sibling of the skip node
ret = skipNode.PrevSibling
} else {
ret = cur.PrevSibling
}
case siblingNext, siblingNextAll, siblingNextUntil:
if cur == nil {
// Start with next sibling of the skip node
ret = skipNode.NextSibling
} else {
ret = cur.NextSibling
}
default:
panic("Invalid sibling type.")
}
if ret == nil || ret.Type == html.ElementNode || st == siblingAllIncludingNonElements {
return
}
// Not a valid node, try again from this one
cur = ret
}
}
for c := iter(nil); c != nil; c = iter(c) {
// If this is an ...Until case, test before append (returns true
// if the until condition is reached)
if st == siblingNextUntil || st == siblingPrevUntil {
if untilFunc(c) {
return
}
}
result = append(result, c)
if st == siblingNext || st == siblingPrev {
// Only one node was requested (immediate next or previous), so exit
return
}
}
return
}
// Internal implementation of parent nodes that return a raw slice of Nodes.
func getParentNodes(nodes []*html.Node) []*html.Node {
return mapNodes(nodes, func(i int, n *html.Node) []*html.Node {
if n.Parent != nil && n.Parent.Type == html.ElementNode {
return []*html.Node{n.Parent}
}
return nil
})
}
// Internal map function used by many traversing methods. Takes the source nodes
// to iterate on and the mapping function that returns an array of nodes.
// 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, set)
}
}
return result
}

793
vendor/github.com/PuerkitoBio/goquery/traversal_test.go generated vendored Normal file
View file

@ -0,0 +1,793 @@
package goquery
import (
"strings"
"testing"
)
func TestFind(t *testing.T) {
sel := Doc().Find("div.row-fluid")
assertLength(t, sel.Nodes, 9)
}
func TestFindRollback(t *testing.T) {
sel := Doc().Find("div.row-fluid")
sel2 := sel.Find("a").End()
assertEqual(t, sel, sel2)
}
func TestFindNotSelf(t *testing.T) {
sel := Doc().Find("h1").Find("h1")
assertLength(t, sel.Nodes, 0)
}
func TestFindInvalid(t *testing.T) {
sel := Doc().Find(":+ ^")
assertLength(t, sel.Nodes, 0)
}
func TestFindBig(t *testing.T) {
doc := DocW()
sel := doc.Find("li")
assertLength(t, sel.Nodes, 373)
sel2 := doc.Find("span")
assertLength(t, sel2.Nodes, 448)
sel3 := sel.FindSelection(sel2)
assertLength(t, sel3.Nodes, 248)
}
func TestChainedFind(t *testing.T) {
sel := Doc().Find("div.hero-unit").Find(".row-fluid")
assertLength(t, sel.Nodes, 4)
}
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)
}
func TestChildrenRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Children().End()
assertEqual(t, sel, sel2)
}
func TestContents(t *testing.T) {
sel := Doc().Find(".pvk-content").Contents()
assertLength(t, sel.Nodes, 13)
}
func TestContentsRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.Contents().End()
assertEqual(t, sel, sel2)
}
func TestChildrenFiltered(t *testing.T) {
sel := Doc().Find(".pvk-content").ChildrenFiltered(".hero-unit")
assertLength(t, sel.Nodes, 1)
}
func TestChildrenFilteredInvalid(t *testing.T) {
sel := Doc().Find(".pvk-content").ChildrenFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestChildrenFilteredRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.ChildrenFiltered(".hero-unit").End()
assertEqual(t, sel, sel2)
}
func TestContentsFiltered(t *testing.T) {
sel := Doc().Find(".pvk-content").ContentsFiltered(".hero-unit")
assertLength(t, sel.Nodes, 1)
}
func TestContentsFilteredInvalid(t *testing.T) {
sel := Doc().Find(".pvk-content").ContentsFiltered("~")
assertLength(t, sel.Nodes, 0)
}
func TestContentsFilteredRollback(t *testing.T) {
sel := Doc().Find(".pvk-content")
sel2 := sel.ContentsFiltered(".hero-unit").End()
assertEqual(t, sel, sel2)
}
func TestChildrenFilteredNone(t *testing.T) {
sel := Doc().Find(".pvk-content").ChildrenFiltered("a.btn")
assertLength(t, sel.Nodes, 0)
}
func TestParent(t *testing.T) {
sel := Doc().Find(".container-fluid").Parent()
assertLength(t, sel.Nodes, 3)
}
func TestParentRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.Parent().End()
assertEqual(t, sel, sel2)
}
func TestParentBody(t *testing.T) {
sel := Doc().Find("body").Parent()
assertLength(t, sel.Nodes, 1)
}
func TestParentFiltered(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentFiltered(".hero-unit")
assertLength(t, sel.Nodes, 1)
assertClass(t, sel, "hero-unit")
}
func TestParentFilteredInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestParentFilteredRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ParentFiltered(".hero-unit").End()
assertEqual(t, sel, sel2)
}
func TestParents(t *testing.T) {
sel := Doc().Find(".container-fluid").Parents()
assertLength(t, sel.Nodes, 8)
}
func TestParentsOrder(t *testing.T) {
sel := Doc().Find("#cf2").Parents()
assertLength(t, sel.Nodes, 6)
assertSelectionIs(t, sel, ".hero-unit", ".pvk-content", "div.row-fluid", "#cf1", "body", "html")
}
func TestParentsRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.Parents().End()
assertEqual(t, sel, sel2)
}
func TestParentsFiltered(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsFiltered("body")
assertLength(t, sel.Nodes, 1)
}
func TestParentsFilteredInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestParentsFilteredRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ParentsFiltered("body").End()
assertEqual(t, sel, sel2)
}
func TestParentsUntil(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsUntil("body")
assertLength(t, sel.Nodes, 6)
}
func TestParentsUntilInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsUntil("")
assertLength(t, sel.Nodes, 8)
}
func TestParentsUntilRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ParentsUntil("body").End()
assertEqual(t, sel, sel2)
}
func TestParentsUntilSelection(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".pvk-content")
sel = sel.ParentsUntilSelection(sel2)
assertLength(t, sel.Nodes, 3)
}
func TestParentsUntilSelectionRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".pvk-content")
sel2 = sel.ParentsUntilSelection(sel2).End()
assertEqual(t, sel, sel2)
}
func TestParentsUntilNodes(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".pvk-content, .hero-unit")
sel = sel.ParentsUntilNodes(sel2.Nodes...)
assertLength(t, sel.Nodes, 2)
}
func TestParentsUntilNodesRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".pvk-content, .hero-unit")
sel2 = sel.ParentsUntilNodes(sel2.Nodes...).End()
assertEqual(t, sel, sel2)
}
func TestParentsFilteredUntil(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsFilteredUntil(".pvk-content", "body")
assertLength(t, sel.Nodes, 2)
}
func TestParentsFilteredUntilInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").ParentsFilteredUntil("", "")
assertLength(t, sel.Nodes, 0)
}
func TestParentsFilteredUntilRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ParentsFilteredUntil(".pvk-content", "body").End()
assertEqual(t, sel, sel2)
}
func TestParentsFilteredUntilSelection(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".row-fluid")
sel = sel.ParentsFilteredUntilSelection("div", sel2)
assertLength(t, sel.Nodes, 3)
}
func TestParentsFilteredUntilSelectionRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".row-fluid")
sel2 = sel.ParentsFilteredUntilSelection("div", sel2).End()
assertEqual(t, sel, sel2)
}
func TestParentsFilteredUntilNodes(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".row-fluid")
sel = sel.ParentsFilteredUntilNodes("body", sel2.Nodes...)
assertLength(t, sel.Nodes, 1)
}
func TestParentsFilteredUntilNodesRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := Doc().Find(".row-fluid")
sel2 = sel.ParentsFilteredUntilNodes("body", sel2.Nodes...).End()
assertEqual(t, sel, sel2)
}
func TestSiblings(t *testing.T) {
sel := Doc().Find("h1").Siblings()
assertLength(t, sel.Nodes, 1)
}
func TestSiblingsRollback(t *testing.T) {
sel := Doc().Find("h1")
sel2 := sel.Siblings().End()
assertEqual(t, sel, sel2)
}
func TestSiblings2(t *testing.T) {
sel := Doc().Find(".pvk-gutter").Siblings()
assertLength(t, sel.Nodes, 9)
}
func TestSiblings3(t *testing.T) {
sel := Doc().Find("body>.container-fluid").Siblings()
assertLength(t, sel.Nodes, 0)
}
func TestSiblingsFiltered(t *testing.T) {
sel := Doc().Find(".pvk-gutter").SiblingsFiltered(".pvk-content")
assertLength(t, sel.Nodes, 3)
}
func TestSiblingsFilteredInvalid(t *testing.T) {
sel := Doc().Find(".pvk-gutter").SiblingsFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestSiblingsFilteredRollback(t *testing.T) {
sel := Doc().Find(".pvk-gutter")
sel2 := sel.SiblingsFiltered(".pvk-content").End()
assertEqual(t, sel, sel2)
}
func TestNext(t *testing.T) {
sel := Doc().Find("h1").Next()
assertLength(t, sel.Nodes, 1)
}
func TestNextRollback(t *testing.T) {
sel := Doc().Find("h1")
sel2 := sel.Next().End()
assertEqual(t, sel, sel2)
}
func TestNext2(t *testing.T) {
sel := Doc().Find(".close").Next()
assertLength(t, sel.Nodes, 1)
}
func TestNextNone(t *testing.T) {
sel := Doc().Find("small").Next()
assertLength(t, sel.Nodes, 0)
}
func TestNextFiltered(t *testing.T) {
sel := Doc().Find(".container-fluid").NextFiltered("div")
assertLength(t, sel.Nodes, 2)
}
func TestNextFilteredInvalid(t *testing.T) {
sel := Doc().Find(".container-fluid").NextFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestNextFilteredRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.NextFiltered("div").End()
assertEqual(t, sel, sel2)
}
func TestNextFiltered2(t *testing.T) {
sel := Doc().Find(".container-fluid").NextFiltered("[ng-view]")
assertLength(t, sel.Nodes, 1)
}
func TestPrev(t *testing.T) {
sel := Doc().Find(".red").Prev()
assertLength(t, sel.Nodes, 1)
assertClass(t, sel, "green")
}
func TestPrevRollback(t *testing.T) {
sel := Doc().Find(".red")
sel2 := sel.Prev().End()
assertEqual(t, sel, sel2)
}
func TestPrev2(t *testing.T) {
sel := Doc().Find(".row-fluid").Prev()
assertLength(t, sel.Nodes, 5)
}
func TestPrevNone(t *testing.T) {
sel := Doc().Find("h2").Prev()
assertLength(t, sel.Nodes, 0)
}
func TestPrevFiltered(t *testing.T) {
sel := Doc().Find(".row-fluid").PrevFiltered(".row-fluid")
assertLength(t, sel.Nodes, 5)
}
func TestPrevFilteredInvalid(t *testing.T) {
sel := Doc().Find(".row-fluid").PrevFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestPrevFilteredRollback(t *testing.T) {
sel := Doc().Find(".row-fluid")
sel2 := sel.PrevFiltered(".row-fluid").End()
assertEqual(t, sel, sel2)
}
func TestNextAll(t *testing.T) {
sel := Doc().Find("#cf2 div:nth-child(1)").NextAll()
assertLength(t, sel.Nodes, 3)
}
func TestNextAllRollback(t *testing.T) {
sel := Doc().Find("#cf2 div:nth-child(1)")
sel2 := sel.NextAll().End()
assertEqual(t, sel, sel2)
}
func TestNextAll2(t *testing.T) {
sel := Doc().Find("div[ng-cloak]").NextAll()
assertLength(t, sel.Nodes, 1)
}
func TestNextAllNone(t *testing.T) {
sel := Doc().Find(".footer").NextAll()
assertLength(t, sel.Nodes, 0)
}
func TestNextAllFiltered(t *testing.T) {
sel := Doc().Find("#cf2 .row-fluid").NextAllFiltered("[ng-cloak]")
assertLength(t, sel.Nodes, 2)
}
func TestNextAllFilteredInvalid(t *testing.T) {
sel := Doc().Find("#cf2 .row-fluid").NextAllFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestNextAllFilteredRollback(t *testing.T) {
sel := Doc().Find("#cf2 .row-fluid")
sel2 := sel.NextAllFiltered("[ng-cloak]").End()
assertEqual(t, sel, sel2)
}
func TestNextAllFiltered2(t *testing.T) {
sel := Doc().Find(".close").NextAllFiltered("h4")
assertLength(t, sel.Nodes, 1)
}
func TestPrevAll(t *testing.T) {
sel := Doc().Find("[ng-view]").PrevAll()
assertLength(t, sel.Nodes, 2)
}
func TestPrevAllOrder(t *testing.T) {
sel := Doc().Find("[ng-view]").PrevAll()
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel, "#cf4", "#cf3")
}
func TestPrevAllRollback(t *testing.T) {
sel := Doc().Find("[ng-view]")
sel2 := sel.PrevAll().End()
assertEqual(t, sel, sel2)
}
func TestPrevAll2(t *testing.T) {
sel := Doc().Find(".pvk-gutter").PrevAll()
assertLength(t, sel.Nodes, 6)
}
func TestPrevAllFiltered(t *testing.T) {
sel := Doc().Find(".pvk-gutter").PrevAllFiltered(".pvk-content")
assertLength(t, sel.Nodes, 3)
}
func TestPrevAllFilteredInvalid(t *testing.T) {
sel := Doc().Find(".pvk-gutter").PrevAllFiltered("")
assertLength(t, sel.Nodes, 0)
}
func TestPrevAllFilteredRollback(t *testing.T) {
sel := Doc().Find(".pvk-gutter")
sel2 := sel.PrevAllFiltered(".pvk-content").End()
assertEqual(t, sel, sel2)
}
func TestNextUntil(t *testing.T) {
sel := Doc().Find(".alert a").NextUntil("p")
assertLength(t, sel.Nodes, 1)
assertSelectionIs(t, sel, "h4")
}
func TestNextUntilInvalid(t *testing.T) {
sel := Doc().Find(".alert a").NextUntil("")
assertLength(t, sel.Nodes, 2)
}
func TestNextUntil2(t *testing.T) {
sel := Doc().Find("#cf2-1").NextUntil("[ng-cloak]")
assertLength(t, sel.Nodes, 1)
assertSelectionIs(t, sel, "#cf2-2")
}
func TestNextUntilOrder(t *testing.T) {
sel := Doc().Find("#cf2-1").NextUntil("#cf2-4")
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel, "#cf2-2", "#cf2-3")
}
func TestNextUntilRollback(t *testing.T) {
sel := Doc().Find("#cf2-1")
sel2 := sel.PrevUntil("#cf2-4").End()
assertEqual(t, sel, sel2)
}
func TestNextUntilSelection(t *testing.T) {
sel := Doc2().Find("#n2")
sel2 := Doc2().Find("#n4")
sel2 = sel.NextUntilSelection(sel2)
assertLength(t, sel2.Nodes, 1)
assertSelectionIs(t, sel2, "#n3")
}
func TestNextUntilSelectionRollback(t *testing.T) {
sel := Doc2().Find("#n2")
sel2 := Doc2().Find("#n4")
sel2 = sel.NextUntilSelection(sel2).End()
assertEqual(t, sel, sel2)
}
func TestNextUntilNodes(t *testing.T) {
sel := Doc2().Find("#n2")
sel2 := Doc2().Find("#n5")
sel2 = sel.NextUntilNodes(sel2.Nodes...)
assertLength(t, sel2.Nodes, 2)
assertSelectionIs(t, sel2, "#n3", "#n4")
}
func TestNextUntilNodesRollback(t *testing.T) {
sel := Doc2().Find("#n2")
sel2 := Doc2().Find("#n5")
sel2 = sel.NextUntilNodes(sel2.Nodes...).End()
assertEqual(t, sel, sel2)
}
func TestPrevUntil(t *testing.T) {
sel := Doc().Find(".alert p").PrevUntil("a")
assertLength(t, sel.Nodes, 1)
assertSelectionIs(t, sel, "h4")
}
func TestPrevUntilInvalid(t *testing.T) {
sel := Doc().Find(".alert p").PrevUntil("")
assertLength(t, sel.Nodes, 2)
}
func TestPrevUntil2(t *testing.T) {
sel := Doc().Find("[ng-cloak]").PrevUntil(":not([ng-cloak])")
assertLength(t, sel.Nodes, 1)
assertSelectionIs(t, sel, "[ng-cloak]")
}
func TestPrevUntilOrder(t *testing.T) {
sel := Doc().Find("#cf2-4").PrevUntil("#cf2-1")
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel, "#cf2-3", "#cf2-2")
}
func TestPrevUntilRollback(t *testing.T) {
sel := Doc().Find("#cf2-4")
sel2 := sel.PrevUntil("#cf2-1").End()
assertEqual(t, sel, sel2)
}
func TestPrevUntilSelection(t *testing.T) {
sel := Doc2().Find("#n4")
sel2 := Doc2().Find("#n2")
sel2 = sel.PrevUntilSelection(sel2)
assertLength(t, sel2.Nodes, 1)
assertSelectionIs(t, sel2, "#n3")
}
func TestPrevUntilSelectionRollback(t *testing.T) {
sel := Doc2().Find("#n4")
sel2 := Doc2().Find("#n2")
sel2 = sel.PrevUntilSelection(sel2).End()
assertEqual(t, sel, sel2)
}
func TestPrevUntilNodes(t *testing.T) {
sel := Doc2().Find("#n5")
sel2 := Doc2().Find("#n2")
sel2 = sel.PrevUntilNodes(sel2.Nodes...)
assertLength(t, sel2.Nodes, 2)
assertSelectionIs(t, sel2, "#n4", "#n3")
}
func TestPrevUntilNodesRollback(t *testing.T) {
sel := Doc2().Find("#n5")
sel2 := Doc2().Find("#n2")
sel2 = sel.PrevUntilNodes(sel2.Nodes...).End()
assertEqual(t, sel, sel2)
}
func TestNextFilteredUntil(t *testing.T) {
sel := Doc2().Find(".two").NextFilteredUntil(".even", ".six")
assertLength(t, sel.Nodes, 4)
assertSelectionIs(t, sel, "#n3", "#n5", "#nf3", "#nf5")
}
func TestNextFilteredUntilInvalid(t *testing.T) {
sel := Doc2().Find(".two").NextFilteredUntil("", "")
assertLength(t, sel.Nodes, 0)
}
func TestNextFilteredUntilRollback(t *testing.T) {
sel := Doc2().Find(".two")
sel2 := sel.NextFilteredUntil(".even", ".six").End()
assertEqual(t, sel, sel2)
}
func TestNextFilteredUntilSelection(t *testing.T) {
sel := Doc2().Find(".even")
sel2 := Doc2().Find(".five")
sel = sel.NextFilteredUntilSelection(".even", sel2)
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel, "#n3", "#nf3")
}
func TestNextFilteredUntilSelectionRollback(t *testing.T) {
sel := Doc2().Find(".even")
sel2 := Doc2().Find(".five")
sel3 := sel.NextFilteredUntilSelection(".even", sel2).End()
assertEqual(t, sel, sel3)
}
func TestNextFilteredUntilNodes(t *testing.T) {
sel := Doc2().Find(".even")
sel2 := Doc2().Find(".four")
sel = sel.NextFilteredUntilNodes(".odd", sel2.Nodes...)
assertLength(t, sel.Nodes, 4)
assertSelectionIs(t, sel, "#n2", "#n6", "#nf2", "#nf6")
}
func TestNextFilteredUntilNodesRollback(t *testing.T) {
sel := Doc2().Find(".even")
sel2 := Doc2().Find(".four")
sel3 := sel.NextFilteredUntilNodes(".odd", sel2.Nodes...).End()
assertEqual(t, sel, sel3)
}
func TestPrevFilteredUntil(t *testing.T) {
sel := Doc2().Find(".five").PrevFilteredUntil(".odd", ".one")
assertLength(t, sel.Nodes, 4)
assertSelectionIs(t, sel, "#n4", "#n2", "#nf4", "#nf2")
}
func TestPrevFilteredUntilInvalid(t *testing.T) {
sel := Doc2().Find(".five").PrevFilteredUntil("", "")
assertLength(t, sel.Nodes, 0)
}
func TestPrevFilteredUntilRollback(t *testing.T) {
sel := Doc2().Find(".four")
sel2 := sel.PrevFilteredUntil(".odd", ".one").End()
assertEqual(t, sel, sel2)
}
func TestPrevFilteredUntilSelection(t *testing.T) {
sel := Doc2().Find(".odd")
sel2 := Doc2().Find(".two")
sel = sel.PrevFilteredUntilSelection(".odd", sel2)
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel, "#n4", "#nf4")
}
func TestPrevFilteredUntilSelectionRollback(t *testing.T) {
sel := Doc2().Find(".even")
sel2 := Doc2().Find(".five")
sel3 := sel.PrevFilteredUntilSelection(".even", sel2).End()
assertEqual(t, sel, sel3)
}
func TestPrevFilteredUntilNodes(t *testing.T) {
sel := Doc2().Find(".even")
sel2 := Doc2().Find(".four")
sel = sel.PrevFilteredUntilNodes(".odd", sel2.Nodes...)
assertLength(t, sel.Nodes, 2)
assertSelectionIs(t, sel, "#n2", "#nf2")
}
func TestPrevFilteredUntilNodesRollback(t *testing.T) {
sel := Doc2().Find(".even")
sel2 := Doc2().Find(".four")
sel3 := sel.PrevFilteredUntilNodes(".odd", sel2.Nodes...).End()
assertEqual(t, sel, sel3)
}
func TestClosestItself(t *testing.T) {
sel := Doc2().Find(".three")
sel2 := sel.Closest(".row")
assertLength(t, sel2.Nodes, sel.Length())
assertSelectionIs(t, sel2, "#n3", "#nf3")
}
func TestClosestNoDupes(t *testing.T) {
sel := Doc().Find(".span12")
sel2 := sel.Closest(".pvk-content")
assertLength(t, sel2.Nodes, 1)
assertClass(t, sel2, "pvk-content")
}
func TestClosestNone(t *testing.T) {
sel := Doc().Find("h4")
sel2 := sel.Closest("a")
assertLength(t, sel2.Nodes, 0)
}
func TestClosestInvalid(t *testing.T) {
sel := Doc().Find("h4")
sel2 := sel.Closest("")
assertLength(t, sel2.Nodes, 0)
}
func TestClosestMany(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.Closest(".pvk-content")
assertLength(t, sel2.Nodes, 2)
assertSelectionIs(t, sel2, "#pc1", "#pc2")
}
func TestClosestRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.Closest(".pvk-content").End()
assertEqual(t, sel, sel2)
}
func TestClosestSelectionItself(t *testing.T) {
sel := Doc2().Find(".three")
sel2 := sel.ClosestSelection(Doc2().Find(".row"))
assertLength(t, sel2.Nodes, sel.Length())
}
func TestClosestSelectionNoDupes(t *testing.T) {
sel := Doc().Find(".span12")
sel2 := sel.ClosestSelection(Doc().Find(".pvk-content"))
assertLength(t, sel2.Nodes, 1)
assertClass(t, sel2, "pvk-content")
}
func TestClosestSelectionNone(t *testing.T) {
sel := Doc().Find("h4")
sel2 := sel.ClosestSelection(Doc().Find("a"))
assertLength(t, sel2.Nodes, 0)
}
func TestClosestSelectionMany(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ClosestSelection(Doc().Find(".pvk-content"))
assertLength(t, sel2.Nodes, 2)
assertSelectionIs(t, sel2, "#pc1", "#pc2")
}
func TestClosestSelectionRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ClosestSelection(Doc().Find(".pvk-content")).End()
assertEqual(t, sel, sel2)
}
func TestClosestNodesItself(t *testing.T) {
sel := Doc2().Find(".three")
sel2 := sel.ClosestNodes(Doc2().Find(".row").Nodes...)
assertLength(t, sel2.Nodes, sel.Length())
}
func TestClosestNodesNoDupes(t *testing.T) {
sel := Doc().Find(".span12")
sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...)
assertLength(t, sel2.Nodes, 1)
assertClass(t, sel2, "pvk-content")
}
func TestClosestNodesNone(t *testing.T) {
sel := Doc().Find("h4")
sel2 := sel.ClosestNodes(Doc().Find("a").Nodes...)
assertLength(t, sel2.Nodes, 0)
}
func TestClosestNodesMany(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...)
assertLength(t, sel2.Nodes, 2)
assertSelectionIs(t, sel2, "#pc1", "#pc2")
}
func TestClosestNodesRollback(t *testing.T) {
sel := Doc().Find(".container-fluid")
sel2 := sel.ClosestNodes(Doc().Find(".pvk-content").Nodes...).End()
assertEqual(t, sel, sel2)
}
func TestIssue26(t *testing.T) {
img1 := `<img src="assets/images/gallery/thumb-1.jpg" alt="150x150" />`
img2 := `<img alt="150x150" src="assets/images/gallery/thumb-1.jpg" />`
cases := []struct {
s string
l int
}{
{s: img1 + img2, l: 2},
{s: img1, l: 1},
{s: img2, l: 1},
}
for _, c := range cases {
doc, err := NewDocumentFromReader(strings.NewReader(c.s))
if err != nil {
t.Fatal(err)
}
sel := doc.Find("img[src]")
assertLength(t, sel.Nodes, c.l)
}
}

141
vendor/github.com/PuerkitoBio/goquery/type.go generated vendored Normal file
View file

@ -0,0 +1,141 @@
package goquery
import (
"errors"
"io"
"net/http"
"net/url"
"github.com/andybalholm/cascadia"
"golang.org/x/net/html"
)
// Document represents an HTML document to be manipulated. Unlike jQuery, which
// is loaded as part of a DOM document, and thus acts upon its containing
// document, GoQuery doesn't know which HTML document to act upon. So it needs
// to be told, and that's what the Document class is for. It holds the root
// document node to manipulate, and can make selections on this document.
type Document struct {
*Selection
Url *url.URL
rootNode *html.Node
}
// NewDocumentFromNode is a Document constructor that takes a root html Node
// as argument.
func NewDocumentFromNode(root *html.Node) *Document {
return newDocument(root, nil)
}
// 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)
if e != nil {
return nil, e
}
return NewDocumentFromResponse(res)
}
// 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, 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)
if e != nil {
return nil, e
}
return newDocument(root, nil), nil
}
// 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")
}
defer res.Body.Close()
if res.Request == nil {
return nil, errors.New("Response.Request is nil")
}
// Parse the HTML into nodes
root, e := html.Parse(res.Body)
if e != nil {
return nil, e
}
// Create and fill the document
return newDocument(root, res.Request.URL), nil
}
// CloneDocument creates a deep-clone of a document.
func CloneDocument(doc *Document) *Document {
return newDocument(cloneNode(doc.rootNode), doc.Url)
}
// Private constructor, make sure all fields are correctly filled.
func newDocument(root *html.Node, url *url.URL) *Document {
// Create and fill the document
d := &Document{nil, url, root}
d.Selection = newSingleSelection(root, d)
return d
}
// Selection represents a collection of nodes matching some criteria. The
// initial Selection can be created by using Document.Find, and then
// manipulated using the jQuery-like chainable syntax and methods.
type Selection struct {
Nodes []*html.Node
document *Document
prevSel *Selection
}
// Helper constructor to create an empty selection
func newEmptySelection(doc *Document) *Selection {
return &Selection{nil, doc, nil}
}
// Helper constructor to create a selection of only one node
func newSingleSelection(node *html.Node, doc *Document) *Selection {
return &Selection{[]*html.Node{node}, doc, nil}
}
// Matcher is an interface that defines the methods to match
// HTML nodes against a compiled selector string. Cascadia's
// Selector implements this interface.
type Matcher interface {
Match(*html.Node) bool
MatchAll(*html.Node) []*html.Node
Filter([]*html.Node) []*html.Node
}
// 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 }

202
vendor/github.com/PuerkitoBio/goquery/type_test.go generated vendored Normal file
View file

@ -0,0 +1,202 @@
package goquery
import (
"bytes"
"fmt"
"os"
"strings"
"testing"
"golang.org/x/net/html"
)
// Test helper functions and members
var doc *Document
var doc2 *Document
var doc3 *Document
var docB *Document
var docW *Document
func Doc() *Document {
if doc == nil {
doc = loadDoc("page.html")
}
return 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 DocW() *Document {
if docW == nil {
docW = loadDoc("gowiki.html")
}
return docW
}
func assertLength(t *testing.T, nodes []*html.Node, length int) {
if len(nodes) != length {
t.Errorf("Expected %d nodes, found %d.", length, len(nodes))
for i, n := range nodes {
t.Logf("Node %d: %+v.", i, n)
}
}
}
func assertClass(t *testing.T, sel *Selection, class string) {
if !sel.HasClass(class) {
t.Errorf("Expected node to have class %s, found %+v.", class, sel.Get(0))
}
}
func assertPanic(t *testing.T) {
if e := recover(); e == nil {
t.Error("Expected a panic.")
}
}
func assertEqual(t *testing.T, s1 *Selection, s2 *Selection) {
if s1 != s2 {
t.Error("Expected selection objects to be the same.")
}
}
func assertSelectionIs(t *testing.T, sel *Selection, is ...string) {
for i := 0; i < sel.Length(); i++ {
if !sel.Eq(i).Is(is[i]) {
t.Errorf("Expected node %d to be %s, found %+v", i, is[i], sel.Get(i))
}
}
}
func printSel(t *testing.T, sel *Selection) {
if testing.Verbose() {
h, err := sel.Html()
if err != nil {
t.Fatal(err)
}
t.Log(h)
}
}
func loadDoc(page string) *Document {
var f *os.File
var e error
if f, e = os.Open(fmt.Sprintf("./testdata/%s", page)); e != nil {
panic(e.Error())
}
defer f.Close()
var node *html.Node
if node, e = html.Parse(f); e != nil {
panic(e.Error())
}
return NewDocumentFromNode(node)
}
func TestNewDocument(t *testing.T) {
if f, e := os.Open("./testdata/page.html"); e != nil {
t.Error(e.Error())
} else {
defer f.Close()
if node, e := html.Parse(f); e != nil {
t.Error(e.Error())
} else {
doc = NewDocumentFromNode(node)
}
}
}
func TestNewDocumentFromReader(t *testing.T) {
cases := []struct {
src string
err bool
sel string
cnt int
}{
0: {
src: `
<html>
<head>
<title>Test</title>
<body>
<h1>Hi</h1>
</body>
</html>`,
sel: "h1",
cnt: 1,
},
1: {
// Actually pretty hard to make html.Parse return an error
// based on content...
src: `<html><body><aef<eqf>>>qq></body></ht>`,
},
}
buf := bytes.NewBuffer(nil)
for i, c := range cases {
buf.Reset()
buf.WriteString(c.src)
d, e := NewDocumentFromReader(buf)
if (e != nil) != c.err {
if c.err {
t.Errorf("[%d] - expected error, got none", i)
} else {
t.Errorf("[%d] - expected no error, got %s", i, e)
}
}
if c.sel != "" {
s := d.Find(c.sel)
if s.Length() != c.cnt {
t.Errorf("[%d] - expected %d nodes, found %d", i, c.cnt, s.Length())
}
}
}
}
func TestNewDocumentFromResponseNil(t *testing.T) {
_, e := NewDocumentFromResponse(nil)
if e == nil {
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)
}

161
vendor/github.com/PuerkitoBio/goquery/utilities.go generated vendored Normal file
View file

@ -0,0 +1,161 @@
package goquery
import (
"bytes"
"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",
html.DocumentNode: "#document",
html.CommentNode: "#comment",
}
// NodeName returns the node name of the first element in the selection.
// It tries to behave in a similar way as the DOM's nodeName property
// (https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeName).
//
// Go's net/html package defines the following node types, listed with
// the corresponding returned value from this function:
//
// ErrorNode : #error
// TextNode : #text
// DocumentNode : #document
// ElementNode : the element's tag name
// CommentNode : #comment
// DoctypeNode : the name of the document type
//
func NodeName(s *Selection) string {
if s.Length() == 0 {
return ""
}
switch n := s.Get(0); n.Type {
case html.ElementNode, html.DoctypeNode:
return n.Data
default:
if n.Type >= 0 && int(n.Type) < len(nodeNames) {
return nodeNames[n.Type]
}
return ""
}
}
// OuterHtml returns the outer HTML rendering of the first item in
// the selection - that is, the HTML including the first element's
// tag and attributes.
//
// Unlike InnerHtml, this is a function and not a method on the Selection,
// because this is not a jQuery method (in javascript-land, this is
// a property provided by the DOM).
func OuterHtml(s *Selection) (string, error) {
var buf bytes.Buffer
if s.Length() == 0 {
return "", nil
}
n := s.Get(0)
if err := html.Render(&buf, n); err != nil {
return "", err
}
return buf.String(), nil
}
// Loop through all container nodes to search for the target node.
func sliceContains(container []*html.Node, contained *html.Node) bool {
for _, n := range container {
if nodeContains(n, contained) {
return true
}
}
return false
}
// Checks if the contained node is within the container node.
func nodeContains(container *html.Node, contained *html.Node) bool {
// Check if the parent of the contained node is the container node, traversing
// upward until the top is reached, or the container is found.
for contained = contained.Parent; contained != nil; contained = contained.Parent {
if container == contained {
return true
}
}
return false
}
// Checks if the target node is in the slice of nodes.
func isInSlice(slice []*html.Node, node *html.Node) bool {
return indexInSlice(slice, node) > -1
}
// Returns the index of the target node in the slice, or -1.
func indexInSlice(slice []*html.Node, node *html.Node) int {
if node != nil {
for i, n := range slice {
if n == node {
return i
}
}
}
return -1
}
// 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. 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 !targetSet[n] {
target = append(target, n)
targetSet[n] = true
}
}
return target
}
// Loop through a selection, returning only those nodes that pass the predicate
// function.
func grep(sel *Selection, predicate func(i int, s *Selection) bool) (result []*html.Node) {
for i, n := range sel.Nodes {
if predicate(i, newSingleSelection(n, sel.document)) {
result = append(result, n)
}
}
return result
}
// Creates a new Selection object based on the specified nodes, and keeps the
// source Selection object on the stack (linked list).
func pushStack(fromSel *Selection, nodes []*html.Node) *Selection {
result := &Selection{nodes, fromSel.document, fromSel}
return result
}

128
vendor/github.com/PuerkitoBio/goquery/utilities_test.go generated vendored Normal file
View file

@ -0,0 +1,128 @@
package goquery
import (
"reflect"
"sort"
"strings"
"testing"
"golang.org/x/net/html"
)
var allNodes = `<!doctype html>
<html>
<head>
<meta a="b">
</head>
<body>
<p><!-- this is a comment -->
This is some text.
</p>
<div></div>
<h1 class="header"></h1>
<h2 class="header"></h2>
</body>
</html>`
func TestNodeName(t *testing.T) {
doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
if err != nil {
t.Fatal(err)
}
n0 := doc.Nodes[0]
nDT := n0.FirstChild
sMeta := doc.Find("meta")
nMeta := sMeta.Get(0)
sP := doc.Find("p")
nP := sP.Get(0)
nComment := nP.FirstChild
nText := nComment.NextSibling
cases := []struct {
node *html.Node
typ html.NodeType
want string
}{
{n0, html.DocumentNode, nodeNames[html.DocumentNode]},
{nDT, html.DoctypeNode, "html"},
{nMeta, html.ElementNode, "meta"},
{nP, html.ElementNode, "p"},
{nComment, html.CommentNode, nodeNames[html.CommentNode]},
{nText, html.TextNode, nodeNames[html.TextNode]},
}
for i, c := range cases {
got := NodeName(newSingleSelection(c.node, doc))
if c.node.Type != c.typ {
t.Errorf("%d: want type %v, got %v", i, c.typ, c.node.Type)
}
if got != c.want {
t.Errorf("%d: want %q, got %q", i, c.want, got)
}
}
}
func TestNodeNameMultiSel(t *testing.T) {
doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
if err != nil {
t.Fatal(err)
}
in := []string{"p", "h1", "div"}
var out []string
doc.Find(strings.Join(in, ", ")).Each(func(i int, s *Selection) {
got := NodeName(s)
out = append(out, got)
})
sort.Strings(in)
sort.Strings(out)
if !reflect.DeepEqual(in, out) {
t.Errorf("want %v, got %v", in, out)
}
}
func TestOuterHtml(t *testing.T) {
doc, err := NewDocumentFromReader(strings.NewReader(allNodes))
if err != nil {
t.Fatal(err)
}
n0 := doc.Nodes[0]
nDT := n0.FirstChild
sMeta := doc.Find("meta")
sP := doc.Find("p")
nP := sP.Get(0)
nComment := nP.FirstChild
nText := nComment.NextSibling
sHeaders := doc.Find(".header")
cases := []struct {
node *html.Node
sel *Selection
want string
}{
{nDT, nil, "<!DOCTYPE html>"}, // render makes DOCTYPE all caps
{nil, sMeta, `<meta a="b"/>`}, // and auto-closes the meta
{nil, sP, `<p><!-- this is a comment -->
This is some text.
</p>`},
{nComment, nil, "<!-- this is a comment -->"},
{nText, nil, `
This is some text.
`},
{nil, sHeaders, `<h1 class="header"></h1>`},
}
for i, c := range cases {
if c.sel == nil {
c.sel = newSingleSelection(c.node, doc)
}
got, err := OuterHtml(c.sel)
if err != nil {
t.Fatal(err)
}
if got != c.want {
t.Errorf("%d: want %q, got %q", i, c.want, got)
}
}
}

24
vendor/github.com/andybalholm/cascadia/LICENSE generated vendored Executable file
View file

@ -0,0 +1,24 @@
Copyright (c) 2011 Andy Balholm. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

9
vendor/github.com/andybalholm/cascadia/README.md generated vendored Normal file
View file

@ -0,0 +1,9 @@
# cascadia
[![](https://travis-ci.org/andybalholm/cascadia.svg)](https://travis-ci.org/andybalholm/cascadia)
The Cascadia package implements CSS selectors for use with the parse trees produced by the html package.
To test CSS selectors without writing Go code, check out [cascadia](https://github.com/suntong/cascadia) the command line tool, a thin wrapper around this package.
[Refer to godoc here](https://godoc.org/github.com/andybalholm/cascadia).

View file

@ -0,0 +1,53 @@
package cascadia
import (
"strings"
"testing"
"golang.org/x/net/html"
)
func MustParseHTML(doc string) *html.Node {
dom, err := html.Parse(strings.NewReader(doc))
if err != nil {
panic(err)
}
return dom
}
var selector = MustCompile(`div.matched`)
var doc = `<!DOCTYPE html>
<html>
<body>
<div class="matched">
<div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
<div class="matched"></div>
</div>
</div>
</body>
</html>
`
var dom = MustParseHTML(doc)
func BenchmarkMatchAll(b *testing.B) {
var matches []*html.Node
for i := 0; i < b.N; i++ {
matches = selector.MatchAll(dom)
}
_ = matches
}

3
vendor/github.com/andybalholm/cascadia/go.mod generated vendored Normal file
View file

@ -0,0 +1,3 @@
module "github.com/andybalholm/cascadia"
require "golang.org/x/net" v0.0.0-20180218175443-cbe0f9307d01

835
vendor/github.com/andybalholm/cascadia/parser.go generated vendored Normal file
View file

@ -0,0 +1,835 @@
// Package cascadia is an implementation of CSS selectors.
package cascadia
import (
"errors"
"fmt"
"regexp"
"strconv"
"strings"
"golang.org/x/net/html"
)
// a parser for CSS selectors
type parser struct {
s string // the source text
i int // the current position
}
// parseEscape parses a backslash escape.
func (p *parser) parseEscape() (result string, err error) {
if len(p.s) < p.i+2 || p.s[p.i] != '\\' {
return "", errors.New("invalid escape sequence")
}
start := p.i + 1
c := p.s[start]
switch {
case c == '\r' || c == '\n' || c == '\f':
return "", errors.New("escaped line ending outside string")
case hexDigit(c):
// unicode escape (hex)
var i int
for i = start; i < p.i+6 && i < len(p.s) && hexDigit(p.s[i]); i++ {
// empty
}
v, _ := strconv.ParseUint(p.s[start:i], 16, 21)
if len(p.s) > i {
switch p.s[i] {
case '\r':
i++
if len(p.s) > i && p.s[i] == '\n' {
i++
}
case ' ', '\t', '\n', '\f':
i++
}
}
p.i = i
return string(rune(v)), nil
}
// Return the literal character after the backslash.
result = p.s[start : start+1]
p.i += 2
return result, nil
}
func hexDigit(c byte) bool {
return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
}
// nameStart returns whether c can be the first character of an identifier
// (not counting an initial hyphen, or an escape sequence).
func nameStart(c byte) bool {
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127
}
// nameChar returns whether c can be a character within an identifier
// (not counting an escape sequence).
func nameChar(c byte) bool {
return 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_' || c > 127 ||
c == '-' || '0' <= c && c <= '9'
}
// parseIdentifier parses an identifier.
func (p *parser) parseIdentifier() (result string, err error) {
startingDash := false
if len(p.s) > p.i && p.s[p.i] == '-' {
startingDash = true
p.i++
}
if len(p.s) <= p.i {
return "", errors.New("expected identifier, found EOF instead")
}
if c := p.s[p.i]; !(nameStart(c) || c == '\\') {
return "", fmt.Errorf("expected identifier, found %c instead", c)
}
result, err = p.parseName()
if startingDash && err == nil {
result = "-" + result
}
return
}
// parseName parses a name (which is like an identifier, but doesn't have
// extra restrictions on the first character).
func (p *parser) parseName() (result string, err error) {
i := p.i
loop:
for i < len(p.s) {
c := p.s[i]
switch {
case nameChar(c):
start := i
for i < len(p.s) && nameChar(p.s[i]) {
i++
}
result += p.s[start:i]
case c == '\\':
p.i = i
val, err := p.parseEscape()
if err != nil {
return "", err
}
i = p.i
result += val
default:
break loop
}
}
if result == "" {
return "", errors.New("expected name, found EOF instead")
}
p.i = i
return result, nil
}
// parseString parses a single- or double-quoted string.
func (p *parser) parseString() (result string, err error) {
i := p.i
if len(p.s) < i+2 {
return "", errors.New("expected string, found EOF instead")
}
quote := p.s[i]
i++
loop:
for i < len(p.s) {
switch p.s[i] {
case '\\':
if len(p.s) > i+1 {
switch c := p.s[i+1]; c {
case '\r':
if len(p.s) > i+2 && p.s[i+2] == '\n' {
i += 3
continue loop
}
fallthrough
case '\n', '\f':
i += 2
continue loop
}
}
p.i = i
val, err := p.parseEscape()
if err != nil {
return "", err
}
i = p.i
result += val
case quote:
break loop
case '\r', '\n', '\f':
return "", errors.New("unexpected end of line in string")
default:
start := i
for i < len(p.s) {
if c := p.s[i]; c == quote || c == '\\' || c == '\r' || c == '\n' || c == '\f' {
break
}
i++
}
result += p.s[start:i]
}
}
if i >= len(p.s) {
return "", errors.New("EOF in string")
}
// Consume the final quote.
i++
p.i = i
return result, nil
}
// parseRegex parses a regular expression; the end is defined by encountering an
// unmatched closing ')' or ']' which is not consumed
func (p *parser) parseRegex() (rx *regexp.Regexp, err error) {
i := p.i
if len(p.s) < i+2 {
return nil, errors.New("expected regular expression, found EOF instead")
}
// number of open parens or brackets;
// when it becomes negative, finished parsing regex
open := 0
loop:
for i < len(p.s) {
switch p.s[i] {
case '(', '[':
open++
case ')', ']':
open--
if open < 0 {
break loop
}
}
i++
}
if i >= len(p.s) {
return nil, errors.New("EOF in regular expression")
}
rx, err = regexp.Compile(p.s[p.i:i])
p.i = i
return rx, err
}
// skipWhitespace consumes whitespace characters and comments.
// It returns true if there was actually anything to skip.
func (p *parser) skipWhitespace() bool {
i := p.i
for i < len(p.s) {
switch p.s[i] {
case ' ', '\t', '\r', '\n', '\f':
i++
continue
case '/':
if strings.HasPrefix(p.s[i:], "/*") {
end := strings.Index(p.s[i+len("/*"):], "*/")
if end != -1 {
i += end + len("/**/")
continue
}
}
}
break
}
if i > p.i {
p.i = i
return true
}
return false
}
// consumeParenthesis consumes an opening parenthesis and any following
// whitespace. It returns true if there was actually a parenthesis to skip.
func (p *parser) consumeParenthesis() bool {
if p.i < len(p.s) && p.s[p.i] == '(' {
p.i++
p.skipWhitespace()
return true
}
return false
}
// consumeClosingParenthesis consumes a closing parenthesis and any preceding
// whitespace. It returns true if there was actually a parenthesis to skip.
func (p *parser) consumeClosingParenthesis() bool {
i := p.i
p.skipWhitespace()
if p.i < len(p.s) && p.s[p.i] == ')' {
p.i++
return true
}
p.i = i
return false
}
// parseTypeSelector parses a type selector (one that matches by tag name).
func (p *parser) parseTypeSelector() (result Selector, err error) {
tag, err := p.parseIdentifier()
if err != nil {
return nil, err
}
return typeSelector(tag), nil
}
// parseIDSelector parses a selector that matches by id attribute.
func (p *parser) parseIDSelector() (Selector, error) {
if p.i >= len(p.s) {
return nil, fmt.Errorf("expected id selector (#id), found EOF instead")
}
if p.s[p.i] != '#' {
return nil, fmt.Errorf("expected id selector (#id), found '%c' instead", p.s[p.i])
}
p.i++
id, err := p.parseName()
if err != nil {
return nil, err
}
return attributeEqualsSelector("id", id), nil
}
// parseClassSelector parses a selector that matches by class attribute.
func (p *parser) parseClassSelector() (Selector, error) {
if p.i >= len(p.s) {
return nil, fmt.Errorf("expected class selector (.class), found EOF instead")
}
if p.s[p.i] != '.' {
return nil, fmt.Errorf("expected class selector (.class), found '%c' instead", p.s[p.i])
}
p.i++
class, err := p.parseIdentifier()
if err != nil {
return nil, err
}
return attributeIncludesSelector("class", class), nil
}
// parseAttributeSelector parses a selector that matches by attribute value.
func (p *parser) parseAttributeSelector() (Selector, error) {
if p.i >= len(p.s) {
return nil, fmt.Errorf("expected attribute selector ([attribute]), found EOF instead")
}
if p.s[p.i] != '[' {
return nil, fmt.Errorf("expected attribute selector ([attribute]), found '%c' instead", p.s[p.i])
}
p.i++
p.skipWhitespace()
key, err := p.parseIdentifier()
if err != nil {
return nil, err
}
p.skipWhitespace()
if p.i >= len(p.s) {
return nil, errors.New("unexpected EOF in attribute selector")
}
if p.s[p.i] == ']' {
p.i++
return attributeExistsSelector(key), nil
}
if p.i+2 >= len(p.s) {
return nil, errors.New("unexpected EOF in attribute selector")
}
op := p.s[p.i : p.i+2]
if op[0] == '=' {
op = "="
} else if op[1] != '=' {
return nil, fmt.Errorf(`expected equality operator, found "%s" instead`, op)
}
p.i += len(op)
p.skipWhitespace()
if p.i >= len(p.s) {
return nil, errors.New("unexpected EOF in attribute selector")
}
var val string
var rx *regexp.Regexp
if op == "#=" {
rx, err = p.parseRegex()
} else {
switch p.s[p.i] {
case '\'', '"':
val, err = p.parseString()
default:
val, err = p.parseIdentifier()
}
}
if err != nil {
return nil, err
}
p.skipWhitespace()
if p.i >= len(p.s) {
return nil, errors.New("unexpected EOF in attribute selector")
}
if p.s[p.i] != ']' {
return nil, fmt.Errorf("expected ']', found '%c' instead", p.s[p.i])
}
p.i++
switch op {
case "=":
return attributeEqualsSelector(key, val), nil
case "!=":
return attributeNotEqualSelector(key, val), nil
case "~=":
return attributeIncludesSelector(key, val), nil
case "|=":
return attributeDashmatchSelector(key, val), nil
case "^=":
return attributePrefixSelector(key, val), nil
case "$=":
return attributeSuffixSelector(key, val), nil
case "*=":
return attributeSubstringSelector(key, val), nil
case "#=":
return attributeRegexSelector(key, rx), nil
}
return nil, fmt.Errorf("attribute operator %q is not supported", op)
}
var errExpectedParenthesis = errors.New("expected '(' but didn't find it")
var errExpectedClosingParenthesis = errors.New("expected ')' but didn't find it")
var errUnmatchedParenthesis = errors.New("unmatched '('")
// parsePseudoclassSelector parses a pseudoclass selector like :not(p).
func (p *parser) parsePseudoclassSelector() (Selector, error) {
if p.i >= len(p.s) {
return nil, fmt.Errorf("expected pseudoclass selector (:pseudoclass), found EOF instead")
}
if p.s[p.i] != ':' {
return nil, fmt.Errorf("expected attribute selector (:pseudoclass), found '%c' instead", p.s[p.i])
}
p.i++
name, err := p.parseIdentifier()
if err != nil {
return nil, err
}
name = toLowerASCII(name)
switch name {
case "not", "has", "haschild":
if !p.consumeParenthesis() {
return nil, errExpectedParenthesis
}
sel, parseErr := p.parseSelectorGroup()
if parseErr != nil {
return nil, parseErr
}
if !p.consumeClosingParenthesis() {
return nil, errExpectedClosingParenthesis
}
switch name {
case "not":
return negatedSelector(sel), nil
case "has":
return hasDescendantSelector(sel), nil
case "haschild":
return hasChildSelector(sel), nil
}
case "contains", "containsown":
if !p.consumeParenthesis() {
return nil, errExpectedParenthesis
}
if p.i == len(p.s) {
return nil, errUnmatchedParenthesis
}
var val string
switch p.s[p.i] {
case '\'', '"':
val, err = p.parseString()
default:
val, err = p.parseIdentifier()
}
if err != nil {
return nil, err
}
val = strings.ToLower(val)
p.skipWhitespace()
if p.i >= len(p.s) {
return nil, errors.New("unexpected EOF in pseudo selector")
}
if !p.consumeClosingParenthesis() {
return nil, errExpectedClosingParenthesis
}
switch name {
case "contains":
return textSubstrSelector(val), nil
case "containsown":
return ownTextSubstrSelector(val), nil
}
case "matches", "matchesown":
if !p.consumeParenthesis() {
return nil, errExpectedParenthesis
}
rx, err := p.parseRegex()
if err != nil {
return nil, err
}
if p.i >= len(p.s) {
return nil, errors.New("unexpected EOF in pseudo selector")
}
if !p.consumeClosingParenthesis() {
return nil, errExpectedClosingParenthesis
}
switch name {
case "matches":
return textRegexSelector(rx), nil
case "matchesown":
return ownTextRegexSelector(rx), nil
}
case "nth-child", "nth-last-child", "nth-of-type", "nth-last-of-type":
if !p.consumeParenthesis() {
return nil, errExpectedParenthesis
}
a, b, err := p.parseNth()
if err != nil {
return nil, err
}
if !p.consumeClosingParenthesis() {
return nil, errExpectedClosingParenthesis
}
if a == 0 {
switch name {
case "nth-child":
return simpleNthChildSelector(b, false), nil
case "nth-of-type":
return simpleNthChildSelector(b, true), nil
case "nth-last-child":
return simpleNthLastChildSelector(b, false), nil
case "nth-last-of-type":
return simpleNthLastChildSelector(b, true), nil
}
}
return nthChildSelector(a, b,
name == "nth-last-child" || name == "nth-last-of-type",
name == "nth-of-type" || name == "nth-last-of-type"),
nil
case "first-child":
return simpleNthChildSelector(1, false), nil
case "last-child":
return simpleNthLastChildSelector(1, false), nil
case "first-of-type":
return simpleNthChildSelector(1, true), nil
case "last-of-type":
return simpleNthLastChildSelector(1, true), nil
case "only-child":
return onlyChildSelector(false), nil
case "only-of-type":
return onlyChildSelector(true), nil
case "input":
return inputSelector, nil
case "empty":
return emptyElementSelector, nil
case "root":
return rootSelector, nil
}
return nil, fmt.Errorf("unknown pseudoclass :%s", name)
}
// parseInteger parses a decimal integer.
func (p *parser) parseInteger() (int, error) {
i := p.i
start := i
for i < len(p.s) && '0' <= p.s[i] && p.s[i] <= '9' {
i++
}
if i == start {
return 0, errors.New("expected integer, but didn't find it")
}
p.i = i
val, err := strconv.Atoi(p.s[start:i])
if err != nil {
return 0, err
}
return val, nil
}
// parseNth parses the argument for :nth-child (normally of the form an+b).
func (p *parser) parseNth() (a, b int, err error) {
// initial state
if p.i >= len(p.s) {
goto eof
}
switch p.s[p.i] {
case '-':
p.i++
goto negativeA
case '+':
p.i++
goto positiveA
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
goto positiveA
case 'n', 'N':
a = 1
p.i++
goto readN
case 'o', 'O', 'e', 'E':
id, nameErr := p.parseName()
if nameErr != nil {
return 0, 0, nameErr
}
id = toLowerASCII(id)
if id == "odd" {
return 2, 1, nil
}
if id == "even" {
return 2, 0, nil
}
return 0, 0, fmt.Errorf("expected 'odd' or 'even', but found '%s' instead", id)
default:
goto invalid
}
positiveA:
if p.i >= len(p.s) {
goto eof
}
switch p.s[p.i] {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
a, err = p.parseInteger()
if err != nil {
return 0, 0, err
}
goto readA
case 'n', 'N':
a = 1
p.i++
goto readN
default:
goto invalid
}
negativeA:
if p.i >= len(p.s) {
goto eof
}
switch p.s[p.i] {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
a, err = p.parseInteger()
if err != nil {
return 0, 0, err
}
a = -a
goto readA
case 'n', 'N':
a = -1
p.i++
goto readN
default:
goto invalid
}
readA:
if p.i >= len(p.s) {
goto eof
}
switch p.s[p.i] {
case 'n', 'N':
p.i++
goto readN
default:
// The number we read as a is actually b.
return 0, a, nil
}
readN:
p.skipWhitespace()
if p.i >= len(p.s) {
goto eof
}
switch p.s[p.i] {
case '+':
p.i++
p.skipWhitespace()
b, err = p.parseInteger()
if err != nil {
return 0, 0, err
}
return a, b, nil
case '-':
p.i++
p.skipWhitespace()
b, err = p.parseInteger()
if err != nil {
return 0, 0, err
}
return a, -b, nil
default:
return a, 0, nil
}
eof:
return 0, 0, errors.New("unexpected EOF while attempting to parse expression of form an+b")
invalid:
return 0, 0, errors.New("unexpected character while attempting to parse expression of form an+b")
}
// parseSimpleSelectorSequence parses a selector sequence that applies to
// a single element.
func (p *parser) parseSimpleSelectorSequence() (Selector, error) {
var result Selector
if p.i >= len(p.s) {
return nil, errors.New("expected selector, found EOF instead")
}
switch p.s[p.i] {
case '*':
// It's the universal selector. Just skip over it, since it doesn't affect the meaning.
p.i++
case '#', '.', '[', ':':
// There's no type selector. Wait to process the other till the main loop.
default:
r, err := p.parseTypeSelector()
if err != nil {
return nil, err
}
result = r
}
loop:
for p.i < len(p.s) {
var ns Selector
var err error
switch p.s[p.i] {
case '#':
ns, err = p.parseIDSelector()
case '.':
ns, err = p.parseClassSelector()
case '[':
ns, err = p.parseAttributeSelector()
case ':':
ns, err = p.parsePseudoclassSelector()
default:
break loop
}
if err != nil {
return nil, err
}
if result == nil {
result = ns
} else {
result = intersectionSelector(result, ns)
}
}
if result == nil {
result = func(n *html.Node) bool {
return n.Type == html.ElementNode
}
}
return result, nil
}
// parseSelector parses a selector that may include combinators.
func (p *parser) parseSelector() (result Selector, err error) {
p.skipWhitespace()
result, err = p.parseSimpleSelectorSequence()
if err != nil {
return
}
for {
var combinator byte
if p.skipWhitespace() {
combinator = ' '
}
if p.i >= len(p.s) {
return
}
switch p.s[p.i] {
case '+', '>', '~':
combinator = p.s[p.i]
p.i++
p.skipWhitespace()
case ',', ')':
// These characters can't begin a selector, but they can legally occur after one.
return
}
if combinator == 0 {
return
}
c, err := p.parseSimpleSelectorSequence()
if err != nil {
return nil, err
}
switch combinator {
case ' ':
result = descendantSelector(result, c)
case '>':
result = childSelector(result, c)
case '+':
result = siblingSelector(result, c, true)
case '~':
result = siblingSelector(result, c, false)
}
}
panic("unreachable")
}
// parseSelectorGroup parses a group of selectors, separated by commas.
func (p *parser) parseSelectorGroup() (result Selector, err error) {
result, err = p.parseSelector()
if err != nil {
return
}
for p.i < len(p.s) {
if p.s[p.i] != ',' {
return result, nil
}
p.i++
c, err := p.parseSelector()
if err != nil {
return nil, err
}
result = unionSelector(result, c)
}
return
}

86
vendor/github.com/andybalholm/cascadia/parser_test.go generated vendored Normal file
View file

@ -0,0 +1,86 @@
package cascadia
import (
"testing"
)
var identifierTests = map[string]string{
"x": "x",
"96": "",
"-x": "-x",
`r\e9 sumé`: "résumé",
`a\"b`: `a"b`,
}
func TestParseIdentifier(t *testing.T) {
for source, want := range identifierTests {
p := &parser{s: source}
got, err := p.parseIdentifier()
if err != nil {
if want == "" {
// It was supposed to be an error.
continue
}
t.Errorf("parsing %q: got error (%s), want %q", source, err, want)
continue
}
if want == "" {
if err == nil {
t.Errorf("parsing %q: got %q, want error", source, got)
}
continue
}
if p.i < len(source) {
t.Errorf("parsing %q: %d bytes left over", source, len(source)-p.i)
continue
}
if got != want {
t.Errorf("parsing %q: got %q, want %q", source, got, want)
}
}
}
var stringTests = map[string]string{
`"x"`: "x",
`'x'`: "x",
`'x`: "",
"'x\\\r\nx'": "xx",
`"r\e9 sumé"`: "résumé",
`"a\"b"`: `a"b`,
}
func TestParseString(t *testing.T) {
for source, want := range stringTests {
p := &parser{s: source}
got, err := p.parseString()
if err != nil {
if want == "" {
// It was supposed to be an error.
continue
}
t.Errorf("parsing %q: got error (%s), want %q", source, err, want)
continue
}
if want == "" {
if err == nil {
t.Errorf("parsing %q: got %q, want error", source, got)
}
continue
}
if p.i < len(source) {
t.Errorf("parsing %q: %d bytes left over", source, len(source)-p.i)
continue
}
if got != want {
t.Errorf("parsing %q: got %q, want %q", source, got, want)
}
}
}

622
vendor/github.com/andybalholm/cascadia/selector.go generated vendored Normal file
View file

@ -0,0 +1,622 @@
package cascadia
import (
"bytes"
"fmt"
"regexp"
"strings"
"golang.org/x/net/html"
)
// the Selector type, and functions for creating them
// A Selector is a function which tells whether a node matches or not.
type Selector func(*html.Node) bool
// hasChildMatch returns whether n has any child that matches a.
func hasChildMatch(n *html.Node, a Selector) bool {
for c := n.FirstChild; c != nil; c = c.NextSibling {
if a(c) {
return true
}
}
return false
}
// hasDescendantMatch performs a depth-first search of n's descendants,
// testing whether any of them match a. It returns true as soon as a match is
// found, or false if no match is found.
func hasDescendantMatch(n *html.Node, a Selector) bool {
for c := n.FirstChild; c != nil; c = c.NextSibling {
if a(c) || (c.Type == html.ElementNode && hasDescendantMatch(c, a)) {
return true
}
}
return false
}
// Compile parses a selector and returns, if successful, a Selector object
// that can be used to match against html.Node objects.
func Compile(sel string) (Selector, error) {
p := &parser{s: sel}
compiled, err := p.parseSelectorGroup()
if err != nil {
return nil, err
}
if p.i < len(sel) {
return nil, fmt.Errorf("parsing %q: %d bytes left over", sel, len(sel)-p.i)
}
return compiled, nil
}
// MustCompile is like Compile, but panics instead of returning an error.
func MustCompile(sel string) Selector {
compiled, err := Compile(sel)
if err != nil {
panic(err)
}
return compiled
}
// MatchAll returns a slice of the nodes that match the selector,
// from n and its children.
func (s Selector) MatchAll(n *html.Node) []*html.Node {
return s.matchAllInto(n, nil)
}
func (s Selector) matchAllInto(n *html.Node, storage []*html.Node) []*html.Node {
if s(n) {
storage = append(storage, n)
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
storage = s.matchAllInto(child, storage)
}
return storage
}
// Match returns true if the node matches the selector.
func (s Selector) Match(n *html.Node) bool {
return s(n)
}
// MatchFirst returns the first node that matches s, from n and its children.
func (s Selector) MatchFirst(n *html.Node) *html.Node {
if s.Match(n) {
return n
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
m := s.MatchFirst(c)
if m != nil {
return m
}
}
return nil
}
// Filter returns the nodes in nodes that match the selector.
func (s Selector) Filter(nodes []*html.Node) (result []*html.Node) {
for _, n := range nodes {
if s(n) {
result = append(result, n)
}
}
return result
}
// typeSelector returns a Selector that matches elements with a given tag name.
func typeSelector(tag string) Selector {
tag = toLowerASCII(tag)
return func(n *html.Node) bool {
return n.Type == html.ElementNode && n.Data == tag
}
}
// toLowerASCII returns s with all ASCII capital letters lowercased.
func toLowerASCII(s string) string {
var b []byte
for i := 0; i < len(s); i++ {
if c := s[i]; 'A' <= c && c <= 'Z' {
if b == nil {
b = make([]byte, len(s))
copy(b, s)
}
b[i] = s[i] + ('a' - 'A')
}
}
if b == nil {
return s
}
return string(b)
}
// attributeSelector returns a Selector that matches elements
// where the attribute named key satisifes the function f.
func attributeSelector(key string, f func(string) bool) Selector {
key = toLowerASCII(key)
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
for _, a := range n.Attr {
if a.Key == key && f(a.Val) {
return true
}
}
return false
}
}
// attributeExistsSelector returns a Selector that matches elements that have
// an attribute named key.
func attributeExistsSelector(key string) Selector {
return attributeSelector(key, func(string) bool { return true })
}
// attributeEqualsSelector returns a Selector that matches elements where
// the attribute named key has the value val.
func attributeEqualsSelector(key, val string) Selector {
return attributeSelector(key,
func(s string) bool {
return s == val
})
}
// attributeNotEqualSelector returns a Selector that matches elements where
// the attribute named key does not have the value val.
func attributeNotEqualSelector(key, val string) Selector {
key = toLowerASCII(key)
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
for _, a := range n.Attr {
if a.Key == key && a.Val == val {
return false
}
}
return true
}
}
// attributeIncludesSelector returns a Selector that matches elements where
// the attribute named key is a whitespace-separated list that includes val.
func attributeIncludesSelector(key, val string) Selector {
return attributeSelector(key,
func(s string) bool {
for s != "" {
i := strings.IndexAny(s, " \t\r\n\f")
if i == -1 {
return s == val
}
if s[:i] == val {
return true
}
s = s[i+1:]
}
return false
})
}
// attributeDashmatchSelector returns a Selector that matches elements where
// the attribute named key equals val or starts with val plus a hyphen.
func attributeDashmatchSelector(key, val string) Selector {
return attributeSelector(key,
func(s string) bool {
if s == val {
return true
}
if len(s) <= len(val) {
return false
}
if s[:len(val)] == val && s[len(val)] == '-' {
return true
}
return false
})
}
// attributePrefixSelector returns a Selector that matches elements where
// the attribute named key starts with val.
func attributePrefixSelector(key, val string) Selector {
return attributeSelector(key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
}
return strings.HasPrefix(s, val)
})
}
// attributeSuffixSelector returns a Selector that matches elements where
// the attribute named key ends with val.
func attributeSuffixSelector(key, val string) Selector {
return attributeSelector(key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
}
return strings.HasSuffix(s, val)
})
}
// attributeSubstringSelector returns a Selector that matches nodes where
// the attribute named key contains val.
func attributeSubstringSelector(key, val string) Selector {
return attributeSelector(key,
func(s string) bool {
if strings.TrimSpace(s) == "" {
return false
}
return strings.Contains(s, val)
})
}
// attributeRegexSelector returns a Selector that matches nodes where
// the attribute named key matches the regular expression rx
func attributeRegexSelector(key string, rx *regexp.Regexp) Selector {
return attributeSelector(key,
func(s string) bool {
return rx.MatchString(s)
})
}
// intersectionSelector returns a selector that matches nodes that match
// both a and b.
func intersectionSelector(a, b Selector) Selector {
return func(n *html.Node) bool {
return a(n) && b(n)
}
}
// unionSelector returns a selector that matches elements that match
// either a or b.
func unionSelector(a, b Selector) Selector {
return func(n *html.Node) bool {
return a(n) || b(n)
}
}
// negatedSelector returns a selector that matches elements that do not match a.
func negatedSelector(a Selector) Selector {
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
return !a(n)
}
}
// writeNodeText writes the text contained in n and its descendants to b.
func writeNodeText(n *html.Node, b *bytes.Buffer) {
switch n.Type {
case html.TextNode:
b.WriteString(n.Data)
case html.ElementNode:
for c := n.FirstChild; c != nil; c = c.NextSibling {
writeNodeText(c, b)
}
}
}
// nodeText returns the text contained in n and its descendants.
func nodeText(n *html.Node) string {
var b bytes.Buffer
writeNodeText(n, &b)
return b.String()
}
// nodeOwnText returns the contents of the text nodes that are direct
// children of n.
func nodeOwnText(n *html.Node) string {
var b bytes.Buffer
for c := n.FirstChild; c != nil; c = c.NextSibling {
if c.Type == html.TextNode {
b.WriteString(c.Data)
}
}
return b.String()
}
// textSubstrSelector returns a selector that matches nodes that
// contain the given text.
func textSubstrSelector(val string) Selector {
return func(n *html.Node) bool {
text := strings.ToLower(nodeText(n))
return strings.Contains(text, val)
}
}
// ownTextSubstrSelector returns a selector that matches nodes that
// directly contain the given text
func ownTextSubstrSelector(val string) Selector {
return func(n *html.Node) bool {
text := strings.ToLower(nodeOwnText(n))
return strings.Contains(text, val)
}
}
// textRegexSelector returns a selector that matches nodes whose text matches
// the specified regular expression
func textRegexSelector(rx *regexp.Regexp) Selector {
return func(n *html.Node) bool {
return rx.MatchString(nodeText(n))
}
}
// ownTextRegexSelector returns a selector that matches nodes whose text
// directly matches the specified regular expression
func ownTextRegexSelector(rx *regexp.Regexp) Selector {
return func(n *html.Node) bool {
return rx.MatchString(nodeOwnText(n))
}
}
// hasChildSelector returns a selector that matches elements
// with a child that matches a.
func hasChildSelector(a Selector) Selector {
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
return hasChildMatch(n, a)
}
}
// hasDescendantSelector returns a selector that matches elements
// with any descendant that matches a.
func hasDescendantSelector(a Selector) Selector {
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
return hasDescendantMatch(n, a)
}
}
// nthChildSelector returns a selector that implements :nth-child(an+b).
// If last is true, implements :nth-last-child instead.
// If ofType is true, implements :nth-of-type instead.
func nthChildSelector(a, b int, last, ofType bool) Selector {
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
parent := n.Parent
if parent == nil {
return false
}
if parent.Type == html.DocumentNode {
return false
}
i := -1
count := 0
for c := parent.FirstChild; c != nil; c = c.NextSibling {
if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
continue
}
count++
if c == n {
i = count
if !last {
break
}
}
}
if i == -1 {
// This shouldn't happen, since n should always be one of its parent's children.
return false
}
if last {
i = count - i + 1
}
i -= b
if a == 0 {
return i == 0
}
return i%a == 0 && i/a >= 0
}
}
// simpleNthChildSelector returns a selector that implements :nth-child(b).
// If ofType is true, implements :nth-of-type instead.
func simpleNthChildSelector(b int, ofType bool) Selector {
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
parent := n.Parent
if parent == nil {
return false
}
if parent.Type == html.DocumentNode {
return false
}
count := 0
for c := parent.FirstChild; c != nil; c = c.NextSibling {
if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
continue
}
count++
if c == n {
return count == b
}
if count >= b {
return false
}
}
return false
}
}
// simpleNthLastChildSelector returns a selector that implements
// :nth-last-child(b). If ofType is true, implements :nth-last-of-type
// instead.
func simpleNthLastChildSelector(b int, ofType bool) Selector {
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
parent := n.Parent
if parent == nil {
return false
}
if parent.Type == html.DocumentNode {
return false
}
count := 0
for c := parent.LastChild; c != nil; c = c.PrevSibling {
if c.Type != html.ElementNode || (ofType && c.Data != n.Data) {
continue
}
count++
if c == n {
return count == b
}
if count >= b {
return false
}
}
return false
}
}
// onlyChildSelector returns a selector that implements :only-child.
// If ofType is true, it implements :only-of-type instead.
func onlyChildSelector(ofType bool) Selector {
return func(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
parent := n.Parent
if parent == nil {
return false
}
if parent.Type == html.DocumentNode {
return false
}
count := 0
for c := parent.FirstChild; c != nil; c = c.NextSibling {
if (c.Type != html.ElementNode) || (ofType && c.Data != n.Data) {
continue
}
count++
if count > 1 {
return false
}
}
return count == 1
}
}
// inputSelector is a Selector that matches input, select, textarea and button elements.
func inputSelector(n *html.Node) bool {
return n.Type == html.ElementNode && (n.Data == "input" || n.Data == "select" || n.Data == "textarea" || n.Data == "button")
}
// emptyElementSelector is a Selector that matches empty elements.
func emptyElementSelector(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
switch c.Type {
case html.ElementNode, html.TextNode:
return false
}
}
return true
}
// descendantSelector returns a Selector that matches an element if
// it matches d and has an ancestor that matches a.
func descendantSelector(a, d Selector) Selector {
return func(n *html.Node) bool {
if !d(n) {
return false
}
for p := n.Parent; p != nil; p = p.Parent {
if a(p) {
return true
}
}
return false
}
}
// childSelector returns a Selector that matches an element if
// it matches d and its parent matches a.
func childSelector(a, d Selector) Selector {
return func(n *html.Node) bool {
return d(n) && n.Parent != nil && a(n.Parent)
}
}
// siblingSelector returns a Selector that matches an element
// if it matches s2 and in is preceded by an element that matches s1.
// If adjacent is true, the sibling must be immediately before the element.
func siblingSelector(s1, s2 Selector, adjacent bool) Selector {
return func(n *html.Node) bool {
if !s2(n) {
return false
}
if adjacent {
for n = n.PrevSibling; n != nil; n = n.PrevSibling {
if n.Type == html.TextNode || n.Type == html.CommentNode {
continue
}
return s1(n)
}
return false
}
// Walk backwards looking for element that matches s1
for c := n.PrevSibling; c != nil; c = c.PrevSibling {
if s1(c) {
return true
}
}
return false
}
}
// rootSelector implements :root
func rootSelector(n *html.Node) bool {
if n.Type != html.ElementNode {
return false
}
if n.Parent == nil {
return false
}
return n.Parent.Type == html.DocumentNode
}

654
vendor/github.com/andybalholm/cascadia/selector_test.go generated vendored Normal file
View file

@ -0,0 +1,654 @@
package cascadia
import (
"bytes"
"strings"
"testing"
"golang.org/x/net/html"
)
type selectorTest struct {
HTML, selector string
results []string
}
func nodeString(n *html.Node) string {
buf := bytes.NewBufferString("")
html.Render(buf, n)
return buf.String()
}
var selectorTests = []selectorTest{
{
`<body><address>This address...</address></body>`,
"address",
[]string{
"<address>This address...</address>",
},
},
{
`<!-- comment --><html><head></head><body>text</body></html>`,
"*",
[]string{
"<html><head></head><body>text</body></html>",
"<head></head>",
"<body>text</body>",
},
},
{
`<html><head></head><body></body></html>`,
"*",
[]string{
"<html><head></head><body></body></html>",
"<head></head>",
"<body></body>",
},
},
{
`<p id="foo"><p id="bar">`,
"#foo",
[]string{
`<p id="foo"></p>`,
},
},
{
`<ul><li id="t1"><p id="t1">`,
"li#t1",
[]string{
`<li id="t1"><p id="t1"></p></li>`,
},
},
{
`<ol><li id="t4"><li id="t44">`,
"*#t4",
[]string{
`<li id="t4"></li>`,
},
},
{
`<ul><li class="t1"><li class="t2">`,
".t1",
[]string{
`<li class="t1"></li>`,
},
},
{
`<p class="t1 t2">`,
"p.t1",
[]string{
`<p class="t1 t2"></p>`,
},
},
{
`<div class="test">`,
"div.teST",
[]string{},
},
{
`<p class="t1 t2">`,
".t1.fail",
[]string{},
},
{
`<p class="t1 t2">`,
"p.t1.t2",
[]string{
`<p class="t1 t2"></p>`,
},
},
{
`<p><p title="title">`,
"p[title]",
[]string{
`<p title="title"></p>`,
},
},
{
`<address><address title="foo"><address title="bar">`,
`address[title="foo"]`,
[]string{
`<address title="foo"><address title="bar"></address></address>`,
},
},
{
`<address><address title="foo"><address title="bar">`,
`address[title!="foo"]`,
[]string{
`<address><address title="foo"><address title="bar"></address></address></address>`,
`<address title="bar"></address>`,
},
},
{
`<p title="tot foo bar">`,
`[ title ~= foo ]`,
[]string{
`<p title="tot foo bar"></p>`,
},
},
{
`<p title="hello world">`,
`[title~="hello world"]`,
[]string{},
},
{
`<p lang="en"><p lang="en-gb"><p lang="enough"><p lang="fr-en">`,
`[lang|="en"]`,
[]string{
`<p lang="en"></p>`,
`<p lang="en-gb"></p>`,
},
},
{
`<p title="foobar"><p title="barfoo">`,
`[title^="foo"]`,
[]string{
`<p title="foobar"></p>`,
},
},
{
`<p title="foobar"><p title="barfoo">`,
`[title$="bar"]`,
[]string{
`<p title="foobar"></p>`,
},
},
{
`<p title="foobarufoo">`,
`[title*="bar"]`,
[]string{
`<p title="foobarufoo"></p>`,
},
},
{
`<p class=" ">This text should be green.</p><p>This text should be green.</p>`,
`p[class$=" "]`,
[]string{},
},
{
`<p class="">This text should be green.</p><p>This text should be green.</p>`,
`p[class$=""]`,
[]string{},
},
{
`<p class=" ">This text should be green.</p><p>This text should be green.</p>`,
`p[class^=" "]`,
[]string{},
},
{
`<p class="">This text should be green.</p><p>This text should be green.</p>`,
`p[class^=""]`,
[]string{},
},
{
`<p class=" ">This text should be green.</p><p>This text should be green.</p>`,
`p[class*=" "]`,
[]string{},
},
{
`<p class="">This text should be green.</p><p>This text should be green.</p>`,
`p[class*=""]`,
[]string{},
},
{
`<input type="radio" name="Sex" value="F"/>`,
`input[name=Sex][value=F]`,
[]string{
`<input type="radio" name="Sex" value="F"/>`,
},
},
{
`<table border="0" cellpadding="0" cellspacing="0" style="table-layout: fixed; width: 100%; border: 0 dashed; border-color: #FFFFFF"><tr style="height:64px">aaa</tr></table>`,
`table[border="0"][cellpadding="0"][cellspacing="0"]`,
[]string{
`<table border="0" cellpadding="0" cellspacing="0" style="table-layout: fixed; width: 100%; border: 0 dashed; border-color: #FFFFFF"><tbody><tr style="height:64px"></tr></tbody></table>`,
},
},
{
`<p class="t1 t2">`,
".t1:not(.t2)",
[]string{},
},
{
`<div class="t3">`,
`div:not(.t1)`,
[]string{
`<div class="t3"></div>`,
},
},
{
`<div><div class="t2"><div class="t3">`,
`div:not([class="t2"])`,
[]string{
`<div><div class="t2"><div class="t3"></div></div></div>`,
`<div class="t3"></div>`,
},
},
{
`<ol><li id=1><li id=2><li id=3></ol>`,
`li:nth-child(odd)`,
[]string{
`<li id="1"></li>`,
`<li id="3"></li>`,
},
},
{
`<ol><li id=1><li id=2><li id=3></ol>`,
`li:nth-child(even)`,
[]string{
`<li id="2"></li>`,
},
},
{
`<ol><li id=1><li id=2><li id=3></ol>`,
`li:nth-child(-n+2)`,
[]string{
`<li id="1"></li>`,
`<li id="2"></li>`,
},
},
{
`<ol><li id=1><li id=2><li id=3></ol>`,
`li:nth-child(3n+1)`,
[]string{
`<li id="1"></li>`,
},
},
{
`<ol><li id=1><li id=2><li id=3><li id=4></ol>`,
`li:nth-last-child(odd)`,
[]string{
`<li id="2"></li>`,
`<li id="4"></li>`,
},
},
{
`<ol><li id=1><li id=2><li id=3><li id=4></ol>`,
`li:nth-last-child(even)`,
[]string{
`<li id="1"></li>`,
`<li id="3"></li>`,
},
},
{
`<ol><li id=1><li id=2><li id=3><li id=4></ol>`,
`li:nth-last-child(-n+2)`,
[]string{
`<li id="3"></li>`,
`<li id="4"></li>`,
},
},
{
`<ol><li id=1><li id=2><li id=3><li id=4></ol>`,
`li:nth-last-child(3n+1)`,
[]string{
`<li id="1"></li>`,
`<li id="4"></li>`,
},
},
{
`<p>some text <span id="1">and a span</span><span id="2"> and another</span></p>`,
`span:first-child`,
[]string{
`<span id="1">and a span</span>`,
},
},
{
`<span>a span</span> and some text`,
`span:last-child`,
[]string{
`<span>a span</span>`,
},
},
{
`<address></address><p id=1><p id=2>`,
`p:nth-of-type(2)`,
[]string{
`<p id="2"></p>`,
},
},
{
`<address></address><p id=1><p id=2></p><a>`,
`p:nth-last-of-type(2)`,
[]string{
`<p id="1"></p>`,
},
},
{
`<address></address><p id=1><p id=2></p><a>`,
`p:last-of-type`,
[]string{
`<p id="2"></p>`,
},
},
{
`<address></address><p id=1><p id=2></p><a>`,
`p:first-of-type`,
[]string{
`<p id="1"></p>`,
},
},
{
`<div><p id="1"></p><a></a></div><div><p id="2"></p></div>`,
`p:only-child`,
[]string{
`<p id="2"></p>`,
},
},
{
`<div><p id="1"></p><a></a></div><div><p id="2"></p><p id="3"></p></div>`,
`p:only-of-type`,
[]string{
`<p id="1"></p>`,
},
},
{
`<p id="1"><!-- --><p id="2">Hello<p id="3"><span>`,
`:empty`,
[]string{
`<head></head>`,
`<p id="1"><!-- --></p>`,
`<span></span>`,
},
},
{
`<div><p id="1"><table><tr><td><p id="2"></table></div><p id="3">`,
`div p`,
[]string{
`<p id="1"><table><tbody><tr><td><p id="2"></p></td></tr></tbody></table></p>`,
`<p id="2"></p>`,
},
},
{
`<div><p id="1"><table><tr><td><p id="2"></table></div><p id="3">`,
`div table p`,
[]string{
`<p id="2"></p>`,
},
},
{
`<div><p id="1"><div><p id="2"></div><table><tr><td><p id="3"></table></div>`,
`div > p`,
[]string{
`<p id="1"></p>`,
`<p id="2"></p>`,
},
},
{
`<p id="1"><p id="2"></p><address></address><p id="3">`,
`p ~ p`,
[]string{
`<p id="2"></p>`,
`<p id="3"></p>`,
},
},
{
`<p id="1"></p>
<!--comment-->
<p id="2"></p><address></address><p id="3">`,
`p + p`,
[]string{
`<p id="2"></p>`,
},
},
{
`<ul><li></li><li></li></ul><p>`,
`li, p`,
[]string{
"<li></li>",
"<li></li>",
"<p></p>",
},
},
{
`<p id="1"><p id="2"></p><address></address><p id="3">`,
`p +/*This is a comment*/ p`,
[]string{
`<p id="2"></p>`,
},
},
{
`<p>Text block that <span>wraps inner text</span> and continues</p>`,
`p:contains("that wraps")`,
[]string{
`<p>Text block that <span>wraps inner text</span> and continues</p>`,
},
},
{
`<p>Text block that <span>wraps inner text</span> and continues</p>`,
`p:containsOwn("that wraps")`,
[]string{},
},
{
`<p>Text block that <span>wraps inner text</span> and continues</p>`,
`:containsOwn("inner")`,
[]string{
`<span>wraps inner text</span>`,
},
},
{
`<p>Text block that <span>wraps inner text</span> and continues</p>`,
`p:containsOwn("block")`,
[]string{
`<p>Text block that <span>wraps inner text</span> and continues</p>`,
},
},
{
`<div id="d1"><p id="p1"><span>text content</span></p></div><div id="d2"/>`,
`div:has(#p1)`,
[]string{
`<div id="d1"><p id="p1"><span>text content</span></p></div>`,
},
},
{
`<div id="d1"><p id="p1"><span>contents 1</span></p></div>
<div id="d2"><p>contents <em>2</em></p></div>`,
`div:has(:containsOwn("2"))`,
[]string{
`<div id="d2"><p>contents <em>2</em></p></div>`,
},
},
{
`<body><div id="d1"><p id="p1"><span>contents 1</span></p></div>
<div id="d2"><p id="p2">contents <em>2</em></p></div></body>`,
`body :has(:containsOwn("2"))`,
[]string{
`<div id="d2"><p id="p2">contents <em>2</em></p></div>`,
`<p id="p2">contents <em>2</em></p>`,
},
},
{
`<body><div id="d1"><p id="p1"><span>contents 1</span></p></div>
<div id="d2"><p id="p2">contents <em>2</em></p></div></body>`,
`body :haschild(:containsOwn("2"))`,
[]string{
`<p id="p2">contents <em>2</em></p>`,
},
},
{
`<p id="p1">0123456789</p><p id="p2">abcdef</p><p id="p3">0123ABCD</p>`,
`p:matches([\d])`,
[]string{
`<p id="p1">0123456789</p>`,
`<p id="p3">0123ABCD</p>`,
},
},
{
`<p id="p1">0123456789</p><p id="p2">abcdef</p><p id="p3">0123ABCD</p>`,
`p:matches([a-z])`,
[]string{
`<p id="p2">abcdef</p>`,
},
},
{
`<p id="p1">0123456789</p><p id="p2">abcdef</p><p id="p3">0123ABCD</p>`,
`p:matches([a-zA-Z])`,
[]string{
`<p id="p2">abcdef</p>`,
`<p id="p3">0123ABCD</p>`,
},
},
{
`<p id="p1">0123456789</p><p id="p2">abcdef</p><p id="p3">0123ABCD</p>`,
`p:matches([^\d])`,
[]string{
`<p id="p2">abcdef</p>`,
`<p id="p3">0123ABCD</p>`,
},
},
{
`<p id="p1">0123456789</p><p id="p2">abcdef</p><p id="p3">0123ABCD</p>`,
`p:matches(^(0|a))`,
[]string{
`<p id="p1">0123456789</p>`,
`<p id="p2">abcdef</p>`,
`<p id="p3">0123ABCD</p>`,
},
},
{
`<p id="p1">0123456789</p><p id="p2">abcdef</p><p id="p3">0123ABCD</p>`,
`p:matches(^\d+$)`,
[]string{
`<p id="p1">0123456789</p>`,
},
},
{
`<p id="p1">0123456789</p><p id="p2">abcdef</p><p id="p3">0123ABCD</p>`,
`p:not(:matches(^\d+$))`,
[]string{
`<p id="p2">abcdef</p>`,
`<p id="p3">0123ABCD</p>`,
},
},
{
`<div><p id="p1">01234<em>567</em>89</p><div>`,
`div :matchesOwn(^\d+$)`,
[]string{
`<p id="p1">01234<em>567</em>89</p>`,
`<em>567</em>`,
},
},
{
`<ul>
<li><a id="a1" href="http://www.google.com/finance"></a>
<li><a id="a2" href="http://finance.yahoo.com/"></a>
<li><a id="a2" href="http://finance.untrusted.com/"/>
<li><a id="a3" href="https://www.google.com/news"/>
<li><a id="a4" href="http://news.yahoo.com"/>
</ul>`,
`[href#=(fina)]:not([href#=(\/\/[^\/]+untrusted)])`,
[]string{
`<a id="a1" href="http://www.google.com/finance"></a>`,
`<a id="a2" href="http://finance.yahoo.com/"></a>`,
},
},
{
`<ul>
<li><a id="a1" href="http://www.google.com/finance"/>
<li><a id="a2" href="http://finance.yahoo.com/"/>
<li><a id="a3" href="https://www.google.com/news"></a>
<li><a id="a4" href="http://news.yahoo.com"/>
</ul>`,
`[href#=(^https:\/\/[^\/]*\/?news)]`,
[]string{
`<a id="a3" href="https://www.google.com/news"></a>`,
},
},
{
`<form>
<label>Username <input type="text" name="username" /></label>
<label>Password <input type="password" name="password" /></label>
<label>Country
<select name="country">
<option value="ca">Canada</option>
<option value="us">United States</option>
</select>
</label>
<label>Bio <textarea name="bio"></textarea></label>
<button>Sign up</button>
</form>`,
`:input`,
[]string{
`<input type="text" name="username"/>`,
`<input type="password" name="password"/>`,
`<select name="country">
<option value="ca">Canada</option>
<option value="us">United States</option>
</select>`,
`<textarea name="bio"></textarea>`,
`<button>Sign up</button>`,
},
},
{
`<html><head></head><body></body></html>`,
":root",
[]string{
"<html><head></head><body></body></html>",
},
},
{
`<html><head></head><body></body></html>`,
"*:root",
[]string{
"<html><head></head><body></body></html>",
},
},
{
`<html><head></head><body></body></html>`,
"*:root:first-child",
[]string{},
},
{
`<html><head></head><body></body></html>`,
"*:root:nth-child(1)",
[]string{},
},
{
`<html><head></head><body><a href="http://www.foo.com"></a></body></html>`,
"a:not(:root)",
[]string{
`<a href="http://www.foo.com"></a>`,
},
},
}
func TestSelectors(t *testing.T) {
for _, test := range selectorTests {
s, err := Compile(test.selector)
if err != nil {
t.Errorf("error compiling %q: %s", test.selector, err)
continue
}
doc, err := html.Parse(strings.NewReader(test.HTML))
if err != nil {
t.Errorf("error parsing %q: %s", test.HTML, err)
continue
}
matches := s.MatchAll(doc)
if len(matches) != len(test.results) {
t.Errorf("selector %s wanted %d elements, got %d instead", test.selector, len(test.results), len(matches))
continue
}
for i, m := range matches {
got := nodeString(m)
if got != test.results[i] {
t.Errorf("selector %s wanted %s, got %s instead", test.selector, test.results[i], got)
}
}
firstMatch := s.MatchFirst(doc)
if len(test.results) == 0 {
if firstMatch != nil {
t.Errorf("MatchFirst: selector %s want nil, got %s", test.selector, nodeString(firstMatch))
}
} else {
got := nodeString(firstMatch)
if got != test.results[0] {
t.Errorf("MatchFirst: selector %s want %s, got %s", test.selector, test.results[0], got)
}
}
}
}

17
vendor/github.com/antchfx/htmlquery/LICENSE generated vendored Normal file
View file

@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

93
vendor/github.com/antchfx/htmlquery/README.md generated vendored Normal file
View file

@ -0,0 +1,93 @@
htmlquery
====
[![Build Status](https://travis-ci.org/antchfx/htmlquery.svg?branch=master)](https://travis-ci.org/antchfx/htmlquery)
[![Coverage Status](https://coveralls.io/repos/github/antchfx/htmlquery/badge.svg?branch=master)](https://coveralls.io/github/antchfx/htmlquery?branch=master)
[![GoDoc](https://godoc.org/github.com/antchfx/htmlquery?status.svg)](https://godoc.org/github.com/antchfx/htmlquery)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/htmlquery)](https://goreportcard.com/report/github.com/antchfx/htmlquery)
Overview
====
htmlquery is an XPath query package for HTML, lets you extract data or evaluate from HTML documents by an XPath expression.
Installation
====
> $ go get github.com/antchfx/htmlquery
Getting Started
====
#### Load HTML document from URL.
```go
doc, err := htmlquery.LoadURL("http://example.com/")
```
#### Load HTML document from string.
```go
s := `<html>....</html>`
doc, err := htmlquery.Parse(strings.NewReader(s))
```
#### Find all A elements.
```go
list := htmlquery.Find(doc, "//a")
```
#### Find all A elements that have `href` attribute.
```go
list := range htmlquery.Find(doc, "//a[@href]")
```
#### Find all A elements and only get `href` attribute self.
```go
list := range htmlquery.Find(doc, "//a/@href")
```
### Find the third A element.
```go
a := htmlquery.FindOne(doc, "//a[3]")
```
#### Evaluate the number of all IMG element.
```go
expr, _ := xpath.Compile("count(//img)")
v := expr.Evaluate(htmlquery.CreateXPathNavigator(doc)).(float64)
fmt.Printf("total count is %f", v)
```
Quick Tutorial
===
```go
func main() {
doc, err := htmlquery.LoadURL("https://www.bing.com/search?q=golang")
if err != nil {
panic(err)
}
// Find all news item.
for i, n := range htmlquery.Find(doc, "//ol/li") {
a := htmlquery.FindOne(n, "//a")
fmt.Printf("%d %s(%s)\n", i, htmlquery.InnerText(a), htmlquery.SelectAttr(a, "href"))
}
}
```
List of supported XPath query packages
===
|Name |Description |
|--------------------------|----------------|
|[htmlquery](https://github.com/antchfx/htmlquery) | XPath query package for the HTML document|
|[xmlquery](https://github.com/antchfx/xmlquery) | XPath query package for the XML document|
|[jsonquery](https://github.com/antchfx/jsonquery) | XPath query package for the JSON document|
Questions
===
Please let me know if you have any questions.

299
vendor/github.com/antchfx/htmlquery/query.go generated vendored Normal file
View file

@ -0,0 +1,299 @@
/*
Package htmlquery provides extract data from HTML documents using XPath expression.
*/
package htmlquery
import (
"bytes"
"fmt"
"io"
"net/http"
"github.com/antchfx/xpath"
"golang.org/x/net/html"
"golang.org/x/net/html/charset"
)
var _ xpath.NodeNavigator = &NodeNavigator{}
// CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node.
func CreateXPathNavigator(top *html.Node) *NodeNavigator {
return &NodeNavigator{curr: top, root: top, attr: -1}
}
// Find searches the html.Node that matches by the specified XPath expr.
func Find(top *html.Node, expr string) []*html.Node {
var elems []*html.Node
exp, err := xpath.Compile(expr)
if err != nil {
panic(err)
}
t := exp.Select(CreateXPathNavigator(top))
for t.MoveNext() {
elems = append(elems, getCurrentNode(t))
}
return elems
}
// FindOne searches the html.Node that matches by the specified XPath expr,
// and returns first element of matched html.Node.
func FindOne(top *html.Node, expr string) *html.Node {
var elem *html.Node
exp, err := xpath.Compile(expr)
if err != nil {
panic(err)
}
t := exp.Select(CreateXPathNavigator(top))
if t.MoveNext() {
elem = getCurrentNode(t)
}
return elem
}
// FindEach searches the html.Node and calls functions cb.
func FindEach(top *html.Node, expr string, cb func(int, *html.Node)) {
exp, err := xpath.Compile(expr)
if err != nil {
panic(err)
}
t := exp.Select(CreateXPathNavigator(top))
i := 0
for t.MoveNext() {
cb(i, getCurrentNode(t))
i++
}
}
// LoadURL loads the HTML document from the specified URL.
func LoadURL(url string) (*html.Node, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
r, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
if err != nil {
return nil, err
}
return html.Parse(r)
}
func getCurrentNode(it *xpath.NodeIterator) *html.Node {
n := it.Current().(*NodeNavigator)
if n.NodeType() == xpath.AttributeNode {
childNode := &html.Node{
Type: html.TextNode,
Data: n.Value(),
}
return &html.Node{
Type: html.ElementNode,
Data: n.LocalName(),
FirstChild: childNode,
LastChild: childNode,
}
}
return n.curr
}
// Parse returns the parse tree for the HTML from the given Reader.
func Parse(r io.Reader) (*html.Node, error) {
return html.Parse(r)
}
// InnerText returns the text between the start and end tags of the object.
func InnerText(n *html.Node) string {
var output func(*bytes.Buffer, *html.Node)
output = func(buf *bytes.Buffer, n *html.Node) {
switch n.Type {
case html.TextNode:
buf.WriteString(n.Data)
return
case html.CommentNode:
return
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
output(buf, child)
}
}
var buf bytes.Buffer
output(&buf, n)
return buf.String()
}
// SelectAttr returns the attribute value with the specified name.
func SelectAttr(n *html.Node, name string) (val string) {
if n == nil {
return
}
if n.Type == html.ElementNode && n.Parent == nil && name == n.Data {
return InnerText(n)
}
for _, attr := range n.Attr {
if attr.Key == name {
val = attr.Val
break
}
}
return
}
// OutputHTML returns the text including tags name.
func OutputHTML(n *html.Node, self bool) string {
var buf bytes.Buffer
if self {
html.Render(&buf, n)
} else {
for n := n.FirstChild; n != nil; n = n.NextSibling {
html.Render(&buf, n)
}
}
return buf.String()
}
type NodeNavigator struct {
root, curr *html.Node
attr int
}
func (h *NodeNavigator) Current() *html.Node {
return h.curr
}
func (h *NodeNavigator) NodeType() xpath.NodeType {
switch h.curr.Type {
case html.CommentNode:
return xpath.CommentNode
case html.TextNode:
return xpath.TextNode
case html.DocumentNode:
return xpath.RootNode
case html.ElementNode:
if h.attr != -1 {
return xpath.AttributeNode
}
return xpath.ElementNode
case html.DoctypeNode:
// ignored <!DOCTYPE HTML> declare and as Root-Node type.
return xpath.RootNode
}
panic(fmt.Sprintf("unknown HTML node type: %v", h.curr.Type))
}
func (h *NodeNavigator) LocalName() string {
if h.attr != -1 {
return h.curr.Attr[h.attr].Key
}
return h.curr.Data
}
func (*NodeNavigator) Prefix() string {
return ""
}
func (h *NodeNavigator) Value() string {
switch h.curr.Type {
case html.CommentNode:
return h.curr.Data
case html.ElementNode:
if h.attr != -1 {
return h.curr.Attr[h.attr].Val
}
return InnerText(h.curr)
case html.TextNode:
return h.curr.Data
}
return ""
}
func (h *NodeNavigator) Copy() xpath.NodeNavigator {
n := *h
return &n
}
func (h *NodeNavigator) MoveToRoot() {
h.curr = h.root
}
func (h *NodeNavigator) MoveToParent() bool {
if h.attr != -1 {
h.attr = -1
return true
} else if node := h.curr.Parent; node != nil {
h.curr = node
return true
}
return false
}
func (h *NodeNavigator) MoveToNextAttribute() bool {
if h.attr >= len(h.curr.Attr)-1 {
return false
}
h.attr++
return true
}
func (h *NodeNavigator) MoveToChild() bool {
if h.attr != -1 {
return false
}
if node := h.curr.FirstChild; node != nil {
h.curr = node
return true
}
return false
}
func (h *NodeNavigator) MoveToFirst() bool {
if h.attr != -1 || h.curr.PrevSibling == nil {
return false
}
for {
node := h.curr.PrevSibling
if node == nil {
break
}
h.curr = node
}
return true
}
func (h *NodeNavigator) String() string {
return h.Value()
}
func (h *NodeNavigator) MoveToNext() bool {
if h.attr != -1 {
return false
}
if node := h.curr.NextSibling; node != nil {
h.curr = node
return true
}
return false
}
func (h *NodeNavigator) MoveToPrevious() bool {
if h.attr != -1 {
return false
}
if node := h.curr.PrevSibling; node != nil {
h.curr = node
return true
}
return false
}
func (h *NodeNavigator) MoveTo(other xpath.NodeNavigator) bool {
node, ok := other.(*NodeNavigator)
if !ok || node.root != h.root {
return false
}
h.curr = node.curr
h.attr = node.attr
return true
}

126
vendor/github.com/antchfx/htmlquery/query_test.go generated vendored Normal file
View file

@ -0,0 +1,126 @@
package htmlquery
import (
"strings"
"testing"
"github.com/antchfx/xpath"
"golang.org/x/net/html"
)
const htmlSample = `<!DOCTYPE html><html lang="en-US">
<head>
<title>Hello,World!</title>
</head>
<body>
<div class="container">
<header>
<!-- Logo -->
<h1>City Gallery</h1>
</header>
<nav>
<ul>
<li><a href="/London">London</a></li>
<li><a href="/Paris">Paris</a></li>
<li><a href="/Tokyo">Tokyo</a></li>
</ul>
</nav>
<article>
<h1>London</h1>
<img src="pic_mountain.jpg" alt="Mountain View" style="width:304px;height:228px;">
<p>London is the capital city of England. It is the most populous city in the United Kingdom, with a metropolitan area of over 13 million inhabitants.</p>
<p>Standing on the River Thames, London has been a major settlement for two millennia, its history going back to its founding by the Romans, who named it Londinium.</p>
</article>
<footer>Copyright &copy; W3Schools.com</footer>
</div>
</body>
</html>
`
var testDoc = loadHTML(htmlSample)
func TestHttpLoad(t *testing.T) {
doc, err := LoadURL("http://www.bing.com")
if err != nil {
t.Fatal(err)
}
if doc == nil {
t.Fatal("doc is nil")
}
}
func TestNavigator(t *testing.T) {
top := FindOne(testDoc, "//html")
nav := &NodeNavigator{curr: top, root: top, attr: -1}
nav.MoveToChild() // HEAD
nav.MoveToNext()
if nav.NodeType() != xpath.TextNode {
t.Fatalf("expectd node type is TextNode,but got %vs", nav.NodeType())
}
nav.MoveToNext() // <BODY>
if nav.Value() != InnerText(FindOne(testDoc, "//body")) {
t.Fatal("body not equal")
}
nav.MoveToPrevious() //
nav.MoveToParent() //<HTML>
if nav.curr != top {
t.Fatal("current node is not html node")
}
nav.MoveToNextAttribute()
if nav.LocalName() != "lang" {
t.Fatal("node not move to lang attribute")
}
nav.MoveToParent()
nav.MoveToFirst() // <!DOCTYPE html>
if nav.curr.Type != html.DoctypeNode {
t.Fatalf("expected node type is DoctypeNode,but got %d", nav.curr.Type)
}
}
func TestXPath(t *testing.T) {
node := FindOne(testDoc, "//html")
if SelectAttr(node, "lang") != "en-US" {
t.Fatal("//html[@lang] != en-Us")
}
var c int
FindEach(testDoc, "//li", func(i int, node *html.Node) {
c++
})
if c != len(Find(testDoc, "//li")) {
t.Fatal("li node count != 3")
}
node = FindOne(testDoc, "//header")
if strings.Index(InnerText(node), "Logo") > 0 {
t.Fatal("InnerText() have comment node text")
}
if strings.Index(OutputHTML(node, true), "Logo") == -1 {
t.Fatal("OutputHTML() shoud have comment node text")
}
link := FindOne(testDoc, "//a[1]/@href")
if link == nil {
t.Fatal("link is nil")
}
if v := InnerText(link); v != "/London" {
t.Fatalf("expect value is /London, but got %s", v)
}
}
func TestXPathCdUp(t *testing.T) {
doc := loadHTML(`<html><b attr="1"></b></html>`)
node := FindOne(doc, "//b/@attr/..")
t.Logf("node = %#v", node)
if node == nil || node.Data != "b" {
t.Fatal("//b/@id/.. != <b></b>")
}
}
func loadHTML(str string) *html.Node {
node, err := Parse(strings.NewReader(str))
if err != nil {
panic(err)
}
return node
}

17
vendor/github.com/antchfx/xmlquery/LICENSE generated vendored Normal file
View file

@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

169
vendor/github.com/antchfx/xmlquery/README.md generated vendored Normal file
View file

@ -0,0 +1,169 @@
xmlquery
====
[![Build Status](https://travis-ci.org/antchfx/xmlquery.svg?branch=master)](https://travis-ci.org/antchfx/xmlquery)
[![Coverage Status](https://coveralls.io/repos/github/antchfx/xmlquery/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xmlquery?branch=master)
[![GoDoc](https://godoc.org/github.com/antchfx/xmlquery?status.svg)](https://godoc.org/github.com/antchfx/xmlquery)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xmlquery)](https://goreportcard.com/report/github.com/antchfx/xmlquery)
Overview
===
xmlquery is an XPath query package for XML document, lets you extract data or evaluate from XML documents by an XPath expression.
Installation
====
> $ go get github.com/antchfx/xmlquery
Getting Started
===
#### Parse a XML from URL.
```go
doc, err := xmlquery.LoadURL("http://www.example.com/sitemap.xml")
```
#### Parse a XML from string.
```go
s := `<?xml version="1.0" encoding="utf-8"?><rss version="2.0"></rss>`
doc, err := xmlquery.Parse(strings.NewReader(s))
```
#### Parse a XML from io.Reader.
```go
f, err := os.Open("../books.xml")
doc, err := xmlquery.Parse(f)
```
#### Find authors of all books in the bookstore.
```go
list := xmlquery.Find(doc, "//book//author")
// or
list := xmlquery.Find(doc, "//author")
```
#### Find the second book.
```go
book := xmlquery.FindOne(doc, "//book[2]")
```
#### Find all book elements and only get `id` attribute self. (New Feature)
```go
list := xmlquery.Find(doc,"//book/@id")
```
#### Find all books with id is bk104.
```go
list := xmlquery.Find(doc, "//book[@id='bk104']")
```
#### Find all books that price less than 5.
```go
list := xmlquery.Find(doc, "//book[price<5]")
```
#### Evaluate the total price of all books.
```go
expr, err := xpath.Compile("sum(//book/price)")
price := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64)
fmt.Printf("total price: %f\n", price)
```
#### Evaluate the number of all books element.
```go
expr, err := xpath.Compile("count(//book)")
price := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64)
```
#### Create XML document.
```go
doc := &xmlquery.Node{
Type: xmlquery.DeclarationNode,
Data: "xml",
Attr: []xml.Attr{
xml.Attr{Name: xml.Name{Local: "version"}, Value: "1.0"},
},
}
root := &xmlquery.Node{
Data: "rss",
Type: xmlquery.ElementNode,
}
doc.FirstChild = root
channel := &xmlquery.Node{
Data: "channel",
Type: xmlquery.ElementNode,
}
root.FirstChild = channel
title := &xmlquery.Node{
Data: "title",
Type: xmlquery.ElementNode,
}
title_text := &xmlquery.Node{
Data: "W3Schools Home Page",
Type: xmlquery.TextNode,
}
title.FirstChild = title_text
channel.FirstChild = title
fmt.Println(doc.OutputXML(true))
// <?xml version="1.0"?><rss><channel><title>W3Schools Home Page</title></channel></rss>
```
Quick Tutorial
===
```go
func main(){
s := `<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">
<channel>
<title>W3Schools Home Page</title>
<link>https://www.w3schools.com</link>
<description>Free web building tutorials</description>
<item>
<title>RSS Tutorial</title>
<link>https://www.w3schools.com/xml/xml_rss.asp</link>
<description>New RSS tutorial on W3Schools</description>
</item>
<item>
<title>XML Tutorial</title>
<link>https://www.w3schools.com/xml</link>
<description>New XML tutorial on W3Schools</description>
</item>
</channel>
</rss>`
doc, err := Parse(strings.NewReader(s))
if err != nil {
panic(err)
}
channel := FindOne(doc, "//channel")
fmt.Printf("title: %s\n", channel.SelectElement("title").InnerText())
fmt.Printf("link: %s\n", channel.SelectElement("link").InnerText())
for i, n := range Find(doc, "//item") {
fmt.Printf("#%d %s\n", i, n.SelectElement("title"))
}
}
```
List of supported XPath query packages
===
|Name |Description |
|--------------------------|----------------|
|[htmlquery](https://github.com/antchfx/htmlquery) | XPath query package for the HTML document|
|[xmlquery](https://github.com/antchfx/xmlquery) | XPath query package for the XML document|
|[jsonquery](https://github.com/antchfx/jsonquery) | XPath query package for the JSON document|
Questions
===
Please let me know if you have any questions

121
vendor/github.com/antchfx/xmlquery/books.xml generated vendored Normal file
View file

@ -0,0 +1,121 @@
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" ?>
<bookstore specialty="novel">
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<book id="bk103">
<author>Corets, Eva</author>
<title>Maeve Ascendant</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-11-17</publish_date>
<description>After the collapse of a nanotechnology
society in England, the young survivors lay the
foundation for a new society.</description>
</book>
<book id="bk104">
<author>Corets, Eva</author>
<title>Oberon's Legacy</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-03-10</publish_date>
<description>In post-apocalypse England, the mysterious
agent known only as Oberon helps to create a new life
for the inhabitants of London. Sequel to Maeve
Ascendant.</description>
</book>
<book id="bk105">
<author>Corets, Eva</author>
<title>The Sundered Grail</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2001-09-10</publish_date>
<description>The two daughters of Maeve, half-sisters,
battle one another for control of England. Sequel to
Oberon's Legacy.</description>
</book>
<book id="bk106">
<author>Randall, Cynthia</author>
<title>Lover Birds</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-09-02</publish_date>
<description>When Carla meets Paul at an ornithology
conference, tempers fly as feathers get ruffled.</description>
</book>
<book id="bk107">
<author>Thurman, Paula</author>
<title>Splish Splash</title>
<genre>Romance</genre>
<price>4.95</price>
<publish_date>2000-11-02</publish_date>
<description>A deep sea diver finds true love twenty
thousand leagues beneath the sea.</description>
</book>
<book id="bk108">
<author>Knorr, Stefan</author>
<title>Creepy Crawlies</title>
<genre>Horror</genre>
<price>4.95</price>
<publish_date>2000-12-06</publish_date>
<description>An anthology of horror stories about roaches,
centipedes, scorpions and other insects.</description>
</book>
<book id="bk109">
<author>Kress, Peter</author>
<title>Paradox Lost</title>
<genre>Science Fiction</genre>
<price>6.95</price>
<publish_date>2000-11-02</publish_date>
<description>After an inadvertant trip through a Heisenberg
Uncertainty Device, James Salway discovers the problems
of being quantum.</description>
</book>
<book id="bk110">
<author>O'Brien, Tim</author>
<title>Microsoft .NET: The Programming Bible</title>
<genre>Computer</genre>
<price>36.95</price>
<publish_date>2000-12-09</publish_date>
<description>Microsoft's .NET initiative is explored in
detail in this deep programmer's reference.</description>
</book>
<book id="bk111">
<author>O'Brien, Tim</author>
<title>MSXML3: A Comprehensive Guide</title>
<genre>Computer</genre>
<price>36.95</price>
<publish_date>2000-12-01</publish_date>
<description>The Microsoft MSXML3 parser is covered in
detail, with attention to XML DOM interfaces, XSLT processing,
SAX and more.</description>
</book>
<book id="bk112">
<author>Galos, Mike</author>
<title>Visual Studio 7: A Comprehensive Guide</title>
<genre>Computer</genre>
<price>49.95</price>
<publish_date>2001-04-16</publish_date>
<description>Microsoft Visual Studio 7 is explored in depth,
looking at how Visual Basic, Visual C++, C#, and ASP+ are
integrated into a comprehensive development
environment.</description>
</book>
</bookstore>

281
vendor/github.com/antchfx/xmlquery/node.go generated vendored Normal file
View file

@ -0,0 +1,281 @@
package xmlquery
import (
"bytes"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"strings"
"golang.org/x/net/html/charset"
)
// A NodeType is the type of a Node.
type NodeType uint
const (
// DocumentNode is a document object that, as the root of the document tree,
// provides access to the entire XML document.
DocumentNode NodeType = iota
// DeclarationNode is the document type declaration, indicated by the following
// tag (for example, <!DOCTYPE...> ).
DeclarationNode
// ElementNode is an element (for example, <item> ).
ElementNode
// TextNode is the text content of a node.
TextNode
// CommentNode a comment (for example, <!-- my comment --> ).
CommentNode
// AttributeNode is an attribute of element.
AttributeNode
)
// A Node consists of a NodeType and some Data (tag name for
// element nodes, content for text) and are part of a tree of Nodes.
type Node struct {
Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node
Type NodeType
Data string
Prefix string
NamespaceURI string
Attr []xml.Attr
level int // node level in the tree
}
// InnerText returns the text between the start and end tags of the object.
func (n *Node) InnerText() string {
var output func(*bytes.Buffer, *Node)
output = func(buf *bytes.Buffer, n *Node) {
switch n.Type {
case TextNode:
buf.WriteString(n.Data)
return
case CommentNode:
return
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
output(buf, child)
}
}
var buf bytes.Buffer
output(&buf, n)
return buf.String()
}
func outputXML(buf *bytes.Buffer, n *Node) {
if n.Type == TextNode || n.Type == CommentNode {
xml.EscapeText(buf, []byte(strings.TrimSpace(n.Data)))
return
}
if n.Type == DeclarationNode {
buf.WriteString("<?" + n.Data)
} else {
if n.Prefix == "" {
buf.WriteString("<" + n.Data)
}else{
buf.WriteString("<" + n.Prefix + ":" + n.Data)
}
}
for _, attr := range n.Attr {
if attr.Name.Space != "" {
buf.WriteString(fmt.Sprintf(` %s:%s="%s"`, attr.Name.Space, attr.Name.Local, attr.Value))
} else {
buf.WriteString(fmt.Sprintf(` %s="%s"`, attr.Name.Local, attr.Value))
}
}
if n.Type == DeclarationNode {
buf.WriteString("?>")
} else {
buf.WriteString(">")
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
outputXML(buf, child)
}
if n.Type != DeclarationNode {
if n.Prefix == "" {
buf.WriteString(fmt.Sprintf("</%s>", n.Data))
}else{
buf.WriteString(fmt.Sprintf("</%s:%s>", n.Prefix, n.Data))
}
}
}
// OutputXML returns the text that including tags name.
func (n *Node) OutputXML(self bool) string {
var buf bytes.Buffer
if self {
outputXML(&buf, n)
} else {
for n := n.FirstChild; n != nil; n = n.NextSibling {
outputXML(&buf, n)
}
}
return buf.String()
}
func addAttr(n *Node, key, val string) {
var attr xml.Attr
if i := strings.Index(key, ":"); i > 0 {
attr = xml.Attr{
Name: xml.Name{Space: key[:i], Local: key[i+1:]},
Value: val,
}
} else {
attr = xml.Attr{
Name: xml.Name{Local: key},
Value: val,
}
}
n.Attr = append(n.Attr, attr)
}
func addChild(parent, n *Node) {
n.Parent = parent
if parent.FirstChild == nil {
parent.FirstChild = n
} else {
parent.LastChild.NextSibling = n
n.PrevSibling = parent.LastChild
}
parent.LastChild = n
}
func addSibling(sibling, n *Node) {
for t := sibling.NextSibling; t != nil; t = t.NextSibling {
sibling = t
}
n.Parent = sibling.Parent
sibling.NextSibling = n
n.PrevSibling = sibling
if sibling.Parent != nil {
sibling.Parent.LastChild = n
}
}
// LoadURL loads the XML document from the specified URL.
func LoadURL(url string) (*Node, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return parse(resp.Body)
}
func parse(r io.Reader) (*Node, error) {
var (
decoder = xml.NewDecoder(r)
doc = &Node{Type: DocumentNode}
space2prefix = make(map[string]string)
level = 0
)
decoder.CharsetReader = charset.NewReaderLabel
prev := doc
for {
tok, err := decoder.Token()
switch {
case err == io.EOF:
goto quit
case err != nil:
return nil, err
}
switch tok := tok.(type) {
case xml.StartElement:
if level == 0 {
// mising XML declaration
node := &Node{Type: DeclarationNode, Data: "xml", level: 1}
addChild(prev, node)
level = 1
prev = node
}
// https://www.w3.org/TR/xml-names/#scoping-defaulting
for _, att := range tok.Attr {
if att.Name.Local == "xmlns" {
space2prefix[att.Value] = ""
} else if att.Name.Space == "xmlns" {
space2prefix[att.Value] = att.Name.Local
}
}
if tok.Name.Space != "" {
if _, found := space2prefix[tok.Name.Space]; !found {
return nil, errors.New("xmlquery: invalid XML document, namespace is missing")
}
}
node := &Node{
Type: ElementNode,
Data: tok.Name.Local,
Prefix: space2prefix[tok.Name.Space],
NamespaceURI: tok.Name.Space,
Attr: tok.Attr,
level: level,
}
//fmt.Println(fmt.Sprintf("start > %s : %d", node.Data, level))
if level == prev.level {
addSibling(prev, node)
} else if level > prev.level {
addChild(prev, node)
} else if level < prev.level {
for i := prev.level - level; i > 1; i-- {
prev = prev.Parent
}
addSibling(prev.Parent, node)
}
prev = node
level++
case xml.EndElement:
level--
case xml.CharData:
node := &Node{Type: TextNode, Data: string(tok), level: level}
if level == prev.level {
addSibling(prev, node)
} else if level > prev.level {
addChild(prev, node)
}
case xml.Comment:
node := &Node{Type: CommentNode, Data: string(tok), level: level}
if level == prev.level {
addSibling(prev, node)
} else if level > prev.level {
addChild(prev, node)
}
case xml.ProcInst: // Processing Instruction
if prev.Type != DeclarationNode {
level++
}
node := &Node{Type: DeclarationNode, Data: tok.Target, level: level}
pairs := strings.Split(string(tok.Inst), " ")
for _, pair := range pairs {
pair = strings.TrimSpace(pair)
if i := strings.Index(pair, "="); i > 0 {
addAttr(node, pair[:i], strings.Trim(pair[i+1:], `"`))
}
}
if level == prev.level {
addSibling(prev, node)
} else if level > prev.level {
addChild(prev, node)
}
prev = node
case xml.Directive:
}
}
quit:
return doc, nil
}
// Parse returns the parse tree for the XML from the given Reader.
func Parse(r io.Reader) (*Node, error) {
return parse(r)
}

308
vendor/github.com/antchfx/xmlquery/node_test.go generated vendored Normal file
View file

@ -0,0 +1,308 @@
package xmlquery
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func findNode(root *Node, name string) *Node {
node := root.FirstChild
for {
if node == nil || node.Data == name {
break
}
node = node.NextSibling
}
return node
}
func childNodes(root *Node, name string) []*Node {
var list []*Node
node := root.FirstChild
for {
if node == nil {
break
}
if node.Data == name {
list = append(list, node)
}
node = node.NextSibling
}
return list
}
func testNode(t *testing.T, n *Node, expected string) {
if n.Data != expected {
t.Fatalf("expected node name is %s,but got %s", expected, n.Data)
}
}
func testAttr(t *testing.T, n *Node, name, expected string) {
for _, attr := range n.Attr {
if attr.Name.Local == name && attr.Value == expected {
return
}
}
t.Fatalf("not found attribute %s in the node %s", name, n.Data)
}
func testValue(t *testing.T, val, expected string) {
if val != expected {
t.Fatalf("expected value is %s,but got %s", expected, val)
}
}
func TestLoadURL(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
s := `<?xml version="1.0"?>
<rss>
<title></title>
</rss>`
w.Header().Set("Content-Type", "text/xml")
w.Write([]byte(s))
}))
defer server.Close()
_, err := LoadURL(server.URL)
if err != nil {
t.Fatal(err)
}
}
func TestNamespaceURL(t *testing.T) {
s := `
<?xml version="1.0"?>
<rss version="2.0" xmlns="http://www.example.com/" xmlns:dc="https://purl.org/dc/elements/1.1/">
<!-- author -->
<dc:creator><![CDATA[Richard Lawler]]></dc:creator>
<dc:identifier>21|22021348</dc:identifier>
</rss>
`
doc, err := Parse(strings.NewReader(s))
if err != nil {
t.Fatal(err)
}
top := FindOne(doc, "//rss")
if top == nil {
t.Fatal("rss feed invalid")
}
node := FindOne(top, "dc:creator")
if node.Prefix != "dc" {
t.Fatalf("expected node prefix name is dc but is=%s", node.Prefix)
}
if node.NamespaceURI != "https://purl.org/dc/elements/1.1/" {
t.Fatalf("dc:creator != %s", node.NamespaceURI)
}
if strings.Index(top.InnerText(), "author") > 0 {
t.Fatalf("InnerText() include comment node text")
}
if strings.Index(top.OutputXML(true), "author") == -1 {
t.Fatal("OutputXML shoud include comment node,but not")
}
}
func TestMultipleProcInst(t *testing.T) {
s := `<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/rss2full.xsl"?>
<?xml-stylesheet type="text/css" media="screen" href="http://feeds.reuters.com/~d/styles/itemcontent.css"?>
<rss xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" version="2.0">
</rss>
`
doc, err := Parse(strings.NewReader(s))
if err != nil {
t.Fatal(err)
}
node := doc.FirstChild // <?xml ?>
if node.Data != "xml" {
t.Fatal("node.Data != xml")
}
node = node.NextSibling // New Line
node = node.NextSibling // <?xml-stylesheet?>
if node.Data != "xml-stylesheet" {
t.Fatal("node.Data != xml-stylesheet")
}
}
func TestParse(t *testing.T) {
s := `<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book>
<title lang="en">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="en">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>`
root, err := Parse(strings.NewReader(s))
if err != nil {
t.Error(err)
}
if root.Type != DocumentNode {
t.Fatal("top node of tree is not DocumentNode")
}
declarNode := root.FirstChild
if declarNode.Type != DeclarationNode {
t.Fatal("first child node of tree is not DeclarationNode")
}
if declarNode.Attr[0].Name.Local != "version" && declarNode.Attr[0].Value != "1.0" {
t.Fatal("version attribute not expected")
}
bookstore := root.LastChild
if bookstore.Data != "bookstore" {
t.Fatal("bookstore elem not found")
}
if bookstore.FirstChild.Data != "\n" {
t.Fatal("first child node of bookstore is not empty node(\n)")
}
books := childNodes(bookstore, "book")
if len(books) != 2 {
t.Fatalf("expected book element count is 2, but got %d", len(books))
}
// first book element
testNode(t, findNode(books[0], "title"), "title")
testAttr(t, findNode(books[0], "title"), "lang", "en")
testValue(t, findNode(books[0], "price").InnerText(), "29.99")
testValue(t, findNode(books[0], "title").InnerText(), "Harry Potter")
// second book element
testNode(t, findNode(books[1], "title"), "title")
testAttr(t, findNode(books[1], "title"), "lang", "en")
testValue(t, findNode(books[1], "price").InnerText(), "39.95")
testValue(t, books[0].OutputXML(true), `<book><title lang="en">Harry Potter</title><price>29.99</price></book>`)
}
func TestMissDeclaration(t *testing.T) {
s := `<AAA>
<BBB></BBB>
<CCC></CCC>
</AAA>`
doc, err := Parse(strings.NewReader(s))
if err != nil {
t.Fatal(err)
}
node := FindOne(doc, "//AAA")
if node == nil {
t.Fatal("//AAA is nil")
}
}
func TestMissingNamespace(t *testing.T) {
s := `<root>
<myns:child id="1">value 1</myns:child>
<myns:child id="2">value 2</myns:child>
</root>`
_, err := Parse(strings.NewReader(s))
if err == nil {
t.Fatal("err is nil, want got invalid XML document")
}
}
func TestTooNested(t *testing.T) {
s := `<?xml version="1.0" encoding="UTF-8"?>
<!-- comment here-->
<AAA>
<BBB>
<DDD>
<CCC>
<DDD/>
<EEE/>
</CCC>
</DDD>
</BBB>
<CCC>
<DDD>
<EEE>
<DDD>
<FFF/>
</DDD>
</EEE>
</DDD>
</CCC>
</AAA>`
root, err := Parse(strings.NewReader(s))
if err != nil {
t.Error(err)
}
aaa := findNode(root, "AAA")
if aaa == nil {
t.Fatal("AAA node not exists")
}
ccc := aaa.LastChild
if ccc.Data != "CCC" {
t.Fatalf("expected node is CCC,but got %s", ccc.Data)
}
bbb := ccc.PrevSibling
if bbb.Data != "BBB" {
t.Fatalf("expected node is bbb,but got %s", bbb.Data)
}
ddd := findNode(bbb, "DDD")
testNode(t, ddd, "DDD")
testNode(t, ddd.LastChild, "CCC")
}
func TestSelectElement(t *testing.T) {
s := `<?xml version="1.0" encoding="UTF-8"?>
<AAA>
<BBB id="1"/>
<CCC id="2">
<DDD/>
</CCC>
<CCC id="3">
<DDD/>
</CCC>
</AAA>`
root, err := Parse(strings.NewReader(s))
if err != nil {
t.Error(err)
}
version := root.FirstChild.SelectAttr("version")
if version != "1.0" {
t.Fatal("version!=1.0")
}
aaa := findNode(root, "AAA")
var n *Node
n = aaa.SelectElement("BBB")
if n == nil {
t.Fatalf("n is nil")
}
n = aaa.SelectElement("CCC")
if n == nil {
t.Fatalf("n is nil")
}
var ns []*Node
ns = aaa.SelectElements("CCC")
if len(ns) != 2 {
t.Fatalf("len(ns)!=2")
}
}
func TestEscapeOutputValue(t *testing.T) {
data := `<AAA>&lt;*&gt;</AAA>`
root, err := Parse(strings.NewReader(data))
if err != nil {
t.Error(err)
}
escapedInnerText := root.OutputXML(true)
if !strings.Contains(escapedInnerText, "&lt;*&gt;") {
t.Fatal("Inner Text has not been escaped")
}
}
func TestOutputXMLWithNamespacePrefix(t *testing.T) {
s := `<?xml version="1.0" encoding="UTF-8"?><S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"><S:Body></S:Body></S:Envelope>`
doc, _ := Parse(strings.NewReader(s))
if s != doc.OutputXML(false) {
t.Fatal("xml document missing some characters")
}
}

256
vendor/github.com/antchfx/xmlquery/query.go generated vendored Normal file
View file

@ -0,0 +1,256 @@
/*
Package xmlquery provides extract data from XML documents using XPath expression.
*/
package xmlquery
import (
"fmt"
"strings"
"github.com/antchfx/xpath"
)
// SelectElements finds child elements with the specified name.
func (n *Node) SelectElements(name string) []*Node {
return Find(n, name)
}
// SelectElement finds child elements with the specified name.
func (n *Node) SelectElement(name string) *Node {
return FindOne(n, name)
}
// SelectAttr returns the attribute value with the specified name.
func (n *Node) SelectAttr(name string) string {
if n.Type == AttributeNode {
if n.Data == name {
return n.InnerText()
}
return ""
}
var local, space string
local = name
if i := strings.Index(name, ":"); i > 0 {
space = name[:i]
local = name[i+1:]
}
for _, attr := range n.Attr {
if attr.Name.Local == local && attr.Name.Space == space {
return attr.Value
}
}
return ""
}
var _ xpath.NodeNavigator = &NodeNavigator{}
// CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node.
func CreateXPathNavigator(top *Node) *NodeNavigator {
return &NodeNavigator{curr: top, root: top, attr: -1}
}
func getCurrentNode(it *xpath.NodeIterator) *Node {
n := it.Current().(*NodeNavigator)
if n.NodeType() == xpath.AttributeNode {
childNode := &Node{
Type: TextNode,
Data: n.Value(),
}
return &Node{
Type: AttributeNode,
Data: n.LocalName(),
FirstChild: childNode,
LastChild: childNode,
}
}
return n.curr
}
// Find searches the Node that matches by the specified XPath expr.
func Find(top *Node, expr string) []*Node {
exp, err := xpath.Compile(expr)
if err != nil {
panic(err)
}
t := exp.Select(CreateXPathNavigator(top))
var elems []*Node
for t.MoveNext() {
elems = append(elems, getCurrentNode(t))
}
return elems
}
// FindOne searches the Node that matches by the specified XPath expr,
// and returns first element of matched.
func FindOne(top *Node, expr string) *Node {
exp, err := xpath.Compile(expr)
if err != nil {
panic(err)
}
t := exp.Select(CreateXPathNavigator(top))
var elem *Node
if t.MoveNext() {
elem = getCurrentNode(t)
}
return elem
}
// FindEach searches the html.Node and calls functions cb.
func FindEach(top *Node, expr string, cb func(int, *Node)) {
exp, err := xpath.Compile(expr)
if err != nil {
panic(err)
}
t := exp.Select(CreateXPathNavigator(top))
var i int
for t.MoveNext() {
cb(i, getCurrentNode(t))
i++
}
}
type NodeNavigator struct {
root, curr *Node
attr int
}
func (x *NodeNavigator) Current() *Node {
return x.curr
}
func (x *NodeNavigator) NodeType() xpath.NodeType {
switch x.curr.Type {
case CommentNode:
return xpath.CommentNode
case TextNode:
return xpath.TextNode
case DeclarationNode, DocumentNode:
return xpath.RootNode
case ElementNode:
if x.attr != -1 {
return xpath.AttributeNode
}
return xpath.ElementNode
}
panic(fmt.Sprintf("unknown XML node type: %v", x.curr.Type))
}
func (x *NodeNavigator) LocalName() string {
if x.attr != -1 {
return x.curr.Attr[x.attr].Name.Local
}
return x.curr.Data
}
func (x *NodeNavigator) Prefix() string {
if x.NodeType() == xpath.AttributeNode {
return ""
}
return x.curr.Prefix
}
func (x *NodeNavigator) Value() string {
switch x.curr.Type {
case CommentNode:
return x.curr.Data
case ElementNode:
if x.attr != -1 {
return x.curr.Attr[x.attr].Value
}
return x.curr.InnerText()
case TextNode:
return x.curr.Data
}
return ""
}
func (x *NodeNavigator) Copy() xpath.NodeNavigator {
n := *x
return &n
}
func (x *NodeNavigator) MoveToRoot() {
x.curr = x.root
}
func (x *NodeNavigator) MoveToParent() bool {
if x.attr != -1 {
x.attr = -1
return true
} else if node := x.curr.Parent; node != nil {
x.curr = node
return true
}
return false
}
func (x *NodeNavigator) MoveToNextAttribute() bool {
if x.attr >= len(x.curr.Attr)-1 {
return false
}
x.attr++
return true
}
func (x *NodeNavigator) MoveToChild() bool {
if x.attr != -1 {
return false
}
if node := x.curr.FirstChild; node != nil {
x.curr = node
return true
}
return false
}
func (x *NodeNavigator) MoveToFirst() bool {
if x.attr != -1 || x.curr.PrevSibling == nil {
return false
}
for {
node := x.curr.PrevSibling
if node == nil {
break
}
x.curr = node
}
return true
}
func (x *NodeNavigator) String() string {
return x.Value()
}
func (x *NodeNavigator) MoveToNext() bool {
if x.attr != -1 {
return false
}
if node := x.curr.NextSibling; node != nil {
x.curr = node
return true
}
return false
}
func (x *NodeNavigator) MoveToPrevious() bool {
if x.attr != -1 {
return false
}
if node := x.curr.PrevSibling; node != nil {
x.curr = node
return true
}
return false
}
func (x *NodeNavigator) MoveTo(other xpath.NodeNavigator) bool {
node, ok := other.(*NodeNavigator)
if !ok || node.root != x.root {
return false
}
x.curr = node.curr
x.attr = node.attr
return true
}

115
vendor/github.com/antchfx/xmlquery/query_test.go generated vendored Normal file
View file

@ -0,0 +1,115 @@
package xmlquery
import (
"strings"
"testing"
)
// https://msdn.microsoft.com/en-us/library/ms762271(v=vs.85).aspx
const xmlDoc = `
<?xml version="1.0"?>
<catalog>
<!-- book list-->
<book id="bk101">
<author>Gambardella, Matthew</author>
<title>XML Developer's Guide</title>
<genre>Computer</genre>
<price>44.95</price>
<publish_date>2000-10-01</publish_date>
<description>An in-depth look at creating applications
with XML.</description>
</book>
<book id="bk102">
<author>Ralls, Kim</author>
<title>Midnight Rain</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-12-16</publish_date>
<description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</description>
</book>
<book id="bk103">
<author>Corets, Eva</author>
<title>Maeve Ascendant</title>
<genre>Fantasy</genre>
<price>5.95</price>
<publish_date>2000-11-17</publish_date>
<description>After the collapse of a nanotechnology
society in England, the young survivors lay the
foundation for a new society.</description>
</book>
</catalog>`
var doc = loadXML(xmlDoc)
func TestXPath(t *testing.T) {
if list := Find(doc, "//book"); len(list) != 3 {
t.Fatal("count(//book) != 3")
}
if node := FindOne(doc, "//book[@id='bk101']"); node == nil {
t.Fatal("//book[@id='bk101] is not found")
}
if node := FindOne(doc, "//book[price>=44.95]"); node == nil {
t.Fatal("//book/price>=44.95 is not found")
}
if list := Find(doc, "//book[genre='Fantasy']"); len(list) != 2 {
t.Fatal("//book[genre='Fantasy'] items count is not equal 2")
}
var c int
FindEach(doc, "//book", func(i int, n *Node) {
c++
})
if c != len(Find(doc, "//book")) {
t.Fatal("count(//book) != 3")
}
node := FindOne(doc, "//book[1]")
if node.SelectAttr("id") != "bk101" {
t.Fatal("//book[1]/@id != bk101")
}
}
func TestXPathCdUp(t *testing.T) {
doc := loadXML(`<a><b attr="1"/></a>`)
node := FindOne(doc, "/a/b/@attr/..")
t.Logf("node = %#v", node)
if node == nil || node.Data != "b" {
t.Fatal("//b/@id/.. != <b/>")
}
}
func TestNavigator(t *testing.T) {
nav := &NodeNavigator{curr: doc, root: doc, attr: -1}
nav.MoveToChild() // New Line
nav.MoveToNext()
nav.MoveToNext() // catalog
if nav.curr.Data != "catalog" {
t.Fatal("current node name != `catalog`")
}
nav.MoveToChild() // New Line
nav.MoveToNext() // comment node
if nav.curr.Type != CommentNode {
t.Fatal("node type not CommentNode")
}
nav.Value()
nav.MoveToNext() // New Line
nav.MoveToNext() //book
nav.MoveToChild()
nav.MoveToNext() // book/author
if nav.LocalName() != "author" {
t.Fatalf("node error")
}
nav.MoveToParent() // book
nav.MoveToNext() // next book
if nav.curr.SelectAttr("id") != "bk102" {
t.Fatal("node error")
}
}
func loadXML(s string) *Node {
node, err := Parse(strings.NewReader(s))
if err != nil {
panic(err)
}
return node
}

17
vendor/github.com/antchfx/xpath/LICENSE generated vendored Normal file
View file

@ -0,0 +1,17 @@
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

155
vendor/github.com/antchfx/xpath/README.md generated vendored Normal file
View file

@ -0,0 +1,155 @@
XPath
====
[![GoDoc](https://godoc.org/github.com/antchfx/xpath?status.svg)](https://godoc.org/github.com/antchfx/xpath)
[![Coverage Status](https://coveralls.io/repos/github/antchfx/xpath/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xpath?branch=master)
[![Build Status](https://travis-ci.org/antchfx/xpath.svg?branch=master)](https://travis-ci.org/antchfx/xpath)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xpath)](https://goreportcard.com/report/github.com/antchfx/xpath)
XPath is Go package provides selecting nodes from XML, HTML or other documents using XPath expression.
Implementation
===
- [htmlquery](https://github.com/antchfx/htmlquery) - an XPath query package for HTML document
- [xmlquery](https://github.com/antchfx/xmlquery) - an XPath query package for XML document.
- [jsonquery](https://github.com/antchfx/jsonquery) - an XPath query package for JSON document
Supported Features
===
#### The basic XPath patterns.
> The basic XPath patterns cover 90% of the cases that most stylesheets will need.
- `node` : Selects all child elements with nodeName of node.
- `*` : Selects all child elements.
- `@attr` : Selects the attribute attr.
- `@*` : Selects all attributes.
- `node()` : Matches an org.w3c.dom.Node.
- `text()` : Matches a org.w3c.dom.Text node.
- `comment()` : Matches a comment.
- `.` : Selects the current node.
- `..` : Selects the parent of current node.
- `/` : Selects the document node.
- `a[expr]` : Select only those nodes matching a which also satisfy the expression expr.
- `a[n]` : Selects the nth matching node matching a When a filter's expression is a number, XPath selects based on position.
- `a/b` : For each node matching a, add the nodes matching b to the result.
- `a//b` : For each node matching a, add the descendant nodes matching b to the result.
- `//b` : Returns elements in the entire document matching b.
- `a|b` : All nodes matching a or b, union operation(not boolean or).
#### Node Axes
- `child::*` : The child axis selects children of the current node.
- `descendant::*` : The descendant axis selects descendants of the current node. It is equivalent to '//'.
- `descendant-or-self::*` : Selects descendants including the current node.
- `attribute::*` : Selects attributes of the current element. It is equivalent to @*
- `following-sibling::*` : Selects nodes after the current node.
- `preceding-sibling::*` : Selects nodes before the current node.
- `following::*` : Selects the first matching node following in document order, excluding descendants.
- `preceding::*` : Selects the first matching node preceding in document order, excluding ancestors.
- `parent::*` : Selects the parent if it matches. The '..' pattern from the core is equivalent to 'parent::node()'.
- `ancestor::*` : Selects matching ancestors.
- `ancestor-or-self::*` : Selects ancestors including the current node.
- `self::*` : Selects the current node. '.' is equivalent to 'self::node()'.
#### Expressions
The gxpath supported three types: number, boolean, string.
- `path` : Selects nodes based on the path.
- `a = b` : Standard comparisons.
* a = b True if a equals b.
* a != b True if a is not equal to b.
* a < b True if a is less than b.
* a <= b True if a is less than or equal to b.
* a > b True if a is greater than b.
* a >= b True if a is greater than or equal to b.
- `a + b` : Arithmetic expressions.
* `- a` Unary minus
* a + b Add
* a - b Substract
* a * b Multiply
* a div b Divide
* a mod b Floating point mod, like Java.
- `a or b` : Boolean `or` operation.
- `a and b` : Boolean `and` operation.
- `(expr)` : Parenthesized expressions.
- `fun(arg1, ..., argn)` : Function calls:
| Function | Supported |
| --- | --- |
`boolean()`| ✓ |
`ceiling()`| ✓ |
`choose()`| ✗ |
`concat()`| ✓ |
`contains()`| ✓ |
`count()`| ✓ |
`current()`| ✗ |
`document()`| ✗ |
`element-available()`| ✗ |
`ends-with()`| ✓ |
`false()`| ✓ |
`floor()`| ✓ |
`format-number()`| ✗ |
`function-available()`| ✗ |
`generate-id()`| ✗ |
`id()`| ✗ |
`key()`| ✗ |
`lang()`| ✗ |
`last()`| ✓ |
`local-name()`| ✓ |
`name()`| ✓ |
`namespace-uri()`| ✓ |
`normalize-space()`| ✓ |
`not()`| ✓ |
`number()`| ✓ |
`position()`| ✓ |
`round()`| ✓ |
`starts-with()`| ✓ |
`string()`| ✓ |
`string-length()`| ✓ |
`substring()`| ✓ |
`substring-after()`| ✓ |
`substring-before()`| ✓ |
`sum()`| ✓ |
`system-property()`| ✗ |
`translate()`| ✓ |
`true()`| ✓ |
`unparsed-entity-url()` | ✗ |

483
vendor/github.com/antchfx/xpath/build.go generated vendored Normal file
View file

@ -0,0 +1,483 @@
package xpath
import (
"errors"
"fmt"
)
type flag int
const (
noneFlag flag = iota
filterFlag
)
// builder provides building an XPath expressions.
type builder struct {
depth int
flag flag
firstInput query
}
// axisPredicate creates a predicate to predicating for this axis node.
func axisPredicate(root *axisNode) func(NodeNavigator) bool {
// get current axix node type.
typ := ElementNode
switch root.AxeType {
case "attribute":
typ = AttributeNode
case "self", "parent":
typ = allNode
default:
switch root.Prop {
case "comment":
typ = CommentNode
case "text":
typ = TextNode
// case "processing-instruction":
// typ = ProcessingInstructionNode
case "node":
typ = allNode
}
}
nametest := root.LocalName != "" || root.Prefix != ""
predicate := func(n NodeNavigator) bool {
if typ == n.NodeType() || typ == allNode || typ == TextNode {
if nametest {
if root.LocalName == n.LocalName() && root.Prefix == n.Prefix() {
return true
}
} else {
return true
}
}
return false
}
return predicate
}
// processAxisNode processes a query for the XPath axis node.
func (b *builder) processAxisNode(root *axisNode) (query, error) {
var (
err error
qyInput query
qyOutput query
predicate = axisPredicate(root)
)
if root.Input == nil {
qyInput = &contextQuery{}
} else {
if root.AxeType == "child" && (root.Input.Type() == nodeAxis) {
if input := root.Input.(*axisNode); input.AxeType == "descendant-or-self" {
var qyGrandInput query
if input.Input != nil {
qyGrandInput, _ = b.processNode(input.Input)
} else {
qyGrandInput = &contextQuery{}
}
qyOutput = &descendantQuery{Input: qyGrandInput, Predicate: predicate, Self: true}
return qyOutput, nil
}
}
qyInput, err = b.processNode(root.Input)
if err != nil {
return nil, err
}
}
switch root.AxeType {
case "ancestor":
qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate}
case "ancestor-or-self":
qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate, Self: true}
case "attribute":
qyOutput = &attributeQuery{Input: qyInput, Predicate: predicate}
case "child":
filter := func(n NodeNavigator) bool {
v := predicate(n)
switch root.Prop {
case "text":
v = v && n.NodeType() == TextNode
case "node":
v = v && (n.NodeType() == ElementNode || n.NodeType() == TextNode)
case "comment":
v = v && n.NodeType() == CommentNode
}
return v
}
qyOutput = &childQuery{Input: qyInput, Predicate: filter}
case "descendant":
qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate}
case "descendant-or-self":
qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate, Self: true}
case "following":
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate}
case "following-sibling":
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
case "parent":
qyOutput = &parentQuery{Input: qyInput, Predicate: predicate}
case "preceding":
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate}
case "preceding-sibling":
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
case "self":
qyOutput = &selfQuery{Input: qyInput, Predicate: predicate}
case "namespace":
// haha,what will you do someting??
default:
err = fmt.Errorf("unknown axe type: %s", root.AxeType)
return nil, err
}
return qyOutput, nil
}
// processFilterNode builds query for the XPath filter predicate.
func (b *builder) processFilterNode(root *filterNode) (query, error) {
b.flag |= filterFlag
qyInput, err := b.processNode(root.Input)
if err != nil {
return nil, err
}
qyCond, err := b.processNode(root.Condition)
if err != nil {
return nil, err
}
qyOutput := &filterQuery{Input: qyInput, Predicate: qyCond}
return qyOutput, nil
}
// processFunctionNode processes query for the XPath function node.
func (b *builder) processFunctionNode(root *functionNode) (query, error) {
var qyOutput query
switch root.FuncName {
case "starts-with":
arg1, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
arg2, err := b.processNode(root.Args[1])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: startwithFunc(arg1, arg2)}
case "ends-with":
arg1, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
arg2, err := b.processNode(root.Args[1])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: endwithFunc(arg1, arg2)}
case "contains":
arg1, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
arg2, err := b.processNode(root.Args[1])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: containsFunc(arg1, arg2)}
case "substring":
//substring( string , start [, length] )
if len(root.Args) < 2 {
return nil, errors.New("xpath: substring function must have at least two parameter")
}
var (
arg1, arg2, arg3 query
err error
)
if arg1, err = b.processNode(root.Args[0]); err != nil {
return nil, err
}
if arg2, err = b.processNode(root.Args[1]); err != nil {
return nil, err
}
if len(root.Args) == 3 {
if arg3, err = b.processNode(root.Args[2]); err != nil {
return nil, err
}
}
qyOutput = &functionQuery{Input: b.firstInput, Func: substringFunc(arg1, arg2, arg3)}
case "substring-before", "substring-after":
//substring-xxxx( haystack, needle )
if len(root.Args) != 2 {
return nil, errors.New("xpath: substring-before function must have two parameters")
}
var (
arg1, arg2 query
err error
)
if arg1, err = b.processNode(root.Args[0]); err != nil {
return nil, err
}
if arg2, err = b.processNode(root.Args[1]); err != nil {
return nil, err
}
qyOutput = &functionQuery{
Input: b.firstInput,
Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"),
}
case "string-length":
// string-length( [string] )
if len(root.Args) < 1 {
return nil, errors.New("xpath: string-length function must have at least one parameter")
}
arg1, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: stringLengthFunc(arg1)}
case "normalize-space":
if len(root.Args) == 0 {
return nil, errors.New("xpath: normalize-space function must have at least one parameter")
}
argQuery, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: argQuery, Func: normalizespaceFunc}
case "translate":
//translate( string , string, string )
if len(root.Args) != 3 {
return nil, errors.New("xpath: translate function must have three parameters")
}
var (
arg1, arg2, arg3 query
err error
)
if arg1, err = b.processNode(root.Args[0]); err != nil {
return nil, err
}
if arg2, err = b.processNode(root.Args[1]); err != nil {
return nil, err
}
if arg3, err = b.processNode(root.Args[2]); err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: translateFunc(arg1, arg2, arg3)}
case "not":
if len(root.Args) == 0 {
return nil, errors.New("xpath: not function must have at least one parameter")
}
argQuery, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: argQuery, Func: notFunc}
case "name", "local-name", "namespace-uri":
inp := b.firstInput
if len(root.Args) > 1 {
return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName)
}
if len(root.Args) == 1 {
argQuery, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
inp = argQuery
}
f := &functionQuery{Input: inp}
switch root.FuncName {
case "name":
f.Func = nameFunc
case "local-name":
f.Func = localNameFunc
case "namespace-uri":
f.Func = namespaceFunc
}
qyOutput = f
case "true", "false":
val := root.FuncName == "true"
qyOutput = &functionQuery{
Input: b.firstInput,
Func: func(_ query, _ iterator) interface{} {
return val
},
}
case "last":
qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc}
case "position":
qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc}
case "boolean", "number", "string":
inp := b.firstInput
if len(root.Args) > 1 {
return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName)
}
if len(root.Args) == 1 {
argQuery, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
inp = argQuery
}
f := &functionQuery{Input: inp}
switch root.FuncName {
case "boolean":
f.Func = booleanFunc
case "string":
f.Func = stringFunc
case "number":
f.Func = numberFunc
}
qyOutput = f
case "count":
//if b.firstInput == nil {
// return nil, errors.New("xpath: expression must evaluate to node-set")
//}
if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets")
}
argQuery, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: argQuery, Func: countFunc}
case "sum":
if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: sum(node-sets) function must with have parameters node-sets")
}
argQuery, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: argQuery, Func: sumFunc}
case "ceiling", "floor", "round":
if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: ceiling(node-sets) function must with have parameters node-sets")
}
argQuery, err := b.processNode(root.Args[0])
if err != nil {
return nil, err
}
f := &functionQuery{Input: argQuery}
switch root.FuncName {
case "ceiling":
f.Func = ceilingFunc
case "floor":
f.Func = floorFunc
case "round":
f.Func = roundFunc
}
qyOutput = f
case "concat":
if len(root.Args) < 2 {
return nil, fmt.Errorf("xpath: concat() must have at least two arguments")
}
var args []query
for _, v := range root.Args {
q, err := b.processNode(v)
if err != nil {
return nil, err
}
args = append(args, q)
}
qyOutput = &functionQuery{Input: b.firstInput, Func: concatFunc(args...)}
default:
return nil, fmt.Errorf("not yet support this function %s()", root.FuncName)
}
return qyOutput, nil
}
func (b *builder) processOperatorNode(root *operatorNode) (query, error) {
left, err := b.processNode(root.Left)
if err != nil {
return nil, err
}
right, err := b.processNode(root.Right)
if err != nil {
return nil, err
}
var qyOutput query
switch root.Op {
case "+", "-", "div", "mod": // Numeric operator
var exprFunc func(interface{}, interface{}) interface{}
switch root.Op {
case "+":
exprFunc = plusFunc
case "-":
exprFunc = minusFunc
case "div":
exprFunc = divFunc
case "mod":
exprFunc = modFunc
}
qyOutput = &numericQuery{Left: left, Right: right, Do: exprFunc}
case "=", ">", ">=", "<", "<=", "!=":
var exprFunc func(iterator, interface{}, interface{}) interface{}
switch root.Op {
case "=":
exprFunc = eqFunc
case ">":
exprFunc = gtFunc
case ">=":
exprFunc = geFunc
case "<":
exprFunc = ltFunc
case "<=":
exprFunc = leFunc
case "!=":
exprFunc = neFunc
}
qyOutput = &logicalQuery{Left: left, Right: right, Do: exprFunc}
case "or", "and":
isOr := false
if root.Op == "or" {
isOr = true
}
qyOutput = &booleanQuery{Left: left, Right: right, IsOr: isOr}
case "|":
qyOutput = &unionQuery{Left: left, Right: right}
}
return qyOutput, nil
}
func (b *builder) processNode(root node) (q query, err error) {
if b.depth = b.depth + 1; b.depth > 1024 {
err = errors.New("the xpath expressions is too complex")
return
}
switch root.Type() {
case nodeConstantOperand:
n := root.(*operandNode)
q = &constantQuery{Val: n.Val}
case nodeRoot:
q = &contextQuery{Root: true}
case nodeAxis:
q, err = b.processAxisNode(root.(*axisNode))
b.firstInput = q
case nodeFilter:
q, err = b.processFilterNode(root.(*filterNode))
case nodeFunction:
q, err = b.processFunctionNode(root.(*functionNode))
case nodeOperator:
q, err = b.processOperatorNode(root.(*operatorNode))
}
return
}
// build builds a specified XPath expressions expr.
func build(expr string) (q query, err error) {
defer func() {
if e := recover(); e != nil {
switch x := e.(type) {
case string:
err = errors.New(x)
case error:
err = x
default:
err = errors.New("unknown panic")
}
}
}()
root := parse(expr)
b := &builder{}
return b.processNode(root)
}

33
vendor/github.com/antchfx/xpath/doc_test.go generated vendored Normal file
View file

@ -0,0 +1,33 @@
package xpath_test
import (
"fmt"
"github.com/antchfx/xpath"
)
// XPath package example.
func Example() {
expr, err := xpath.Compile("count(//book)")
if err != nil {
panic(err)
}
var root xpath.NodeNavigator
// using Evaluate() method
val := expr.Evaluate(root) // it returns float64 type
fmt.Println(val.(float64))
// using Evaluate() method
expr = xpath.MustCompile("//book")
val = expr.Evaluate(root) // it returns NodeIterator type.
iter := val.(*xpath.NodeIterator)
for iter.MoveNext() {
fmt.Println(iter.Current().Value())
}
// using Select() method
iter = expr.Select(root) // it always returns NodeIterator object.
for iter.MoveNext() {
fmt.Println(iter.Current().Value())
}
}

475
vendor/github.com/antchfx/xpath/func.go generated vendored Normal file
View file

@ -0,0 +1,475 @@
package xpath
import (
"errors"
"fmt"
"math"
"strconv"
"strings"
)
// The XPath function list.
func predicate(q query) func(NodeNavigator) bool {
type Predicater interface {
Test(NodeNavigator) bool
}
if p, ok := q.(Predicater); ok {
return p.Test
}
return func(NodeNavigator) bool { return true }
}
// positionFunc is a XPath Node Set functions position().
func positionFunc(q query, t iterator) interface{} {
var (
count = 1
node = t.Current()
)
test := predicate(q)
for node.MoveToPrevious() {
if test(node) {
count++
}
}
return float64(count)
}
// lastFunc is a XPath Node Set functions last().
func lastFunc(q query, t iterator) interface{} {
var (
count = 0
node = t.Current()
)
node.MoveToFirst()
test := predicate(q)
for {
if test(node) {
count++
}
if !node.MoveToNext() {
break
}
}
return float64(count)
}
// countFunc is a XPath Node Set functions count(node-set).
func countFunc(q query, t iterator) interface{} {
var count = 0
test := predicate(q)
switch typ := q.Evaluate(t).(type) {
case query:
for node := typ.Select(t); node != nil; node = typ.Select(t) {
if test(node) {
count++
}
}
}
return float64(count)
}
// sumFunc is a XPath Node Set functions sum(node-set).
func sumFunc(q query, t iterator) interface{} {
var sum float64
switch typ := q.Evaluate(t).(type) {
case query:
for node := typ.Select(t); node != nil; node = typ.Select(t) {
if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
sum += v
}
}
case float64:
sum = typ
case string:
v, err := strconv.ParseFloat(typ, 64)
if err != nil {
panic(errors.New("sum() function argument type must be a node-set or number"))
}
sum = v
}
return sum
}
func asNumber(t iterator, o interface{}) float64 {
switch typ := o.(type) {
case query:
node := typ.Select(t)
if node == nil {
return float64(0)
}
if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
return v
}
case float64:
return typ
case string:
v, err := strconv.ParseFloat(typ, 64)
if err != nil {
panic(errors.New("ceiling() function argument type must be a node-set or number"))
}
return v
}
return 0
}
// ceilingFunc is a XPath Node Set functions ceiling(node-set).
func ceilingFunc(q query, t iterator) interface{} {
val := asNumber(t, q.Evaluate(t))
return math.Ceil(val)
}
// floorFunc is a XPath Node Set functions floor(node-set).
func floorFunc(q query, t iterator) interface{} {
val := asNumber(t, q.Evaluate(t))
return math.Floor(val)
}
// roundFunc is a XPath Node Set functions round(node-set).
func roundFunc(q query, t iterator) interface{} {
val := asNumber(t, q.Evaluate(t))
//return math.Round(val)
return round(val)
}
// nameFunc is a XPath functions name([node-set]).
func nameFunc(q query, t iterator) interface{} {
v := q.Select(t)
if v == nil {
return ""
}
ns := v.Prefix()
if ns == "" {
return v.LocalName()
}
return ns + ":" + v.LocalName()
}
// localNameFunc is a XPath functions local-name([node-set]).
func localNameFunc(q query, t iterator) interface{} {
v := q.Select(t)
if v == nil {
return ""
}
return v.LocalName()
}
// namespaceFunc is a XPath functions namespace-uri([node-set]).
func namespaceFunc(q query, t iterator) interface{} {
v := q.Select(t)
if v == nil {
return ""
}
return v.Prefix()
}
func asBool(t iterator, v interface{}) bool {
switch v := v.(type) {
case nil:
return false
case *NodeIterator:
return v.MoveNext()
case bool:
return bool(v)
case float64:
return v != 0
case string:
return v != ""
case query:
return v.Select(t) != nil
default:
panic(fmt.Errorf("unexpected type: %T", v))
}
}
func asString(t iterator, v interface{}) string {
switch v := v.(type) {
case nil:
return ""
case bool:
if v {
return "true"
}
return "false"
case float64:
return strconv.FormatFloat(v, 'g', -1, 64)
case string:
return v
case query:
node := v.Select(t)
if node == nil {
return ""
}
return node.Value()
default:
panic(fmt.Errorf("unexpected type: %T", v))
}
}
// booleanFunc is a XPath functions boolean([node-set]).
func booleanFunc(q query, t iterator) interface{} {
v := q.Evaluate(t)
return asBool(t, v)
}
// numberFunc is a XPath functions number([node-set]).
func numberFunc(q query, t iterator) interface{} {
v := q.Evaluate(t)
return asNumber(t, v)
}
// stringFunc is a XPath functions string([node-set]).
func stringFunc(q query, t iterator) interface{} {
v := q.Evaluate(t)
return asString(t, v)
}
// startwithFunc is a XPath functions starts-with(string, string).
func startwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
var (
m, n string
ok bool
)
switch typ := arg1.Evaluate(t).(type) {
case string:
m = typ
case query:
node := typ.Select(t)
if node == nil {
return false
}
m = node.Value()
default:
panic(errors.New("starts-with() function argument type must be string"))
}
n, ok = arg2.Evaluate(t).(string)
if !ok {
panic(errors.New("starts-with() function argument type must be string"))
}
return strings.HasPrefix(m, n)
}
}
// endwithFunc is a XPath functions ends-with(string, string).
func endwithFunc(arg1, arg2 query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
var (
m, n string
ok bool
)
switch typ := arg1.Evaluate(t).(type) {
case string:
m = typ
case query:
node := typ.Select(t)
if node == nil {
return false
}
m = node.Value()
default:
panic(errors.New("ends-with() function argument type must be string"))
}
n, ok = arg2.Evaluate(t).(string)
if !ok {
panic(errors.New("ends-with() function argument type must be string"))
}
return strings.HasSuffix(m, n)
}
}
// containsFunc is a XPath functions contains(string or @attr, string).
func containsFunc(arg1, arg2 query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
var (
m, n string
ok bool
)
switch typ := arg1.Evaluate(t).(type) {
case string:
m = typ
case query:
node := typ.Select(t)
if node == nil {
return false
}
m = node.Value()
default:
panic(errors.New("contains() function argument type must be string"))
}
n, ok = arg2.Evaluate(t).(string)
if !ok {
panic(errors.New("contains() function argument type must be string"))
}
return strings.Contains(m, n)
}
}
// normalizespaceFunc is XPath functions normalize-space(string?)
func normalizespaceFunc(q query, t iterator) interface{} {
var m string
switch typ := q.Evaluate(t).(type) {
case string:
m = typ
case query:
node := typ.Select(t)
if node == nil {
return false
}
m = node.Value()
}
return strings.TrimSpace(m)
}
// substringFunc is XPath functions substring function returns a part of a given string.
func substringFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
var m string
switch typ := arg1.Evaluate(t).(type) {
case string:
m = typ
case query:
node := typ.Select(t)
if node == nil {
return ""
}
m = node.Value()
}
var start, length float64
var ok bool
if start, ok = arg2.Evaluate(t).(float64); !ok {
panic(errors.New("substring() function first argument type must be int"))
} else if start < 1 {
panic(errors.New("substring() function first argument type must be >= 1"))
}
start--
if arg3 != nil {
if length, ok = arg3.Evaluate(t).(float64); !ok {
panic(errors.New("substring() function second argument type must be int"))
}
}
if (len(m) - int(start)) < int(length) {
panic(errors.New("substring() function start and length argument out of range"))
}
if length > 0 {
return m[int(start):int(length+start)]
}
return m[int(start):]
}
}
// substringIndFunc is XPath functions substring-before/substring-after function returns a part of a given string.
func substringIndFunc(arg1, arg2 query, after bool) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
var str string
switch v := arg1.Evaluate(t).(type) {
case string:
str = v
case query:
node := v.Select(t)
if node == nil {
return ""
}
str = node.Value()
}
var word string
switch v := arg2.Evaluate(t).(type) {
case string:
word = v
case query:
node := v.Select(t)
if node == nil {
return ""
}
word = node.Value()
}
if word == "" {
return ""
}
i := strings.Index(str, word)
if i < 0 {
return ""
}
if after {
return str[i+len(word):]
}
return str[:i]
}
}
// stringLengthFunc is XPATH string-length( [string] ) function that returns a number
// equal to the number of characters in a given string.
func stringLengthFunc(arg1 query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
switch v := arg1.Evaluate(t).(type) {
case string:
return float64(len(v))
case query:
node := v.Select(t)
if node == nil {
break
}
return float64(len(node.Value()))
}
return float64(0)
}
}
// translateFunc is XPath functions translate() function returns a replaced string.
func translateFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
str := asString(t, arg1.Evaluate(t))
src := asString(t, arg2.Evaluate(t))
dst := asString(t, arg3.Evaluate(t))
var replace []string
for i, s := range src {
d := ""
if i < len(dst) {
d = string(dst[i])
}
replace = append(replace, string(s), d)
}
return strings.NewReplacer(replace...).Replace(str)
}
}
// notFunc is XPATH functions not(expression) function operation.
func notFunc(q query, t iterator) interface{} {
switch v := q.Evaluate(t).(type) {
case bool:
return !v
case query:
node := v.Select(t)
return node == nil
default:
return false
}
}
// concatFunc is the concat function concatenates two or more
// strings and returns the resulting string.
// concat( string1 , string2 [, stringn]* )
func concatFunc(args ...query) func(query, iterator) interface{} {
return func(q query, t iterator) interface{} {
var a []string
for _, v := range args {
switch v := v.Evaluate(t).(type) {
case string:
a = append(a, v)
case query:
node := v.Select(t)
if node != nil {
a = append(a, node.Value())
}
}
}
return strings.Join(a, "")
}
}

9
vendor/github.com/antchfx/xpath/func_go110.go generated vendored Normal file
View file

@ -0,0 +1,9 @@
// +build go1.10
package xpath
import "math"
func round(f float64) int {
return int(math.Round(f))
}

15
vendor/github.com/antchfx/xpath/func_pre_go110.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
// +build !go1.10
package xpath
import "math"
// math.Round() is supported by Go 1.10+,
// This method just compatible for version <1.10.
// https://github.com/golang/go/issues/20100
func round(f float64) int {
if math.Abs(f) < 0.5 {
return 0
}
return int(f + math.Copysign(0.5, f))
}

295
vendor/github.com/antchfx/xpath/operator.go generated vendored Normal file
View file

@ -0,0 +1,295 @@
package xpath
import (
"fmt"
"reflect"
"strconv"
)
// The XPath number operator function list.
// valueType is a return value type.
type valueType int
const (
booleanType valueType = iota
numberType
stringType
nodeSetType
)
func getValueType(i interface{}) valueType {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Float64:
return numberType
case reflect.String:
return stringType
case reflect.Bool:
return booleanType
default:
if _, ok := i.(query); ok {
return nodeSetType
}
}
panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
}
type logical func(iterator, string, interface{}, interface{}) bool
var logicalFuncs = [][]logical{
{cmpBooleanBoolean, nil, nil, nil},
{nil, cmpNumericNumeric, cmpNumericString, cmpNumericNodeSet},
{nil, cmpStringNumeric, cmpStringString, cmpStringNodeSet},
{nil, cmpNodeSetNumeric, cmpNodeSetString, cmpNodeSetNodeSet},
}
// number vs number
func cmpNumberNumberF(op string, a, b float64) bool {
switch op {
case "=":
return a == b
case ">":
return a > b
case "<":
return a < b
case ">=":
return a >= b
case "<=":
return a <= b
case "!=":
return a != b
}
return false
}
// string vs string
func cmpStringStringF(op string, a, b string) bool {
switch op {
case "=":
return a == b
case ">":
return a > b
case "<":
return a < b
case ">=":
return a >= b
case "<=":
return a <= b
case "!=":
return a != b
}
return false
}
func cmpBooleanBooleanF(op string, a, b bool) bool {
switch op {
case "or":
return a || b
case "and":
return a && b
}
return false
}
func cmpNumericNumeric(t iterator, op string, m, n interface{}) bool {
a := m.(float64)
b := n.(float64)
return cmpNumberNumberF(op, a, b)
}
func cmpNumericString(t iterator, op string, m, n interface{}) bool {
a := m.(float64)
b := n.(string)
num, err := strconv.ParseFloat(b, 64)
if err != nil {
panic(err)
}
return cmpNumberNumberF(op, a, num)
}
func cmpNumericNodeSet(t iterator, op string, m, n interface{}) bool {
a := m.(float64)
b := n.(query)
for {
node := b.Select(t)
if node == nil {
break
}
num, err := strconv.ParseFloat(node.Value(), 64)
if err != nil {
panic(err)
}
if cmpNumberNumberF(op, a, num) {
return true
}
}
return false
}
func cmpNodeSetNumeric(t iterator, op string, m, n interface{}) bool {
a := m.(query)
b := n.(float64)
for {
node := a.Select(t)
if node == nil {
break
}
num, err := strconv.ParseFloat(node.Value(), 64)
if err != nil {
panic(err)
}
if cmpNumberNumberF(op, num, b) {
return true
}
}
return false
}
func cmpNodeSetString(t iterator, op string, m, n interface{}) bool {
a := m.(query)
b := n.(string)
for {
node := a.Select(t)
if node == nil {
break
}
if cmpStringStringF(op, b, node.Value()) {
return true
}
}
return false
}
func cmpNodeSetNodeSet(t iterator, op string, m, n interface{}) bool {
return false
}
func cmpStringNumeric(t iterator, op string, m, n interface{}) bool {
a := m.(string)
b := n.(float64)
num, err := strconv.ParseFloat(a, 64)
if err != nil {
panic(err)
}
return cmpNumberNumberF(op, b, num)
}
func cmpStringString(t iterator, op string, m, n interface{}) bool {
a := m.(string)
b := n.(string)
return cmpStringStringF(op, a, b)
}
func cmpStringNodeSet(t iterator, op string, m, n interface{}) bool {
a := m.(string)
b := n.(query)
for {
node := b.Select(t)
if node == nil {
break
}
if cmpStringStringF(op, a, node.Value()) {
return true
}
}
return false
}
func cmpBooleanBoolean(t iterator, op string, m, n interface{}) bool {
a := m.(bool)
b := n.(bool)
return cmpBooleanBooleanF(op, a, b)
}
// eqFunc is an `=` operator.
func eqFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m)
t2 := getValueType(n)
return logicalFuncs[t1][t2](t, "=", m, n)
}
// gtFunc is an `>` operator.
func gtFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m)
t2 := getValueType(n)
return logicalFuncs[t1][t2](t, ">", m, n)
}
// geFunc is an `>=` operator.
func geFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m)
t2 := getValueType(n)
return logicalFuncs[t1][t2](t, ">=", m, n)
}
// ltFunc is an `<` operator.
func ltFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m)
t2 := getValueType(n)
return logicalFuncs[t1][t2](t, "<", m, n)
}
// leFunc is an `<=` operator.
func leFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m)
t2 := getValueType(n)
return logicalFuncs[t1][t2](t, "<=", m, n)
}
// neFunc is an `!=` operator.
func neFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m)
t2 := getValueType(n)
return logicalFuncs[t1][t2](t, "!=", m, n)
}
// orFunc is an `or` operator.
var orFunc = func(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m)
t2 := getValueType(n)
return logicalFuncs[t1][t2](t, "or", m, n)
}
func numericExpr(m, n interface{}, cb func(float64, float64) float64) float64 {
typ := reflect.TypeOf(float64(0))
a := reflect.ValueOf(m).Convert(typ)
b := reflect.ValueOf(n).Convert(typ)
return cb(a.Float(), b.Float())
}
// plusFunc is an `+` operator.
var plusFunc = func(m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 {
return a + b
})
}
// minusFunc is an `-` operator.
var minusFunc = func(m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 {
return a - b
})
}
// mulFunc is an `*` operator.
var mulFunc = func(m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 {
return a * b
})
}
// divFunc is an `DIV` operator.
var divFunc = func(m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 {
return a / b
})
}
// modFunc is an 'MOD' operator.
var modFunc = func(m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 {
return float64(int(a) % int(b))
})
}

1164
vendor/github.com/antchfx/xpath/parse.go generated vendored Normal file

File diff suppressed because it is too large Load diff

791
vendor/github.com/antchfx/xpath/query.go generated vendored Normal file
View file

@ -0,0 +1,791 @@
package xpath
import (
"reflect"
)
type iterator interface {
Current() NodeNavigator
}
// An XPath query interface.
type query interface {
// Select traversing iterator returns a query matched node NodeNavigator.
Select(iterator) NodeNavigator
// Evaluate evaluates query and returns values of the current query.
Evaluate(iterator) interface{}
Clone() query
}
// contextQuery is returns current node on the iterator object query.
type contextQuery struct {
count int
Root bool // Moving to root-level node in the current context iterator.
}
func (c *contextQuery) Select(t iterator) (n NodeNavigator) {
if c.count == 0 {
c.count++
n = t.Current().Copy()
if c.Root {
n.MoveToRoot()
}
}
return n
}
func (c *contextQuery) Evaluate(iterator) interface{} {
c.count = 0
return c
}
func (c *contextQuery) Clone() query {
return &contextQuery{count: 0, Root: c.Root}
}
// ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
type ancestorQuery struct {
iterator func() NodeNavigator
Self bool
Input query
Predicate func(NodeNavigator) bool
}
func (a *ancestorQuery) Select(t iterator) NodeNavigator {
for {
if a.iterator == nil {
node := a.Input.Select(t)
if node == nil {
return nil
}
first := true
a.iterator = func() NodeNavigator {
if first && a.Self {
first = false
if a.Predicate(node) {
return node
}
}
for node.MoveToParent() {
if !a.Predicate(node) {
break
}
return node
}
return nil
}
}
if node := a.iterator(); node != nil {
return node
}
a.iterator = nil
}
}
func (a *ancestorQuery) Evaluate(t iterator) interface{} {
a.Input.Evaluate(t)
a.iterator = nil
return a
}
func (a *ancestorQuery) Test(n NodeNavigator) bool {
return a.Predicate(n)
}
func (a *ancestorQuery) Clone() query {
return &ancestorQuery{Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate}
}
// attributeQuery is an XPath attribute node query.(@*)
type attributeQuery struct {
iterator func() NodeNavigator
Input query
Predicate func(NodeNavigator) bool
}
func (a *attributeQuery) Select(t iterator) NodeNavigator {
for {
if a.iterator == nil {
node := a.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
a.iterator = func() NodeNavigator {
for {
onAttr := node.MoveToNextAttribute()
if !onAttr {
return nil
}
if a.Predicate(node) {
return node
}
}
}
}
if node := a.iterator(); node != nil {
return node
}
a.iterator = nil
}
}
func (a *attributeQuery) Evaluate(t iterator) interface{} {
a.Input.Evaluate(t)
a.iterator = nil
return a
}
func (a *attributeQuery) Test(n NodeNavigator) bool {
return a.Predicate(n)
}
func (a *attributeQuery) Clone() query {
return &attributeQuery{Input: a.Input.Clone(), Predicate: a.Predicate}
}
// childQuery is an XPath child node query.(child::*)
type childQuery struct {
posit int
iterator func() NodeNavigator
Input query
Predicate func(NodeNavigator) bool
}
func (c *childQuery) Select(t iterator) NodeNavigator {
for {
if c.iterator == nil {
c.posit = 0
node := c.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
first := true
c.iterator = func() NodeNavigator {
for {
if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) {
return nil
}
first = false
if c.Predicate(node) {
return node
}
}
}
}
if node := c.iterator(); node != nil {
c.posit++
return node
}
c.iterator = nil
}
}
func (c *childQuery) Evaluate(t iterator) interface{} {
c.Input.Evaluate(t)
c.iterator = nil
return c
}
func (c *childQuery) Test(n NodeNavigator) bool {
return c.Predicate(n)
}
func (c *childQuery) Clone() query {
return &childQuery{Input: c.Input.Clone(), Predicate: c.Predicate}
}
// position returns a position of current NodeNavigator.
func (c *childQuery) position() int {
return c.posit
}
// descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*)
type descendantQuery struct {
iterator func() NodeNavigator
posit int
Self bool
Input query
Predicate func(NodeNavigator) bool
}
func (d *descendantQuery) Select(t iterator) NodeNavigator {
for {
if d.iterator == nil {
d.posit = 0
node := d.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
level := 0
first := true
d.iterator = func() NodeNavigator {
if first && d.Self {
first = false
if d.Predicate(node) {
return node
}
}
for {
if node.MoveToChild() {
level++
} else {
for {
if level == 0 {
return nil
}
if node.MoveToNext() {
break
}
node.MoveToParent()
level--
}
}
if d.Predicate(node) {
return node
}
}
}
}
if node := d.iterator(); node != nil {
d.posit++
return node
}
d.iterator = nil
}
}
func (d *descendantQuery) Evaluate(t iterator) interface{} {
d.Input.Evaluate(t)
d.iterator = nil
return d
}
func (d *descendantQuery) Test(n NodeNavigator) bool {
return d.Predicate(n)
}
// position returns a position of current NodeNavigator.
func (d *descendantQuery) position() int {
return d.posit
}
func (d *descendantQuery) Clone() query {
return &descendantQuery{Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate}
}
// followingQuery is an XPath following node query.(following::*|following-sibling::*)
type followingQuery struct {
iterator func() NodeNavigator
Input query
Sibling bool // The matching sibling node of current node.
Predicate func(NodeNavigator) bool
}
func (f *followingQuery) Select(t iterator) NodeNavigator {
for {
if f.iterator == nil {
node := f.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
if f.Sibling {
f.iterator = func() NodeNavigator {
for {
if !node.MoveToNext() {
return nil
}
if f.Predicate(node) {
return node
}
}
}
} else {
var q query // descendant query
f.iterator = func() NodeNavigator {
for {
if q == nil {
for !node.MoveToNext() {
if !node.MoveToParent() {
return nil
}
}
q = &descendantQuery{
Self: true,
Input: &contextQuery{},
Predicate: f.Predicate,
}
t.Current().MoveTo(node)
}
if node := q.Select(t); node != nil {
return node
}
q = nil
}
}
}
}
if node := f.iterator(); node != nil {
return node
}
f.iterator = nil
}
}
func (f *followingQuery) Evaluate(t iterator) interface{} {
f.Input.Evaluate(t)
return f
}
func (f *followingQuery) Test(n NodeNavigator) bool {
return f.Predicate(n)
}
func (f *followingQuery) Clone() query {
return &followingQuery{Input: f.Input.Clone(), Sibling: f.Sibling, Predicate: f.Predicate}
}
// precedingQuery is an XPath preceding node query.(preceding::*)
type precedingQuery struct {
iterator func() NodeNavigator
Input query
Sibling bool // The matching sibling node of current node.
Predicate func(NodeNavigator) bool
}
func (p *precedingQuery) Select(t iterator) NodeNavigator {
for {
if p.iterator == nil {
node := p.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
if p.Sibling {
p.iterator = func() NodeNavigator {
for {
for !node.MoveToPrevious() {
return nil
}
if p.Predicate(node) {
return node
}
}
}
} else {
var q query
p.iterator = func() NodeNavigator {
for {
if q == nil {
for !node.MoveToPrevious() {
if !node.MoveToParent() {
return nil
}
}
q = &descendantQuery{
Self: true,
Input: &contextQuery{},
Predicate: p.Predicate,
}
t.Current().MoveTo(node)
}
if node := q.Select(t); node != nil {
return node
}
q = nil
}
}
}
}
if node := p.iterator(); node != nil {
return node
}
p.iterator = nil
}
}
func (p *precedingQuery) Evaluate(t iterator) interface{} {
p.Input.Evaluate(t)
return p
}
func (p *precedingQuery) Test(n NodeNavigator) bool {
return p.Predicate(n)
}
func (p *precedingQuery) Clone() query {
return &precedingQuery{Input: p.Input.Clone(), Sibling: p.Sibling, Predicate: p.Predicate}
}
// parentQuery is an XPath parent node query.(parent::*)
type parentQuery struct {
Input query
Predicate func(NodeNavigator) bool
}
func (p *parentQuery) Select(t iterator) NodeNavigator {
for {
node := p.Input.Select(t)
if node == nil {
return nil
}
node = node.Copy()
if node.MoveToParent() && p.Predicate(node) {
return node
}
}
}
func (p *parentQuery) Evaluate(t iterator) interface{} {
p.Input.Evaluate(t)
return p
}
func (p *parentQuery) Clone() query {
return &parentQuery{Input: p.Input.Clone(), Predicate: p.Predicate}
}
func (p *parentQuery) Test(n NodeNavigator) bool {
return p.Predicate(n)
}
// selfQuery is an Self node query.(self::*)
type selfQuery struct {
Input query
Predicate func(NodeNavigator) bool
}
func (s *selfQuery) Select(t iterator) NodeNavigator {
for {
node := s.Input.Select(t)
if node == nil {
return nil
}
if s.Predicate(node) {
return node
}
}
}
func (s *selfQuery) Evaluate(t iterator) interface{} {
s.Input.Evaluate(t)
return s
}
func (s *selfQuery) Test(n NodeNavigator) bool {
return s.Predicate(n)
}
func (s *selfQuery) Clone() query {
return &selfQuery{Input: s.Input.Clone(), Predicate: s.Predicate}
}
// filterQuery is an XPath query for predicate filter.
type filterQuery struct {
Input query
Predicate query
}
func (f *filterQuery) do(t iterator) bool {
val := reflect.ValueOf(f.Predicate.Evaluate(t))
switch val.Kind() {
case reflect.Bool:
return val.Bool()
case reflect.String:
return len(val.String()) > 0
case reflect.Float64:
pt := float64(getNodePosition(f.Input))
return int(val.Float()) == int(pt)
default:
if q, ok := f.Predicate.(query); ok {
return q.Select(t) != nil
}
}
return false
}
func (f *filterQuery) Select(t iterator) NodeNavigator {
for {
node := f.Input.Select(t)
if node == nil {
return node
}
node = node.Copy()
//fmt.Println(node.LocalName())
t.Current().MoveTo(node)
if f.do(t) {
return node
}
}
}
func (f *filterQuery) Evaluate(t iterator) interface{} {
f.Input.Evaluate(t)
return f
}
func (f *filterQuery) Clone() query {
return &filterQuery{Input: f.Input.Clone(), Predicate: f.Predicate.Clone()}
}
// functionQuery is an XPath function that call a function to returns
// value of current NodeNavigator node.
type functionQuery struct {
Input query // Node Set
Func func(query, iterator) interface{} // The xpath function.
}
func (f *functionQuery) Select(t iterator) NodeNavigator {
return nil
}
// Evaluate call a specified function that will returns the
// following value type: number,string,boolean.
func (f *functionQuery) Evaluate(t iterator) interface{} {
return f.Func(f.Input, t)
}
func (f *functionQuery) Clone() query {
return &functionQuery{Input: f.Input.Clone(), Func: f.Func}
}
// constantQuery is an XPath constant operand.
type constantQuery struct {
Val interface{}
}
func (c *constantQuery) Select(t iterator) NodeNavigator {
return nil
}
func (c *constantQuery) Evaluate(t iterator) interface{} {
return c.Val
}
func (c *constantQuery) Clone() query {
return c
}
// logicalQuery is an XPath logical expression.
type logicalQuery struct {
Left, Right query
Do func(iterator, interface{}, interface{}) interface{}
}
func (l *logicalQuery) Select(t iterator) NodeNavigator {
// When a XPath expr is logical expression.
node := t.Current().Copy()
val := l.Evaluate(t)
switch val.(type) {
case bool:
if val.(bool) == true {
return node
}
}
return nil
}
func (l *logicalQuery) Evaluate(t iterator) interface{} {
m := l.Left.Evaluate(t)
n := l.Right.Evaluate(t)
return l.Do(t, m, n)
}
func (l *logicalQuery) Clone() query {
return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do}
}
// numericQuery is an XPath numeric operator expression.
type numericQuery struct {
Left, Right query
Do func(interface{}, interface{}) interface{}
}
func (n *numericQuery) Select(t iterator) NodeNavigator {
return nil
}
func (n *numericQuery) Evaluate(t iterator) interface{} {
m := n.Left.Evaluate(t)
k := n.Right.Evaluate(t)
return n.Do(m, k)
}
func (n *numericQuery) Clone() query {
return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do}
}
type booleanQuery struct {
IsOr bool
Left, Right query
iterator func() NodeNavigator
}
func (b *booleanQuery) Select(t iterator) NodeNavigator {
if b.iterator == nil {
var list []NodeNavigator
i := 0
root := t.Current().Copy()
if b.IsOr {
for {
node := b.Left.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(list, node)
}
t.Current().MoveTo(root)
for {
node := b.Right.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(list, node)
}
} else {
var m []NodeNavigator
var n []NodeNavigator
for {
node := b.Left.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(m, node)
}
t.Current().MoveTo(root)
for {
node := b.Right.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(n, node)
}
for _, k := range m {
for _, j := range n {
if k == j {
list = append(list, k)
}
}
}
}
b.iterator = func() NodeNavigator {
if i >= len(list) {
return nil
}
node := list[i]
i++
return node
}
}
return b.iterator()
}
func (b *booleanQuery) Evaluate(t iterator) interface{} {
m := b.Left.Evaluate(t)
left := asBool(t, m)
if b.IsOr && left {
return true
} else if !b.IsOr && !left {
return false
}
m = b.Right.Evaluate(t)
return asBool(t, m)
}
func (b *booleanQuery) Clone() query {
return &booleanQuery{IsOr: b.IsOr, Left: b.Left.Clone(), Right: b.Right.Clone()}
}
type unionQuery struct {
Left, Right query
iterator func() NodeNavigator
}
func (u *unionQuery) Select(t iterator) NodeNavigator {
if u.iterator == nil {
var list []NodeNavigator
var i int
root := t.Current().Copy()
for {
node := u.Left.Select(t)
if node == nil {
break
}
node = node.Copy()
list = append(list, node)
}
t.Current().MoveTo(root)
for {
node := u.Right.Select(t)
if node == nil {
break
}
node = node.Copy()
var exists bool
for _, x := range list {
if reflect.DeepEqual(x, node) {
exists = true
break
}
}
if !exists {
list = append(list, node)
}
}
u.iterator = func() NodeNavigator {
if i >= len(list) {
return nil
}
node := list[i]
i++
return node
}
}
return u.iterator()
}
func (u *unionQuery) Evaluate(t iterator) interface{} {
u.iterator = nil
u.Left.Evaluate(t)
u.Right.Evaluate(t)
return u
}
func (u *unionQuery) Clone() query {
return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()}
}
func getNodePosition(q query) int {
type Position interface {
position() int
}
if count, ok := q.(Position); ok {
return count.position()
}
return 1
}

157
vendor/github.com/antchfx/xpath/xpath.go generated vendored Normal file
View file

@ -0,0 +1,157 @@
package xpath
import (
"errors"
)
// NodeType represents a type of XPath node.
type NodeType int
const (
// RootNode is a root node of the XML document or node tree.
RootNode NodeType = iota
// ElementNode is an element, such as <element>.
ElementNode
// AttributeNode is an attribute, such as id='123'.
AttributeNode
// TextNode is the text content of a node.
TextNode
// CommentNode is a comment node, such as <!-- my comment -->
CommentNode
// allNode is any types of node, used by xpath package only to predicate match.
allNode
)
// NodeNavigator provides cursor model for navigating XML data.
type NodeNavigator interface {
// NodeType returns the XPathNodeType of the current node.
NodeType() NodeType
// LocalName gets the Name of the current node.
LocalName() string
// Prefix returns namespace prefix associated with the current node.
Prefix() string
// Value gets the value of current node.
Value() string
// Copy does a deep copy of the NodeNavigator and all its components.
Copy() NodeNavigator
// MoveToRoot moves the NodeNavigator to the root node of the current node.
MoveToRoot()
// MoveToParent moves the NodeNavigator to the parent node of the current node.
MoveToParent() bool
// MoveToNextAttribute moves the NodeNavigator to the next attribute on current node.
MoveToNextAttribute() bool
// MoveToChild moves the NodeNavigator to the first child node of the current node.
MoveToChild() bool
// MoveToFirst moves the NodeNavigator to the first sibling node of the current node.
MoveToFirst() bool
// MoveToNext moves the NodeNavigator to the next sibling node of the current node.
MoveToNext() bool
// MoveToPrevious moves the NodeNavigator to the previous sibling node of the current node.
MoveToPrevious() bool
// MoveTo moves the NodeNavigator to the same position as the specified NodeNavigator.
MoveTo(NodeNavigator) bool
}
// NodeIterator holds all matched Node object.
type NodeIterator struct {
node NodeNavigator
query query
}
// Current returns current node which matched.
func (t *NodeIterator) Current() NodeNavigator {
return t.node
}
// MoveNext moves Navigator to the next match node.
func (t *NodeIterator) MoveNext() bool {
n := t.query.Select(t)
if n != nil {
if !t.node.MoveTo(n) {
t.node = n.Copy()
}
return true
}
return false
}
// Select selects a node set using the specified XPath expression.
// This method is deprecated, recommend using Expr.Select() method instead.
func Select(root NodeNavigator, expr string) *NodeIterator {
exp, err := Compile(expr)
if err != nil {
panic(err)
}
return exp.Select(root)
}
// Expr is an XPath expression for query.
type Expr struct {
s string
q query
}
type iteratorFunc func() NodeNavigator
func (f iteratorFunc) Current() NodeNavigator {
return f()
}
// Evaluate returns the result of the expression.
// The result type of the expression is one of the follow: bool,float64,string,NodeIterator).
func (expr *Expr) Evaluate(root NodeNavigator) interface{} {
val := expr.q.Evaluate(iteratorFunc(func() NodeNavigator { return root }))
switch val.(type) {
case query:
return &NodeIterator{query: expr.q.Clone(), node: root}
}
return val
}
// Select selects a node set using the specified XPath expression.
func (expr *Expr) Select(root NodeNavigator) *NodeIterator {
return &NodeIterator{query: expr.q.Clone(), node: root}
}
// String returns XPath expression string.
func (expr *Expr) String() string {
return expr.s
}
// Compile compiles an XPath expression string.
func Compile(expr string) (*Expr, error) {
if expr == "" {
return nil, errors.New("expr expression is nil")
}
qy, err := build(expr)
if err != nil {
return nil, err
}
return &Expr{s: expr, q: qy}, nil
}
// MustCompile compiles an XPath expression string and ignored error.
func MustCompile(expr string) *Expr {
exp, err := Compile(expr)
if err != nil {
return nil
}
return exp
}

631
vendor/github.com/antchfx/xpath/xpath_test.go generated vendored Normal file
View file

@ -0,0 +1,631 @@
package xpath
import (
"bytes"
"strings"
"testing"
)
var html *TNode = example()
func TestCompile(t *testing.T) {
var err error
_, err = Compile("//a")
if err != nil {
t.Fatalf("//a should be correct but got error %s", err)
}
_, err = Compile("//a[id=']/span")
if err == nil {
t.Fatal("//a[id=] should be got correct but is nil")
}
_, err = Compile("//ul/li/@class")
if err != nil {
t.Fatalf("//ul/li/@class should be correct but got error %s", err)
}
}
func TestSelf(t *testing.T) {
testXPath(t, html, ".", "html")
testXPath(t, html.FirstChild, ".", "head")
testXPath(t, html, "self::*", "html")
testXPath(t, html.LastChild, "self::body", "body")
testXPath2(t, html, "//body/./ul/li/a", 3)
}
func TestParent(t *testing.T) {
testXPath(t, html.LastChild, "..", "html")
testXPath(t, html.LastChild, "parent::*", "html")
a := selectNode(html, "//li/a")
testXPath(t, a, "parent::*", "li")
testXPath(t, html, "//title/parent::head", "head")
}
func TestAttribute(t *testing.T) {
testXPath(t, html, "@lang='en'", "html")
testXPath2(t, html, "@lang='zh'", 0)
testXPath2(t, html, "//@href", 3)
testXPath2(t, html, "//a[@*]", 3)
}
func TestRelativePath(t *testing.T) {
testXPath(t, html, "head", "head")
testXPath(t, html, "/head", "head")
testXPath(t, html, "body//li", "li")
testXPath(t, html, "/head/title", "title")
testXPath2(t, html, "/body/ul/li/a", 3)
testXPath(t, html, "//title", "title")
testXPath(t, html, "//title/..", "head")
testXPath(t, html, "//title/../..", "html")
testXPath2(t, html, "//a[@href]", 3)
testXPath(t, html, "//ul/../footer", "footer")
}
func TestChild(t *testing.T) {
testXPath(t, html, "/child::head", "head")
testXPath(t, html, "/child::head/child::title", "title")
testXPath(t, html, "//title/../child::title", "title")
testXPath(t, html.Parent, "//child::*", "html")
}
func TestDescendant(t *testing.T) {
testXPath2(t, html, "descendant::*", 15)
testXPath2(t, html, "/head/descendant::*", 2)
testXPath2(t, html, "//ul/descendant::*", 7) // <li> + <a>
testXPath2(t, html, "//ul/descendant::li", 4) // <li>
}
func TestAncestor(t *testing.T) {
testXPath2(t, html, "/body/footer/ancestor::*", 2) // body>html
testXPath2(t, html, "/body/ul/li/a/ancestor::li", 3)
testXPath2(t, html, "/body/ul/li/a/ancestor-or-self::li", 3)
}
func TestFollowingSibling(t *testing.T) {
var list []*TNode
list = selectNodes(html, "//li/following-sibling::*")
for _, n := range list {
if n.Data != "li" {
t.Fatalf("expected node is li,but got:%s", n.Data)
}
}
list = selectNodes(html, "//ul/following-sibling::*") // p,footer
for _, n := range list {
if n.Data != "p" && n.Data != "footer" {
t.Fatal("expected node is not one of the following nodes: [p,footer]")
}
}
testXPath(t, html, "//ul/following-sibling::footer", "footer")
list = selectNodes(html, "//h1/following::*") // ul>li>a,p,footer
if list[0].Data != "ul" {
t.Fatal("expected node is not ul")
}
if list[1].Data != "li" {
t.Fatal("expected node is not li")
}
if list[len(list)-1].Data != "footer" {
t.Fatal("expected node is not footer")
}
}
func TestPrecedingSibling(t *testing.T) {
testXPath(t, html, "/body/footer/preceding-sibling::*", "p")
testXPath2(t, html, "/body/footer/preceding-sibling::*", 3) // p,ul,h1
list := selectNodes(html, "//h1/preceding::*") // head>title>meta
if list[0].Data != "head" {
t.Fatal("expected is not head")
}
if list[1].Data != "title" {
t.Fatal("expected is not title")
}
if list[2].Data != "meta" {
t.Fatal("expected is not meta")
}
}
func TestStarWide(t *testing.T) {
testXPath(t, html, "/head/*", "title")
testXPath2(t, html, "//ul/*", 4)
testXPath(t, html, "@*", "html")
testXPath2(t, html, "/body/h1/*", 0)
testXPath2(t, html, `//ul/*/a`, 3)
}
func TestNodeTestType(t *testing.T) {
testXPath(t, html, "//title/text()", "Hello")
testXPath(t, html, "//a[@href='/']/text()", "Home")
testXPath2(t, html, "//head/node()", 2)
testXPath2(t, html, "//ul/node()", 4)
}
func TestPosition(t *testing.T) {
testXPath3(t, html, "/head[1]", html.FirstChild) // compare to 'head' element
ul := selectNode(html, "//ul")
testXPath3(t, html, "/head[last()]", html.FirstChild)
testXPath3(t, html, "//li[1]", ul.FirstChild)
testXPath3(t, html, "//li[4]", ul.LastChild)
testXPath3(t, html, "//li[last()]", ul.LastChild)
}
func TestPredicate(t *testing.T) {
testXPath(t, html.Parent, "html[@lang='en']", "html")
testXPath(t, html, "//a[@href='/']", "a")
testXPath(t, html, "//meta[@name]", "meta")
ul := selectNode(html, "//ul")
testXPath3(t, html, "//li[position()=4]", ul.LastChild)
testXPath3(t, html, "//li[position()=1]", ul.FirstChild)
testXPath2(t, html, "//li[position()>0]", 4)
testXPath3(t, html, "//a[text()='Home']", selectNode(html, "//a[1]"))
}
func TestOr_And(t *testing.T) {
list := selectNodes(html, "//h1|//footer")
if len(list) == 0 {
t.Fatal("//h1|//footer no any node found")
}
if list[0].Data != "h1" {
t.Fatalf("expected first node of node-set is h1,but got %s", list[0].Data)
}
if list[1].Data != "footer" {
t.Fatalf("expected first node of node-set is footer,but got %s", list[1].Data)
}
list = selectNodes(html, "//a[@id=1 or @id=2]")
if list[0] != selectNode(html, "//a[@id=1]") {
t.Fatal("node is not equal")
}
if list[1] != selectNode(html, "//a[@id=2]") {
t.Fatal("node is not equal")
}
list = selectNodes(html, "//a[@id or @href]")
if list[0] != selectNode(html, "//a[@id=1]") {
t.Fatal("node is not equal")
}
if list[1] != selectNode(html, "//a[@id=2]") {
t.Fatal("node is not equal")
}
testXPath3(t, html, "//a[@id=1 and @href='/']", selectNode(html, "//a[1]"))
testXPath3(t, html, "//a[text()='Home' and @id='1']", selectNode(html, "//a[1]"))
}
func TestFunction(t *testing.T) {
testEval(t, html, "boolean(//*[@id])", true)
testEval(t, html, "boolean(//*[@x])", false)
testEval(t, html, "name(//title)", "title")
testXPath2(t, html, "//*[name()='a']", 3)
testXPath(t, html, "//*[starts-with(name(),'h1')]", "h1")
testXPath(t, html, "//*[ends-with(name(),'itle')]", "title") // Head title
testXPath2(t, html, "//*[contains(@href,'a')]", 2)
testXPath2(t, html, "//*[starts-with(@href,'/a')]", 2) // a links: `/account`,`/about`
testXPath2(t, html, "//*[ends-with(@href,'t')]", 2) // a links: `/account`,`/about`
testXPath3(t, html, "//h1[normalize-space(text())='This is a H1']", selectNode(html, "//h1"))
testXPath3(t, html, "//title[substring(.,1)='Hello']", selectNode(html, "//title"))
testXPath3(t, html, "//title[substring(text(),1,4)='Hell']", selectNode(html, "//title"))
testXPath3(t, html, "//title[substring(self::*,1,4)='Hell']", selectNode(html, "//title"))
testXPath2(t, html, "//title[substring(child::*,1)]", 0) // Here substring return boolen (false), should it?
testXPath2(t, html, "//title[substring(child::*,1) = '']", 1)
testXPath3(t, html, "//li[not(a)]", selectNode(html, "//ul/li[4]"))
testXPath2(t, html, "//li/a[not(@id='1')]", 2) // //li/a[@id!=1]
testXPath2(t, html, "//h1[string-length(normalize-space(' abc ')) = 3]", 1)
testXPath2(t, html, "//h1[string-length(normalize-space(self::text())) = 12]", 1)
testXPath2(t, html, "//title[string-length(normalize-space(child::*)) = 0]", 1)
testXPath2(t, html, "//title[string-length(self::text()) = 5]", 1) // Hello = 5
testXPath2(t, html, "//title[string-length(child::*) = 5]", 0)
testXPath2(t, html, "//ul[count(li)=4]", 1)
testEval(t, html, "true()", true)
testEval(t, html, "false()", false)
testEval(t, html, "boolean(0)", false)
testEval(t, html, "boolean(1)", true)
testEval(t, html, "sum(1+2)", float64(3))
testEval(t, html, "string(sum(1+2))", "3")
testEval(t, html, "sum(1.1+2)", float64(3.1))
testEval(t, html, "sum(//a/@id)", float64(6)) // 1+2+3
testEval(t, html, `concat("1","2","3")`, "123")
testEval(t, html, `concat(" ",//a[@id='1']/@href," ")`, " / ")
testEval(t, html, "ceiling(5.2)", float64(6))
testEval(t, html, "floor(5.2)", float64(5))
testEval(t, html, `substring-before('aa-bb','-')`, "aa")
testEval(t, html, `substring-before('aa-bb','a')`, "")
testEval(t, html, `substring-before('aa-bb','b')`, "aa-")
testEval(t, html, `substring-before('aa-bb','q')`, "")
testEval(t, html, `substring-after('aa-bb','-')`, "bb")
testEval(t, html, `substring-after('aa-bb','a')`, "a-bb")
testEval(t, html, `substring-after('aa-bb','b')`, "b")
testEval(t, html, `substring-after('aa-bb','q')`, "")
testEval(t, html,
`translate('The quick brown fox.', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')`,
"THE QUICK BROWN FOX.",
)
testEval(t, html,
`translate('The quick brown fox.', 'brown', 'red')`,
"The quick red fdx.",
)
}
func TestPanic(t *testing.T) {
// starts-with
assertPanic(t, func() { testXPath(t, html, "//*[starts-with(0, 0)]", "") })
assertPanic(t, func() { testXPath(t, html, "//*[starts-with(name(), 0)]", "") })
//ends-with
assertPanic(t, func() { testXPath(t, html, "//*[ends-with(0, 0)]", "") })
assertPanic(t, func() { testXPath(t, html, "//*[ends-with(name(), 0)]", "") })
// contains
assertPanic(t, func() { testXPath2(t, html, "//*[contains(0, 0)]", 0) })
assertPanic(t, func() { testXPath2(t, html, "//*[contains(@href, 0)]", 0) })
// sum
assertPanic(t, func() { testXPath3(t, html, "//title[sum('Hello') = 0]", nil) })
// substring
assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,'')=0]", nil) })
assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,4,'')=0]", nil) })
assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,4,4)=0]", nil) })
//assertPanic(t, func() { testXPath2(t, html, "//title[substring(child::*,0) = '']", 0) }) // Here substring return boolen (false), should it?
}
func assertPanic(t *testing.T, f func()) {
defer func() {
if r := recover(); r == nil {
t.Errorf("The code did not panic")
}
}()
f()
}
func TestEvaluate(t *testing.T) {
testEval(t, html, "count(//ul/li)", float64(4))
testEval(t, html, "//html/@lang", []string{"en"})
testEval(t, html, "//title/text()", []string{"Hello"})
}
func TestOperationOrLogical(t *testing.T) {
testXPath3(t, html, "//li[1+1]", selectNode(html, "//li[2]"))
testXPath3(t, html, "//li[5 div 2]", selectNode(html, "//li[2]"))
testXPath3(t, html, "//li[3 mod 2]", selectNode(html, "//li[1]"))
testXPath3(t, html, "//li[3 - 2]", selectNode(html, "//li[1]"))
testXPath2(t, html, "//li[position() mod 2 = 0 ]", 2) // //li[2],li[4]
testXPath2(t, html, "//a[@id>=1]", 3) // //a[@id>=1] == a[1],a[2],a[3]
testXPath2(t, html, "//a[@id<=2]", 2) // //a[@id<=2] == a[1],a[1]
testXPath2(t, html, "//a[@id<2]", 1) // //a[@id>=1] == a[1]
testXPath2(t, html, "//a[@id!=2]", 2) // //a[@id>=1] == a[1],a[3]
testXPath2(t, html, "//a[@id=1 or @id=3]", 2) // //a[@id>=1] == a[1],a[3]
testXPath3(t, html, "//a[@id=1 and @href='/']", selectNode(html, "//a[1]"))
}
func testEval(t *testing.T, root *TNode, expr string, expected interface{}) {
v := MustCompile(expr).Evaluate(createNavigator(root))
if it, ok := v.(*NodeIterator); ok {
exp, ok := expected.([]string)
if !ok {
t.Fatalf("expected value, got: %#v", v)
}
got := iterateNavs(it)
if len(exp) != len(got) {
t.Fatalf("expected: %#v, got: %#v", exp, got)
}
for i, n1 := range exp {
n2 := got[i]
if n1 != n2.Value() {
t.Fatalf("expected: %#v, got: %#v", n1, n2)
}
}
return
}
if v != expected {
t.Fatalf("expected: %#v, got: %#v", expected, v)
}
}
func testXPath(t *testing.T, root *TNode, expr string, expected string) {
node := selectNode(root, expr)
if node == nil {
t.Fatalf("`%s` returns node is nil", expr)
}
if node.Data != expected {
t.Fatalf("`%s` expected node is %s,but got %s", expr, expected, node.Data)
}
}
func testXPath2(t *testing.T, root *TNode, expr string, expected int) {
list := selectNodes(root, expr)
if len(list) != expected {
t.Fatalf("`%s` expected node numbers is %d,but got %d", expr, expected, len(list))
}
}
func testXPath3(t *testing.T, root *TNode, expr string, expected *TNode) {
node := selectNode(root, expr)
if node == nil {
t.Fatalf("`%s` returns node is nil", expr)
}
if node != expected {
t.Fatalf("`%s` %s != %s", expr, node.Value(), expected.Value())
}
}
func iterateNavs(t *NodeIterator) []*TNodeNavigator {
var nodes []*TNodeNavigator
for t.MoveNext() {
node := t.Current().(*TNodeNavigator)
nodes = append(nodes, node)
}
return nodes
}
func iterateNodes(t *NodeIterator) []*TNode {
var nodes []*TNode
for t.MoveNext() {
node := (t.Current().(*TNodeNavigator)).curr
nodes = append(nodes, node)
}
return nodes
}
func selectNode(root *TNode, expr string) (n *TNode) {
t := Select(createNavigator(root), expr)
if t.MoveNext() {
n = (t.Current().(*TNodeNavigator)).curr
}
return n
}
func selectNodes(root *TNode, expr string) []*TNode {
t := Select(createNavigator(root), expr)
return iterateNodes(t)
}
func createNavigator(n *TNode) *TNodeNavigator {
return &TNodeNavigator{curr: n, root: n, attr: -1}
}
type Attribute struct {
Key, Value string
}
type TNode struct {
Parent, FirstChild, LastChild, PrevSibling, NextSibling *TNode
Type NodeType
Data string
Attr []Attribute
}
func (n *TNode) Value() string {
if n.Type == TextNode {
return n.Data
}
var buff bytes.Buffer
var output func(*TNode)
output = func(node *TNode) {
if node.Type == TextNode {
buff.WriteString(node.Data)
}
for child := node.FirstChild; child != nil; child = child.NextSibling {
output(child)
}
}
output(n)
return buff.String()
}
// TNodeNavigator is for navigating TNode.
type TNodeNavigator struct {
curr, root *TNode
attr int
}
func (n *TNodeNavigator) NodeType() NodeType {
if n.curr.Type == ElementNode && n.attr != -1 {
return AttributeNode
}
return n.curr.Type
}
func (n *TNodeNavigator) LocalName() string {
if n.attr != -1 {
return n.curr.Attr[n.attr].Key
}
return n.curr.Data
}
func (n *TNodeNavigator) Prefix() string {
return ""
}
func (n *TNodeNavigator) Value() string {
switch n.curr.Type {
case CommentNode:
return n.curr.Data
case ElementNode:
if n.attr != -1 {
return n.curr.Attr[n.attr].Value
}
var buf bytes.Buffer
node := n.curr.FirstChild
for node != nil {
if node.Type == TextNode {
buf.WriteString(strings.TrimSpace(node.Data))
}
node = node.NextSibling
}
return buf.String()
case TextNode:
return n.curr.Data
}
return ""
}
func (n *TNodeNavigator) Copy() NodeNavigator {
n2 := *n
return &n2
}
func (n *TNodeNavigator) MoveToRoot() {
n.curr = n.root
}
func (n *TNodeNavigator) MoveToParent() bool {
if node := n.curr.Parent; node != nil {
n.curr = node
return true
}
return false
}
func (n *TNodeNavigator) MoveToNextAttribute() bool {
if n.attr >= len(n.curr.Attr)-1 {
return false
}
n.attr++
return true
}
func (n *TNodeNavigator) MoveToChild() bool {
if node := n.curr.FirstChild; node != nil {
n.curr = node
return true
}
return false
}
func (n *TNodeNavigator) MoveToFirst() bool {
if n.curr.PrevSibling == nil {
return false
}
for {
node := n.curr.PrevSibling
if node == nil {
break
}
n.curr = node
}
return true
}
func (n *TNodeNavigator) String() string {
return n.Value()
}
func (n *TNodeNavigator) MoveToNext() bool {
if node := n.curr.NextSibling; node != nil {
n.curr = node
return true
}
return false
}
func (n *TNodeNavigator) MoveToPrevious() bool {
if node := n.curr.PrevSibling; node != nil {
n.curr = node
return true
}
return false
}
func (n *TNodeNavigator) MoveTo(other NodeNavigator) bool {
node, ok := other.(*TNodeNavigator)
if !ok || node.root != n.root {
return false
}
n.curr = node.curr
n.attr = node.attr
return true
}
func createNode(data string, typ NodeType) *TNode {
return &TNode{Data: data, Type: typ, Attr: make([]Attribute, 0)}
}
func (n *TNode) createChildNode(data string, typ NodeType) *TNode {
m := createNode(data, typ)
m.Parent = n
if n.FirstChild == nil {
n.FirstChild = m
} else {
n.LastChild.NextSibling = m
m.PrevSibling = n.LastChild
}
n.LastChild = m
return m
}
func (n *TNode) appendNode(data string, typ NodeType) *TNode {
m := createNode(data, typ)
m.Parent = n.Parent
n.NextSibling = m
m.PrevSibling = n
if n.Parent != nil {
n.Parent.LastChild = m
}
return m
}
func (n *TNode) addAttribute(k, v string) {
n.Attr = append(n.Attr, Attribute{k, v})
}
func example() *TNode {
/*
<html lang="en">
<head>
<title>Hello</title>
<meta name="language" content="en"/>
</head>
<body>
<h1> This is a H1 </h1>
<ul>
<li><a id="1" href="/">Home</a></li>
<li><a id="2" href="/about">about</a></li>
<li><a id="3" href="/account">login</a></li>
<li></li>
</ul>
<p>
Hello,This is an example for gxpath.
</p>
<footer>footer script</footer>
</body>
</html>
*/
doc := createNode("", RootNode)
xhtml := doc.createChildNode("html", ElementNode)
xhtml.addAttribute("lang", "en")
// The HTML head section.
head := xhtml.createChildNode("head", ElementNode)
n := head.createChildNode("title", ElementNode)
n = n.createChildNode("Hello", TextNode)
n = head.createChildNode("meta", ElementNode)
n.addAttribute("name", "language")
n.addAttribute("content", "en")
// The HTML body section.
body := xhtml.createChildNode("body", ElementNode)
n = body.createChildNode("h1", ElementNode)
n = n.createChildNode(" This is a H1 ", TextNode)
ul := body.createChildNode("ul", ElementNode)
n = ul.createChildNode("li", ElementNode)
n = n.createChildNode("a", ElementNode)
n.addAttribute("id", "1")
n.addAttribute("href", "/")
n = n.createChildNode("Home", TextNode)
n = ul.createChildNode("li", ElementNode)
n = n.createChildNode("a", ElementNode)
n.addAttribute("id", "2")
n.addAttribute("href", "/about")
n = n.createChildNode("about", TextNode)
n = ul.createChildNode("li", ElementNode)
n = n.createChildNode("a", ElementNode)
n.addAttribute("id", "3")
n.addAttribute("href", "/account")
n = n.createChildNode("login", TextNode)
n = ul.createChildNode("li", ElementNode)
n = body.createChildNode("p", ElementNode)
n = n.createChildNode("Hello,This is an example for gxpath.", TextNode)
n = body.createChildNode("footer", ElementNode)
n = n.createChildNode("footer script", TextNode)
return xhtml
}

21
vendor/github.com/gobwas/glob/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Sergey Kamardin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

26
vendor/github.com/gobwas/glob/bench.sh generated vendored Executable file
View file

@ -0,0 +1,26 @@
#! /bin/bash
bench() {
filename="/tmp/$1-$2.bench"
if test -e "${filename}";
then
echo "Already exists ${filename}"
else
backup=`git rev-parse --abbrev-ref HEAD`
git checkout $1
echo -n "Creating ${filename}... "
go test ./... -run=NONE -bench=$2 > "${filename}" -benchmem
echo "OK"
git checkout ${backup}
sleep 5
fi
}
to=$1
current=`git rev-parse --abbrev-ref HEAD`
bench ${to} $2
bench ${current} $2
benchcmp $3 "/tmp/${to}-$2.bench" "/tmp/${current}-$2.bench"

525
vendor/github.com/gobwas/glob/compiler/compiler.go generated vendored Normal file
View file

@ -0,0 +1,525 @@
package compiler
// TODO use constructor with all matchers, and to their structs private
// TODO glue multiple Text nodes (like after QuoteMeta)
import (
"fmt"
"reflect"
"github.com/gobwas/glob/match"
"github.com/gobwas/glob/syntax/ast"
"github.com/gobwas/glob/util/runes"
)
func optimizeMatcher(matcher match.Matcher) match.Matcher {
switch m := matcher.(type) {
case match.Any:
if len(m.Separators) == 0 {
return match.NewSuper()
}
case match.AnyOf:
if len(m.Matchers) == 1 {
return m.Matchers[0]
}
return m
case match.List:
if m.Not == false && len(m.List) == 1 {
return match.NewText(string(m.List))
}
return m
case match.BTree:
m.Left = optimizeMatcher(m.Left)
m.Right = optimizeMatcher(m.Right)
r, ok := m.Value.(match.Text)
if !ok {
return m
}
var (
leftNil = m.Left == nil
rightNil = m.Right == nil
)
if leftNil && rightNil {
return match.NewText(r.Str)
}
_, leftSuper := m.Left.(match.Super)
lp, leftPrefix := m.Left.(match.Prefix)
la, leftAny := m.Left.(match.Any)
_, rightSuper := m.Right.(match.Super)
rs, rightSuffix := m.Right.(match.Suffix)
ra, rightAny := m.Right.(match.Any)
switch {
case leftSuper && rightSuper:
return match.NewContains(r.Str, false)
case leftSuper && rightNil:
return match.NewSuffix(r.Str)
case rightSuper && leftNil:
return match.NewPrefix(r.Str)
case leftNil && rightSuffix:
return match.NewPrefixSuffix(r.Str, rs.Suffix)
case rightNil && leftPrefix:
return match.NewPrefixSuffix(lp.Prefix, r.Str)
case rightNil && leftAny:
return match.NewSuffixAny(r.Str, la.Separators)
case leftNil && rightAny:
return match.NewPrefixAny(r.Str, ra.Separators)
}
return m
}
return matcher
}
func compileMatchers(matchers []match.Matcher) (match.Matcher, error) {
if len(matchers) == 0 {
return nil, fmt.Errorf("compile error: need at least one matcher")
}
if len(matchers) == 1 {
return matchers[0], nil
}
if m := glueMatchers(matchers); m != nil {
return m, nil
}
idx := -1
maxLen := -1
var val match.Matcher
for i, matcher := range matchers {
if l := matcher.Len(); l != -1 && l >= maxLen {
maxLen = l
idx = i
val = matcher
}
}
if val == nil { // not found matcher with static length
r, err := compileMatchers(matchers[1:])
if err != nil {
return nil, err
}
return match.NewBTree(matchers[0], nil, r), nil
}
left := matchers[:idx]
var right []match.Matcher
if len(matchers) > idx+1 {
right = matchers[idx+1:]
}
var l, r match.Matcher
var err error
if len(left) > 0 {
l, err = compileMatchers(left)
if err != nil {
return nil, err
}
}
if len(right) > 0 {
r, err = compileMatchers(right)
if err != nil {
return nil, err
}
}
return match.NewBTree(val, l, r), nil
}
func glueMatchers(matchers []match.Matcher) match.Matcher {
if m := glueMatchersAsEvery(matchers); m != nil {
return m
}
if m := glueMatchersAsRow(matchers); m != nil {
return m
}
return nil
}
func glueMatchersAsRow(matchers []match.Matcher) match.Matcher {
if len(matchers) <= 1 {
return nil
}
var (
c []match.Matcher
l int
)
for _, matcher := range matchers {
if ml := matcher.Len(); ml == -1 {
return nil
} else {
c = append(c, matcher)
l += ml
}
}
return match.NewRow(l, c...)
}
func glueMatchersAsEvery(matchers []match.Matcher) match.Matcher {
if len(matchers) <= 1 {
return nil
}
var (
hasAny bool
hasSuper bool
hasSingle bool
min int
separator []rune
)
for i, matcher := range matchers {
var sep []rune
switch m := matcher.(type) {
case match.Super:
sep = []rune{}
hasSuper = true
case match.Any:
sep = m.Separators
hasAny = true
case match.Single:
sep = m.Separators
hasSingle = true
min++
case match.List:
if !m.Not {
return nil
}
sep = m.List
hasSingle = true
min++
default:
return nil
}
// initialize
if i == 0 {
separator = sep
}
if runes.Equal(sep, separator) {
continue
}
return nil
}
if hasSuper && !hasAny && !hasSingle {
return match.NewSuper()
}
if hasAny && !hasSuper && !hasSingle {
return match.NewAny(separator)
}
if (hasAny || hasSuper) && min > 0 && len(separator) == 0 {
return match.NewMin(min)
}
every := match.NewEveryOf()
if min > 0 {
every.Add(match.NewMin(min))
if !hasAny && !hasSuper {
every.Add(match.NewMax(min))
}
}
if len(separator) > 0 {
every.Add(match.NewContains(string(separator), true))
}
return every
}
func minimizeMatchers(matchers []match.Matcher) []match.Matcher {
var done match.Matcher
var left, right, count int
for l := 0; l < len(matchers); l++ {
for r := len(matchers); r > l; r-- {
if glued := glueMatchers(matchers[l:r]); glued != nil {
var swap bool
if done == nil {
swap = true
} else {
cl, gl := done.Len(), glued.Len()
swap = cl > -1 && gl > -1 && gl > cl
swap = swap || count < r-l
}
if swap {
done = glued
left = l
right = r
count = r - l
}
}
}
}
if done == nil {
return matchers
}
next := append(append([]match.Matcher{}, matchers[:left]...), done)
if right < len(matchers) {
next = append(next, matchers[right:]...)
}
if len(next) == len(matchers) {
return next
}
return minimizeMatchers(next)
}
// minimizeAnyOf tries to apply some heuristics to minimize number of nodes in given tree
func minimizeTree(tree *ast.Node) *ast.Node {
switch tree.Kind {
case ast.KindAnyOf:
return minimizeTreeAnyOf(tree)
default:
return nil
}
}
// minimizeAnyOf tries to find common children of given node of AnyOf pattern
// it searches for common children from left and from right
// if any common children are found then it returns new optimized ast tree
// else it returns nil
func minimizeTreeAnyOf(tree *ast.Node) *ast.Node {
if !areOfSameKind(tree.Children, ast.KindPattern) {
return nil
}
commonLeft, commonRight := commonChildren(tree.Children)
commonLeftCount, commonRightCount := len(commonLeft), len(commonRight)
if commonLeftCount == 0 && commonRightCount == 0 { // there are no common parts
return nil
}
var result []*ast.Node
if commonLeftCount > 0 {
result = append(result, ast.NewNode(ast.KindPattern, nil, commonLeft...))
}
var anyOf []*ast.Node
for _, child := range tree.Children {
reuse := child.Children[commonLeftCount : len(child.Children)-commonRightCount]
var node *ast.Node
if len(reuse) == 0 {
// this pattern is completely reduced by commonLeft and commonRight patterns
// so it become nothing
node = ast.NewNode(ast.KindNothing, nil)
} else {
node = ast.NewNode(ast.KindPattern, nil, reuse...)
}
anyOf = appendIfUnique(anyOf, node)
}
switch {
case len(anyOf) == 1 && anyOf[0].Kind != ast.KindNothing:
result = append(result, anyOf[0])
case len(anyOf) > 1:
result = append(result, ast.NewNode(ast.KindAnyOf, nil, anyOf...))
}
if commonRightCount > 0 {
result = append(result, ast.NewNode(ast.KindPattern, nil, commonRight...))
}
return ast.NewNode(ast.KindPattern, nil, result...)
}
func commonChildren(nodes []*ast.Node) (commonLeft, commonRight []*ast.Node) {
if len(nodes) <= 1 {
return
}
// find node that has least number of children
idx := leastChildren(nodes)
if idx == -1 {
return
}
tree := nodes[idx]
treeLength := len(tree.Children)
// allocate max able size for rightCommon slice
// to get ability insert elements in reverse order (from end to start)
// without sorting
commonRight = make([]*ast.Node, treeLength)
lastRight := treeLength // will use this to get results as commonRight[lastRight:]
var (
breakLeft bool
breakRight bool
commonTotal int
)
for i, j := 0, treeLength-1; commonTotal < treeLength && j >= 0 && !(breakLeft && breakRight); i, j = i+1, j-1 {
treeLeft := tree.Children[i]
treeRight := tree.Children[j]
for k := 0; k < len(nodes) && !(breakLeft && breakRight); k++ {
// skip least children node
if k == idx {
continue
}
restLeft := nodes[k].Children[i]
restRight := nodes[k].Children[j+len(nodes[k].Children)-treeLength]
breakLeft = breakLeft || !treeLeft.Equal(restLeft)
// disable searching for right common parts, if left part is already overlapping
breakRight = breakRight || (!breakLeft && j <= i)
breakRight = breakRight || !treeRight.Equal(restRight)
}
if !breakLeft {
commonTotal++
commonLeft = append(commonLeft, treeLeft)
}
if !breakRight {
commonTotal++
lastRight = j
commonRight[j] = treeRight
}
}
commonRight = commonRight[lastRight:]
return
}
func appendIfUnique(target []*ast.Node, val *ast.Node) []*ast.Node {
for _, n := range target {
if reflect.DeepEqual(n, val) {
return target
}
}
return append(target, val)
}
func areOfSameKind(nodes []*ast.Node, kind ast.Kind) bool {
for _, n := range nodes {
if n.Kind != kind {
return false
}
}
return true
}
func leastChildren(nodes []*ast.Node) int {
min := -1
idx := -1
for i, n := range nodes {
if idx == -1 || (len(n.Children) < min) {
min = len(n.Children)
idx = i
}
}
return idx
}
func compileTreeChildren(tree *ast.Node, sep []rune) ([]match.Matcher, error) {
var matchers []match.Matcher
for _, desc := range tree.Children {
m, err := compile(desc, sep)
if err != nil {
return nil, err
}
matchers = append(matchers, optimizeMatcher(m))
}
return matchers, nil
}
func compile(tree *ast.Node, sep []rune) (m match.Matcher, err error) {
switch tree.Kind {
case ast.KindAnyOf:
// todo this could be faster on pattern_alternatives_combine_lite (see glob_test.go)
if n := minimizeTree(tree); n != nil {
return compile(n, sep)
}
matchers, err := compileTreeChildren(tree, sep)
if err != nil {
return nil, err
}
return match.NewAnyOf(matchers...), nil
case ast.KindPattern:
if len(tree.Children) == 0 {
return match.NewNothing(), nil
}
matchers, err := compileTreeChildren(tree, sep)
if err != nil {
return nil, err
}
m, err = compileMatchers(minimizeMatchers(matchers))
if err != nil {
return nil, err
}
case ast.KindAny:
m = match.NewAny(sep)
case ast.KindSuper:
m = match.NewSuper()
case ast.KindSingle:
m = match.NewSingle(sep)
case ast.KindNothing:
m = match.NewNothing()
case ast.KindList:
l := tree.Value.(ast.List)
m = match.NewList([]rune(l.Chars), l.Not)
case ast.KindRange:
r := tree.Value.(ast.Range)
m = match.NewRange(r.Lo, r.Hi, r.Not)
case ast.KindText:
t := tree.Value.(ast.Text)
m = match.NewText(t.Text)
default:
return nil, fmt.Errorf("could not compile tree: unknown node type")
}
return optimizeMatcher(m), nil
}
func Compile(tree *ast.Node, sep []rune) (match.Matcher, error) {
m, err := compile(tree, sep)
if err != nil {
return nil, err
}
return m, nil
}

624
vendor/github.com/gobwas/glob/compiler/compiler_test.go generated vendored Normal file
View file

@ -0,0 +1,624 @@
package compiler
import (
"github.com/gobwas/glob/match"
"github.com/gobwas/glob/match/debug"
"github.com/gobwas/glob/syntax/ast"
"reflect"
"testing"
)
var separators = []rune{'.'}
func TestCommonChildren(t *testing.T) {
for i, test := range []struct {
nodes []*ast.Node
left []*ast.Node
right []*ast.Node
}{
{
nodes: []*ast.Node{
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"z"}),
ast.NewNode(ast.KindText, ast.Text{"c"}),
),
},
},
{
nodes: []*ast.Node{
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"z"}),
ast.NewNode(ast.KindText, ast.Text{"c"}),
),
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"b"}),
ast.NewNode(ast.KindText, ast.Text{"c"}),
),
},
left: []*ast.Node{
ast.NewNode(ast.KindText, ast.Text{"a"}),
},
right: []*ast.Node{
ast.NewNode(ast.KindText, ast.Text{"c"}),
},
},
{
nodes: []*ast.Node{
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"b"}),
ast.NewNode(ast.KindText, ast.Text{"c"}),
ast.NewNode(ast.KindText, ast.Text{"d"}),
),
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"b"}),
ast.NewNode(ast.KindText, ast.Text{"c"}),
ast.NewNode(ast.KindText, ast.Text{"c"}),
ast.NewNode(ast.KindText, ast.Text{"d"}),
),
},
left: []*ast.Node{
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"b"}),
},
right: []*ast.Node{
ast.NewNode(ast.KindText, ast.Text{"c"}),
ast.NewNode(ast.KindText, ast.Text{"d"}),
},
},
{
nodes: []*ast.Node{
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"b"}),
ast.NewNode(ast.KindText, ast.Text{"c"}),
),
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"b"}),
ast.NewNode(ast.KindText, ast.Text{"b"}),
ast.NewNode(ast.KindText, ast.Text{"c"}),
),
},
left: []*ast.Node{
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"b"}),
},
right: []*ast.Node{
ast.NewNode(ast.KindText, ast.Text{"c"}),
},
},
{
nodes: []*ast.Node{
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"d"}),
),
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"d"}),
),
ast.NewNode(ast.KindNothing, nil,
ast.NewNode(ast.KindText, ast.Text{"a"}),
ast.NewNode(ast.KindText, ast.Text{"e"}),
),
},
left: []*ast.Node{
ast.NewNode(ast.KindText, ast.Text{"a"}),
},
right: []*ast.Node{},
},
} {
left, right := commonChildren(test.nodes)
if !nodesEqual(left, test.left) {
t.Errorf("[%d] left, right := commonChildren(); left = %v; want %v", i, left, test.left)
}
if !nodesEqual(right, test.right) {
t.Errorf("[%d] left, right := commonChildren(); right = %v; want %v", i, right, test.right)
}
}
}
func nodesEqual(a, b []*ast.Node) bool {
if len(a) != len(b) {
return false
}
for i, av := range a {
if !av.Equal(b[i]) {
return false
}
}
return true
}
func TestGlueMatchers(t *testing.T) {
for id, test := range []struct {
in []match.Matcher
exp match.Matcher
}{
{
[]match.Matcher{
match.NewSuper(),
match.NewSingle(nil),
},
match.NewMin(1),
},
{
[]match.Matcher{
match.NewAny(separators),
match.NewSingle(separators),
},
match.EveryOf{match.Matchers{
match.NewMin(1),
match.NewContains(string(separators), true),
}},
},
{
[]match.Matcher{
match.NewSingle(nil),
match.NewSingle(nil),
match.NewSingle(nil),
},
match.EveryOf{match.Matchers{
match.NewMin(3),
match.NewMax(3),
}},
},
{
[]match.Matcher{
match.NewList([]rune{'a'}, true),
match.NewAny([]rune{'a'}),
},
match.EveryOf{match.Matchers{
match.NewMin(1),
match.NewContains("a", true),
}},
},
} {
act, err := compileMatchers(test.in)
if err != nil {
t.Errorf("#%d convert matchers error: %s", id, err)
continue
}
if !reflect.DeepEqual(act, test.exp) {
t.Errorf("#%d unexpected convert matchers result:\nact: %#v;\nexp: %#v", id, act, test.exp)
continue
}
}
}
func TestCompileMatchers(t *testing.T) {
for id, test := range []struct {
in []match.Matcher
exp match.Matcher
}{
{
[]match.Matcher{
match.NewSuper(),
match.NewSingle(separators),
match.NewText("c"),
},
match.NewBTree(
match.NewText("c"),
match.NewBTree(
match.NewSingle(separators),
match.NewSuper(),
nil,
),
nil,
),
},
{
[]match.Matcher{
match.NewAny(nil),
match.NewText("c"),
match.NewAny(nil),
},
match.NewBTree(
match.NewText("c"),
match.NewAny(nil),
match.NewAny(nil),
),
},
{
[]match.Matcher{
match.NewRange('a', 'c', true),
match.NewList([]rune{'z', 't', 'e'}, false),
match.NewText("c"),
match.NewSingle(nil),
},
match.NewRow(
4,
match.Matchers{
match.NewRange('a', 'c', true),
match.NewList([]rune{'z', 't', 'e'}, false),
match.NewText("c"),
match.NewSingle(nil),
}...,
),
},
} {
act, err := compileMatchers(test.in)
if err != nil {
t.Errorf("#%d convert matchers error: %s", id, err)
continue
}
if !reflect.DeepEqual(act, test.exp) {
t.Errorf("#%d unexpected convert matchers result:\nact: %#v\nexp: %#v", id, act, test.exp)
continue
}
}
}
func TestConvertMatchers(t *testing.T) {
for id, test := range []struct {
in, exp []match.Matcher
}{
{
[]match.Matcher{
match.NewRange('a', 'c', true),
match.NewList([]rune{'z', 't', 'e'}, false),
match.NewText("c"),
match.NewSingle(nil),
match.NewAny(nil),
},
[]match.Matcher{
match.NewRow(
4,
[]match.Matcher{
match.NewRange('a', 'c', true),
match.NewList([]rune{'z', 't', 'e'}, false),
match.NewText("c"),
match.NewSingle(nil),
}...,
),
match.NewAny(nil),
},
},
{
[]match.Matcher{
match.NewRange('a', 'c', true),
match.NewList([]rune{'z', 't', 'e'}, false),
match.NewText("c"),
match.NewSingle(nil),
match.NewAny(nil),
match.NewSingle(nil),
match.NewSingle(nil),
match.NewAny(nil),
},
[]match.Matcher{
match.NewRow(
3,
match.Matchers{
match.NewRange('a', 'c', true),
match.NewList([]rune{'z', 't', 'e'}, false),
match.NewText("c"),
}...,
),
match.NewMin(3),
},
},
} {
act := minimizeMatchers(test.in)
if !reflect.DeepEqual(act, test.exp) {
t.Errorf("#%d unexpected convert matchers 2 result:\nact: %#v\nexp: %#v", id, act, test.exp)
continue
}
}
}
func TestCompiler(t *testing.T) {
for id, test := range []struct {
ast *ast.Node
result match.Matcher
sep []rune
}{
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
),
result: match.NewText("abc"),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil),
),
sep: separators,
result: match.NewAny(separators),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil),
),
result: match.NewSuper(),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindSuper, nil),
),
result: match.NewSuper(),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindSingle, nil),
),
sep: separators,
result: match.NewSingle(separators),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindRange, ast.Range{
Lo: 'a',
Hi: 'z',
Not: true,
}),
),
result: match.NewRange('a', 'z', true),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindList, ast.List{
Chars: "abc",
Not: true,
}),
),
result: match.NewList([]rune{'a', 'b', 'c'}, true),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindSingle, nil),
ast.NewNode(ast.KindSingle, nil),
ast.NewNode(ast.KindSingle, nil),
),
sep: separators,
result: match.EveryOf{Matchers: match.Matchers{
match.NewMin(3),
match.NewContains(string(separators), true),
}},
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindSingle, nil),
ast.NewNode(ast.KindSingle, nil),
ast.NewNode(ast.KindSingle, nil),
),
result: match.NewMin(3),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindSingle, nil),
),
sep: separators,
result: match.NewBTree(
match.NewRow(
4,
match.Matchers{
match.NewText("abc"),
match.NewSingle(separators),
}...,
),
match.NewAny(separators),
nil,
),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"/"}),
ast.NewNode(ast.KindAnyOf, nil,
ast.NewNode(ast.KindText, ast.Text{"z"}),
ast.NewNode(ast.KindText, ast.Text{"ab"}),
),
ast.NewNode(ast.KindSuper, nil),
),
sep: separators,
result: match.NewBTree(
match.NewText("/"),
nil,
match.NewBTree(
match.NewAnyOf(match.NewText("z"), match.NewText("ab")),
nil,
match.NewSuper(),
),
),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindSuper, nil),
ast.NewNode(ast.KindSingle, nil),
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindSingle, nil),
),
sep: separators,
result: match.NewBTree(
match.NewRow(
5,
match.Matchers{
match.NewSingle(separators),
match.NewText("abc"),
match.NewSingle(separators),
}...,
),
match.NewSuper(),
nil,
),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindText, ast.Text{"abc"}),
),
result: match.NewSuffix("abc"),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindAny, nil),
),
result: match.NewPrefix("abc"),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindText, ast.Text{"def"}),
),
result: match.NewPrefixSuffix("abc", "def"),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindAny, nil),
),
result: match.NewContains("abc", false),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindAny, nil),
ast.NewNode(ast.KindAny, nil),
),
sep: separators,
result: match.NewBTree(
match.NewText("abc"),
match.NewAny(separators),
match.NewAny(separators),
),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindSuper, nil),
ast.NewNode(ast.KindSingle, nil),
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindSuper, nil),
ast.NewNode(ast.KindSingle, nil),
),
result: match.NewBTree(
match.NewText("abc"),
match.NewMin(1),
match.NewMin(1),
),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
),
result: match.NewText("abc"),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAnyOf, nil,
ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAnyOf, nil,
ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
),
),
),
),
),
result: match.NewText("abc"),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAnyOf, nil,
ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindSingle, nil),
),
ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindList, ast.List{Chars: "def"}),
),
ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
),
ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
),
),
),
result: match.NewBTree(
match.NewText("abc"),
nil,
match.AnyOf{Matchers: match.Matchers{
match.NewSingle(nil),
match.NewList([]rune{'d', 'e', 'f'}, false),
match.NewNothing(),
}},
),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'z'}),
ast.NewNode(ast.KindRange, ast.Range{Lo: 'a', Hi: 'x', Not: true}),
ast.NewNode(ast.KindAny, nil),
),
result: match.NewBTree(
match.NewRow(
2,
match.Matchers{
match.NewRange('a', 'z', false),
match.NewRange('a', 'x', true),
}...,
),
nil,
match.NewSuper(),
),
},
{
ast: ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindAnyOf, nil,
ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindList, ast.List{Chars: "abc"}),
ast.NewNode(ast.KindText, ast.Text{"ghi"}),
),
ast.NewNode(ast.KindPattern, nil,
ast.NewNode(ast.KindText, ast.Text{"abc"}),
ast.NewNode(ast.KindList, ast.List{Chars: "def"}),
ast.NewNode(ast.KindText, ast.Text{"ghi"}),
),
),
),
result: match.NewRow(
7,
match.Matchers{
match.NewText("abc"),
match.AnyOf{Matchers: match.Matchers{
match.NewList([]rune{'a', 'b', 'c'}, false),
match.NewList([]rune{'d', 'e', 'f'}, false),
}},
match.NewText("ghi"),
}...,
),
},
} {
m, err := Compile(test.ast, test.sep)
if err != nil {
t.Errorf("compilation error: %s", err)
continue
}
if !reflect.DeepEqual(m, test.result) {
t.Errorf("[%d] Compile():\nexp: %#v\nact: %#v\n\ngraphviz:\nexp:\n%s\nact:\n%s\n", id, test.result, m, debug.Graphviz("", test.result.(match.Matcher)), debug.Graphviz("", m.(match.Matcher)))
continue
}
}
}

80
vendor/github.com/gobwas/glob/glob.go generated vendored Normal file
View file

@ -0,0 +1,80 @@
package glob
import (
"github.com/gobwas/glob/compiler"
"github.com/gobwas/glob/syntax"
)
// Glob represents compiled glob pattern.
type Glob interface {
Match(string) bool
}
// Compile creates Glob for given pattern and strings (if any present after pattern) as separators.
// The pattern syntax is:
//
// pattern:
// { term }
//
// term:
// `*` matches any sequence of non-separator characters
// `**` matches any sequence of characters
// `?` matches any single non-separator character
// `[` [ `!` ] { character-range } `]`
// character class (must be non-empty)
// `{` pattern-list `}`
// pattern alternatives
// c matches character c (c != `*`, `**`, `?`, `\`, `[`, `{`, `}`)
// `\` c matches character c
//
// character-range:
// c matches character c (c != `\\`, `-`, `]`)
// `\` c matches character c
// lo `-` hi matches character c for lo <= c <= hi
//
// pattern-list:
// pattern { `,` pattern }
// comma-separated (without spaces) patterns
//
func Compile(pattern string, separators ...rune) (Glob, error) {
ast, err := syntax.Parse(pattern)
if err != nil {
return nil, err
}
matcher, err := compiler.Compile(ast, separators)
if err != nil {
return nil, err
}
return matcher, nil
}
// MustCompile is the same as Compile, except that if Compile returns error, this will panic
func MustCompile(pattern string, separators ...rune) Glob {
g, err := Compile(pattern, separators...)
if err != nil {
panic(err)
}
return g
}
// QuoteMeta returns a string that quotes all glob pattern meta characters
// inside the argument text; For example, QuoteMeta(`{foo*}`) returns `\[foo\*\]`.
func QuoteMeta(s string) string {
b := make([]byte, 2*len(s))
// a byte loop is correct because all meta characters are ASCII
j := 0
for i := 0; i < len(s); i++ {
if syntax.Special(s[i]) {
b[j] = '\\'
j++
}
b[j] = s[i]
j++
}
return string(b[0:j])
}

531
vendor/github.com/gobwas/glob/glob_test.go generated vendored Normal file
View file

@ -0,0 +1,531 @@
package glob
import (
"regexp"
"testing"
)
const (
pattern_all = "[a-z][!a-x]*cat*[h][!b]*eyes*"
regexp_all = `^[a-z][^a-x].*cat.*[h][^b].*eyes.*$`
fixture_all_match = "my cat has very bright eyes"
fixture_all_mismatch = "my dog has very bright eyes"
pattern_plain = "google.com"
regexp_plain = `^google\.com$`
fixture_plain_match = "google.com"
fixture_plain_mismatch = "gobwas.com"
pattern_multiple = "https://*.google.*"
regexp_multiple = `^https:\/\/.*\.google\..*$`
fixture_multiple_match = "https://account.google.com"
fixture_multiple_mismatch = "https://google.com"
pattern_alternatives = "{https://*.google.*,*yandex.*,*yahoo.*,*mail.ru}"
regexp_alternatives = `^(https:\/\/.*\.google\..*|.*yandex\..*|.*yahoo\..*|.*mail\.ru)$`
fixture_alternatives_match = "http://yahoo.com"
fixture_alternatives_mismatch = "http://google.com"
pattern_alternatives_suffix = "{https://*gobwas.com,http://exclude.gobwas.com}"
regexp_alternatives_suffix = `^(https:\/\/.*gobwas\.com|http://exclude.gobwas.com)$`
fixture_alternatives_suffix_first_match = "https://safe.gobwas.com"
fixture_alternatives_suffix_first_mismatch = "http://safe.gobwas.com"
fixture_alternatives_suffix_second = "http://exclude.gobwas.com"
pattern_prefix = "abc*"
regexp_prefix = `^abc.*$`
pattern_suffix = "*def"
regexp_suffix = `^.*def$`
pattern_prefix_suffix = "ab*ef"
regexp_prefix_suffix = `^ab.*ef$`
fixture_prefix_suffix_match = "abcdef"
fixture_prefix_suffix_mismatch = "af"
pattern_alternatives_combine_lite = "{abc*def,abc?def,abc[zte]def}"
regexp_alternatives_combine_lite = `^(abc.*def|abc.def|abc[zte]def)$`
fixture_alternatives_combine_lite = "abczdef"
pattern_alternatives_combine_hard = "{abc*[a-c]def,abc?[d-g]def,abc[zte]?def}"
regexp_alternatives_combine_hard = `^(abc.*[a-c]def|abc.[d-g]def|abc[zte].def)$`
fixture_alternatives_combine_hard = "abczqdef"
)
type test struct {
pattern, match string
should bool
delimiters []rune
}
func glob(s bool, p, m string, d ...rune) test {
return test{p, m, s, d}
}
func TestGlob(t *testing.T) {
for _, test := range []test{
glob(true, "* ?at * eyes", "my cat has very bright eyes"),
glob(true, "", ""),
glob(false, "", "b"),
glob(true, "*ä", "åä"),
glob(true, "abc", "abc"),
glob(true, "a*c", "abc"),
glob(true, "a*c", "a12345c"),
glob(true, "a?c", "a1c"),
glob(true, "a.b", "a.b", '.'),
glob(true, "a.*", "a.b", '.'),
glob(true, "a.**", "a.b.c", '.'),
glob(true, "a.?.c", "a.b.c", '.'),
glob(true, "a.?.?", "a.b.c", '.'),
glob(true, "?at", "cat"),
glob(true, "?at", "fat"),
glob(true, "*", "abc"),
glob(true, `\*`, "*"),
glob(true, "**", "a.b.c", '.'),
glob(false, "?at", "at"),
glob(false, "?at", "fat", 'f'),
glob(false, "a.*", "a.b.c", '.'),
glob(false, "a.?.c", "a.bb.c", '.'),
glob(false, "*", "a.b.c", '.'),
glob(true, "*test", "this is a test"),
glob(true, "this*", "this is a test"),
glob(true, "*is *", "this is a test"),
glob(true, "*is*a*", "this is a test"),
glob(true, "**test**", "this is a test"),
glob(true, "**is**a***test*", "this is a test"),
glob(false, "*is", "this is a test"),
glob(false, "*no*", "this is a test"),
glob(true, "[!a]*", "this is a test3"),
glob(true, "*abc", "abcabc"),
glob(true, "**abc", "abcabc"),
glob(true, "???", "abc"),
glob(true, "?*?", "abc"),
glob(true, "?*?", "ac"),
glob(false, "sta", "stagnation"),
glob(true, "sta*", "stagnation"),
glob(false, "sta?", "stagnation"),
glob(false, "sta?n", "stagnation"),
glob(true, "{abc,def}ghi", "defghi"),
glob(true, "{abc,abcd}a", "abcda"),
glob(true, "{a,ab}{bc,f}", "abc"),
glob(true, "{*,**}{a,b}", "ab"),
glob(false, "{*,**}{a,b}", "ac"),
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/rate"),
glob(true, "/{rate,[0-9][0-9][0-9]}*", "/rate"),
glob(true, "/{rate,[a-z][a-z][a-z]}*", "/usd"),
glob(true, "{*.google.*,*.yandex.*}", "www.google.com", '.'),
glob(true, "{*.google.*,*.yandex.*}", "www.yandex.com", '.'),
glob(false, "{*.google.*,*.yandex.*}", "yandex.com", '.'),
glob(false, "{*.google.*,*.yandex.*}", "google.com", '.'),
glob(true, "{*.google.*,yandex.*}", "www.google.com", '.'),
glob(true, "{*.google.*,yandex.*}", "yandex.com", '.'),
glob(false, "{*.google.*,yandex.*}", "www.yandex.com", '.'),
glob(false, "{*.google.*,yandex.*}", "google.com", '.'),
glob(true, "*//{,*.}example.com", "https://www.example.com"),
glob(true, "*//{,*.}example.com", "http://example.com"),
glob(false, "*//{,*.}example.com", "http://example.com.net"),
glob(true, pattern_all, fixture_all_match),
glob(false, pattern_all, fixture_all_mismatch),
glob(true, pattern_plain, fixture_plain_match),
glob(false, pattern_plain, fixture_plain_mismatch),
glob(true, pattern_multiple, fixture_multiple_match),
glob(false, pattern_multiple, fixture_multiple_mismatch),
glob(true, pattern_alternatives, fixture_alternatives_match),
glob(false, pattern_alternatives, fixture_alternatives_mismatch),
glob(true, pattern_alternatives_suffix, fixture_alternatives_suffix_first_match),
glob(false, pattern_alternatives_suffix, fixture_alternatives_suffix_first_mismatch),
glob(true, pattern_alternatives_suffix, fixture_alternatives_suffix_second),
glob(true, pattern_alternatives_combine_hard, fixture_alternatives_combine_hard),
glob(true, pattern_alternatives_combine_lite, fixture_alternatives_combine_lite),
glob(true, pattern_prefix, fixture_prefix_suffix_match),
glob(false, pattern_prefix, fixture_prefix_suffix_mismatch),
glob(true, pattern_suffix, fixture_prefix_suffix_match),
glob(false, pattern_suffix, fixture_prefix_suffix_mismatch),
glob(true, pattern_prefix_suffix, fixture_prefix_suffix_match),
glob(false, pattern_prefix_suffix, fixture_prefix_suffix_mismatch),
} {
t.Run("", func(t *testing.T) {
g := MustCompile(test.pattern, test.delimiters...)
result := g.Match(test.match)
if result != test.should {
t.Errorf(
"pattern %q matching %q should be %v but got %v\n%s",
test.pattern, test.match, test.should, result, g,
)
}
})
}
}
func TestQuoteMeta(t *testing.T) {
for id, test := range []struct {
in, out string
}{
{
in: `[foo*]`,
out: `\[foo\*\]`,
},
{
in: `{foo*}`,
out: `\{foo\*\}`,
},
{
in: `*?\[]{}`,
out: `\*\?\\\[\]\{\}`,
},
{
in: `some text and *?\[]{}`,
out: `some text and \*\?\\\[\]\{\}`,
},
} {
act := QuoteMeta(test.in)
if act != test.out {
t.Errorf("#%d QuoteMeta(%q) = %q; want %q", id, test.in, act, test.out)
}
if _, err := Compile(act); err != nil {
t.Errorf("#%d _, err := Compile(QuoteMeta(%q) = %q); err = %q", id, test.in, act, err)
}
}
}
func BenchmarkParseGlob(b *testing.B) {
for i := 0; i < b.N; i++ {
Compile(pattern_all)
}
}
func BenchmarkParseRegexp(b *testing.B) {
for i := 0; i < b.N; i++ {
regexp.MustCompile(regexp_all)
}
}
func BenchmarkAllGlobMatch(b *testing.B) {
m, _ := Compile(pattern_all)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_all_match)
}
}
func BenchmarkAllGlobMatchParallel(b *testing.B) {
m, _ := Compile(pattern_all)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = m.Match(fixture_all_match)
}
})
}
func BenchmarkAllRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_all)
f := []byte(fixture_all_match)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkAllGlobMismatch(b *testing.B) {
m, _ := Compile(pattern_all)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_all_mismatch)
}
}
func BenchmarkAllGlobMismatchParallel(b *testing.B) {
m, _ := Compile(pattern_all)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = m.Match(fixture_all_mismatch)
}
})
}
func BenchmarkAllRegexpMismatch(b *testing.B) {
m := regexp.MustCompile(regexp_all)
f := []byte(fixture_all_mismatch)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkMultipleGlobMatch(b *testing.B) {
m, _ := Compile(pattern_multiple)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_multiple_match)
}
}
func BenchmarkMultipleRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_multiple)
f := []byte(fixture_multiple_match)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkMultipleGlobMismatch(b *testing.B) {
m, _ := Compile(pattern_multiple)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_multiple_mismatch)
}
}
func BenchmarkMultipleRegexpMismatch(b *testing.B) {
m := regexp.MustCompile(regexp_multiple)
f := []byte(fixture_multiple_mismatch)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkAlternativesGlobMatch(b *testing.B) {
m, _ := Compile(pattern_alternatives)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_alternatives_match)
}
}
func BenchmarkAlternativesGlobMismatch(b *testing.B) {
m, _ := Compile(pattern_alternatives)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_alternatives_mismatch)
}
}
func BenchmarkAlternativesRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_alternatives)
f := []byte(fixture_alternatives_match)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkAlternativesRegexpMismatch(b *testing.B) {
m := regexp.MustCompile(regexp_alternatives)
f := []byte(fixture_alternatives_mismatch)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkAlternativesSuffixFirstGlobMatch(b *testing.B) {
m, _ := Compile(pattern_alternatives_suffix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_alternatives_suffix_first_match)
}
}
func BenchmarkAlternativesSuffixFirstGlobMismatch(b *testing.B) {
m, _ := Compile(pattern_alternatives_suffix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_alternatives_suffix_first_mismatch)
}
}
func BenchmarkAlternativesSuffixSecondGlobMatch(b *testing.B) {
m, _ := Compile(pattern_alternatives_suffix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_alternatives_suffix_second)
}
}
func BenchmarkAlternativesCombineLiteGlobMatch(b *testing.B) {
m, _ := Compile(pattern_alternatives_combine_lite)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_alternatives_combine_lite)
}
}
func BenchmarkAlternativesCombineHardGlobMatch(b *testing.B) {
m, _ := Compile(pattern_alternatives_combine_hard)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_alternatives_combine_hard)
}
}
func BenchmarkAlternativesSuffixFirstRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_alternatives_suffix)
f := []byte(fixture_alternatives_suffix_first_match)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkAlternativesSuffixFirstRegexpMismatch(b *testing.B) {
m := regexp.MustCompile(regexp_alternatives_suffix)
f := []byte(fixture_alternatives_suffix_first_mismatch)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkAlternativesSuffixSecondRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_alternatives_suffix)
f := []byte(fixture_alternatives_suffix_second)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkAlternativesCombineLiteRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_alternatives_combine_lite)
f := []byte(fixture_alternatives_combine_lite)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkAlternativesCombineHardRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_alternatives_combine_hard)
f := []byte(fixture_alternatives_combine_hard)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkPlainGlobMatch(b *testing.B) {
m, _ := Compile(pattern_plain)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_plain_match)
}
}
func BenchmarkPlainRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_plain)
f := []byte(fixture_plain_match)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkPlainGlobMismatch(b *testing.B) {
m, _ := Compile(pattern_plain)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_plain_mismatch)
}
}
func BenchmarkPlainRegexpMismatch(b *testing.B) {
m := regexp.MustCompile(regexp_plain)
f := []byte(fixture_plain_mismatch)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkPrefixGlobMatch(b *testing.B) {
m, _ := Compile(pattern_prefix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_prefix_suffix_match)
}
}
func BenchmarkPrefixRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_prefix)
f := []byte(fixture_prefix_suffix_match)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkPrefixGlobMismatch(b *testing.B) {
m, _ := Compile(pattern_prefix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_prefix_suffix_mismatch)
}
}
func BenchmarkPrefixRegexpMismatch(b *testing.B) {
m := regexp.MustCompile(regexp_prefix)
f := []byte(fixture_prefix_suffix_mismatch)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkSuffixGlobMatch(b *testing.B) {
m, _ := Compile(pattern_suffix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_prefix_suffix_match)
}
}
func BenchmarkSuffixRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_suffix)
f := []byte(fixture_prefix_suffix_match)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkSuffixGlobMismatch(b *testing.B) {
m, _ := Compile(pattern_suffix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_prefix_suffix_mismatch)
}
}
func BenchmarkSuffixRegexpMismatch(b *testing.B) {
m := regexp.MustCompile(regexp_suffix)
f := []byte(fixture_prefix_suffix_mismatch)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkPrefixSuffixGlobMatch(b *testing.B) {
m, _ := Compile(pattern_prefix_suffix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_prefix_suffix_match)
}
}
func BenchmarkPrefixSuffixRegexpMatch(b *testing.B) {
m := regexp.MustCompile(regexp_prefix_suffix)
f := []byte(fixture_prefix_suffix_match)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}
func BenchmarkPrefixSuffixGlobMismatch(b *testing.B) {
m, _ := Compile(pattern_prefix_suffix)
for i := 0; i < b.N; i++ {
_ = m.Match(fixture_prefix_suffix_mismatch)
}
}
func BenchmarkPrefixSuffixRegexpMismatch(b *testing.B) {
m := regexp.MustCompile(regexp_prefix_suffix)
f := []byte(fixture_prefix_suffix_mismatch)
for i := 0; i < b.N; i++ {
_ = m.Match(f)
}
}

45
vendor/github.com/gobwas/glob/match/any.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
package match
import (
"fmt"
"github.com/gobwas/glob/util/strings"
)
type Any struct {
Separators []rune
}
func NewAny(s []rune) Any {
return Any{s}
}
func (self Any) Match(s string) bool {
return strings.IndexAnyRunes(s, self.Separators) == -1
}
func (self Any) Index(s string) (int, []int) {
found := strings.IndexAnyRunes(s, self.Separators)
switch found {
case -1:
case 0:
return 0, segments0
default:
s = s[:found]
}
segments := acquireSegments(len(s))
for i := range s {
segments = append(segments, i)
}
segments = append(segments, len(s))
return 0, segments
}
func (self Any) Len() int {
return lenNo
}
func (self Any) String() string {
return fmt.Sprintf("<any:![%s]>", string(self.Separators))
}

82
vendor/github.com/gobwas/glob/match/any_of.go generated vendored Normal file
View file

@ -0,0 +1,82 @@
package match
import "fmt"
type AnyOf struct {
Matchers Matchers
}
func NewAnyOf(m ...Matcher) AnyOf {
return AnyOf{Matchers(m)}
}
func (self *AnyOf) Add(m Matcher) error {
self.Matchers = append(self.Matchers, m)
return nil
}
func (self AnyOf) Match(s string) bool {
for _, m := range self.Matchers {
if m.Match(s) {
return true
}
}
return false
}
func (self AnyOf) Index(s string) (int, []int) {
index := -1
segments := acquireSegments(len(s))
for _, m := range self.Matchers {
idx, seg := m.Index(s)
if idx == -1 {
continue
}
if index == -1 || idx < index {
index = idx
segments = append(segments[:0], seg...)
continue
}
if idx > index {
continue
}
// here idx == index
segments = appendMerge(segments, seg)
}
if index == -1 {
releaseSegments(segments)
return -1, nil
}
return index, segments
}
func (self AnyOf) Len() (l int) {
l = -1
for _, m := range self.Matchers {
ml := m.Len()
switch {
case l == -1:
l = ml
continue
case ml == -1:
return -1
case l != ml:
return -1
}
}
return
}
func (self AnyOf) String() string {
return fmt.Sprintf("<any_of:[%s]>", self.Matchers)
}

53
vendor/github.com/gobwas/glob/match/any_of_test.go generated vendored Normal file
View file

@ -0,0 +1,53 @@
package match
import (
"reflect"
"testing"
)
func TestAnyOfIndex(t *testing.T) {
for id, test := range []struct {
matchers Matchers
fixture string
index int
segments []int
}{
{
Matchers{
NewAny(nil),
NewText("b"),
NewText("c"),
},
"abc",
0,
[]int{0, 1, 2, 3},
},
{
Matchers{
NewPrefix("b"),
NewSuffix("c"),
},
"abc",
0,
[]int{3},
},
{
Matchers{
NewList([]rune("[def]"), false),
NewList([]rune("[abc]"), false),
},
"abcdef",
0,
[]int{1},
},
} {
everyOf := NewAnyOf(test.matchers...)
index, segments := everyOf.Index(test.fixture)
if index != test.index {
t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index)
}
if !reflect.DeepEqual(segments, test.segments) {
t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments)
}
}
}

57
vendor/github.com/gobwas/glob/match/any_test.go generated vendored Normal file
View file

@ -0,0 +1,57 @@
package match
import (
"reflect"
"testing"
)
func TestAnyIndex(t *testing.T) {
for id, test := range []struct {
sep []rune
fixture string
index int
segments []int
}{
{
[]rune{'.'},
"abc",
0,
[]int{0, 1, 2, 3},
},
{
[]rune{'.'},
"abc.def",
0,
[]int{0, 1, 2, 3},
},
} {
p := NewAny(test.sep)
index, segments := p.Index(test.fixture)
if index != test.index {
t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index)
}
if !reflect.DeepEqual(segments, test.segments) {
t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments)
}
}
}
func BenchmarkIndexAny(b *testing.B) {
m := NewAny(bench_separators)
for i := 0; i < b.N; i++ {
_, s := m.Index(bench_pattern)
releaseSegments(s)
}
}
func BenchmarkIndexAnyParallel(b *testing.B) {
m := NewAny(bench_separators)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, s := m.Index(bench_pattern)
releaseSegments(s)
}
})
}

185
vendor/github.com/gobwas/glob/match/btree.go generated vendored Normal file
View file

@ -0,0 +1,185 @@
package match
import (
"fmt"
"unicode/utf8"
)
type BTree struct {
Value Matcher
Left Matcher
Right Matcher
ValueLengthRunes int
LeftLengthRunes int
RightLengthRunes int
LengthRunes int
}
func NewBTree(Value, Left, Right Matcher) (tree BTree) {
tree.Value = Value
tree.Left = Left
tree.Right = Right
lenOk := true
if tree.ValueLengthRunes = Value.Len(); tree.ValueLengthRunes == -1 {
lenOk = false
}
if Left != nil {
if tree.LeftLengthRunes = Left.Len(); tree.LeftLengthRunes == -1 {
lenOk = false
}
}
if Right != nil {
if tree.RightLengthRunes = Right.Len(); tree.RightLengthRunes == -1 {
lenOk = false
}
}
if lenOk {
tree.LengthRunes = tree.LeftLengthRunes + tree.ValueLengthRunes + tree.RightLengthRunes
} else {
tree.LengthRunes = -1
}
return tree
}
func (self BTree) Len() int {
return self.LengthRunes
}
// todo?
func (self BTree) Index(s string) (index int, segments []int) {
//inputLen := len(s)
//// try to cut unnecessary parts
//// by knowledge of length of right and left part
//offset, limit := self.offsetLimit(inputLen)
//for offset < limit {
// // search for matching part in substring
// vi, segments := self.Value.Index(s[offset:limit])
// if index == -1 {
// return -1, nil
// }
// if self.Left == nil {
// if index != offset {
// return -1, nil
// }
// } else {
// left := s[:offset+vi]
// i := self.Left.IndexSuffix(left)
// if i == -1 {
// return -1, nil
// }
// index = i
// }
// if self.Right != nil {
// for _, seg := range segments {
// right := s[:offset+vi+seg]
// }
// }
// l := s[:offset+index]
// var left bool
// if self.Left != nil {
// left = self.Left.Index(l)
// } else {
// left = l == ""
// }
//}
return -1, nil
}
func (self BTree) Match(s string) bool {
inputLen := len(s)
// try to cut unnecessary parts
// by knowledge of length of right and left part
offset, limit := self.offsetLimit(inputLen)
for offset < limit {
// search for matching part in substring
index, segments := self.Value.Index(s[offset:limit])
if index == -1 {
releaseSegments(segments)
return false
}
l := s[:offset+index]
var left bool
if self.Left != nil {
left = self.Left.Match(l)
} else {
left = l == ""
}
if left {
for i := len(segments) - 1; i >= 0; i-- {
length := segments[i]
var right bool
var r string
// if there is no string for the right branch
if inputLen <= offset+index+length {
r = ""
} else {
r = s[offset+index+length:]
}
if self.Right != nil {
right = self.Right.Match(r)
} else {
right = r == ""
}
if right {
releaseSegments(segments)
return true
}
}
}
_, step := utf8.DecodeRuneInString(s[offset+index:])
offset += index + step
releaseSegments(segments)
}
return false
}
func (self BTree) offsetLimit(inputLen int) (offset int, limit int) {
// self.Length, self.RLen and self.LLen are values meaning the length of runes for each part
// here we manipulating byte length for better optimizations
// but these checks still works, cause minLen of 1-rune string is 1 byte.
if self.LengthRunes != -1 && self.LengthRunes > inputLen {
return 0, 0
}
if self.LeftLengthRunes >= 0 {
offset = self.LeftLengthRunes
}
if self.RightLengthRunes >= 0 {
limit = inputLen - self.RightLengthRunes
} else {
limit = inputLen
}
return offset, limit
}
func (self BTree) String() string {
const n string = "<nil>"
var l, r string
if self.Left == nil {
l = n
} else {
l = self.Left.String()
}
if self.Right == nil {
r = n
} else {
r = self.Right.String()
}
return fmt.Sprintf("<btree:[%s<-%s->%s]>", l, self.Value, r)
}

90
vendor/github.com/gobwas/glob/match/btree_test.go generated vendored Normal file
View file

@ -0,0 +1,90 @@
package match
import (
"testing"
)
func TestBTree(t *testing.T) {
for id, test := range []struct {
tree BTree
str string
exp bool
}{
{
NewBTree(NewText("abc"), NewSuper(), NewSuper()),
"abc",
true,
},
{
NewBTree(NewText("a"), NewSingle(nil), NewSingle(nil)),
"aaa",
true,
},
{
NewBTree(NewText("b"), NewSingle(nil), nil),
"bbb",
false,
},
{
NewBTree(
NewText("c"),
NewBTree(
NewSingle(nil),
NewSuper(),
nil,
),
nil,
),
"abc",
true,
},
} {
act := test.tree.Match(test.str)
if act != test.exp {
t.Errorf("#%d match %q error: act: %t; exp: %t", id, test.str, act, test.exp)
continue
}
}
}
type fakeMatcher struct {
len int
name string
}
func (f *fakeMatcher) Match(string) bool {
return true
}
var i = 3
func (f *fakeMatcher) Index(s string) (int, []int) {
seg := make([]int, 0, i)
for x := 0; x < i; x++ {
seg = append(seg, x)
}
return 0, seg
}
func (f *fakeMatcher) Len() int {
return f.len
}
func (f *fakeMatcher) String() string {
return f.name
}
func BenchmarkMatchBTree(b *testing.B) {
l := &fakeMatcher{4, "left_fake"}
r := &fakeMatcher{4, "right_fake"}
v := &fakeMatcher{2, "value_fake"}
// must be <= len(l + r + v)
fixture := "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij"
bt := NewBTree(v, l, r)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
bt.Match(fixture)
}
})
}

58
vendor/github.com/gobwas/glob/match/contains.go generated vendored Normal file
View file

@ -0,0 +1,58 @@
package match
import (
"fmt"
"strings"
)
type Contains struct {
Needle string
Not bool
}
func NewContains(needle string, not bool) Contains {
return Contains{needle, not}
}
func (self Contains) Match(s string) bool {
return strings.Contains(s, self.Needle) != self.Not
}
func (self Contains) Index(s string) (int, []int) {
var offset int
idx := strings.Index(s, self.Needle)
if !self.Not {
if idx == -1 {
return -1, nil
}
offset = idx + len(self.Needle)
if len(s) <= offset {
return 0, []int{offset}
}
s = s[offset:]
} else if idx != -1 {
s = s[:idx]
}
segments := acquireSegments(len(s) + 1)
for i := range s {
segments = append(segments, offset+i)
}
return 0, append(segments, offset+len(s))
}
func (self Contains) Len() int {
return lenNo
}
func (self Contains) String() string {
var not string
if self.Not {
not = "!"
}
return fmt.Sprintf("<contains:%s[%s]>", not, self.Needle)
}

74
vendor/github.com/gobwas/glob/match/contains_test.go generated vendored Normal file
View file

@ -0,0 +1,74 @@
package match
import (
"reflect"
"testing"
)
func TestContainsIndex(t *testing.T) {
for id, test := range []struct {
prefix string
not bool
fixture string
index int
segments []int
}{
{
"ab",
false,
"abc",
0,
[]int{2, 3},
},
{
"ab",
false,
"fffabfff",
0,
[]int{5, 6, 7, 8},
},
{
"ab",
true,
"abc",
0,
[]int{0},
},
{
"ab",
true,
"fffabfff",
0,
[]int{0, 1, 2, 3},
},
} {
p := NewContains(test.prefix, test.not)
index, segments := p.Index(test.fixture)
if index != test.index {
t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index)
}
if !reflect.DeepEqual(segments, test.segments) {
t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments)
}
}
}
func BenchmarkIndexContains(b *testing.B) {
m := NewContains(string(bench_separators), true)
for i := 0; i < b.N; i++ {
_, s := m.Index(bench_pattern)
releaseSegments(s)
}
}
func BenchmarkIndexContainsParallel(b *testing.B) {
m := NewContains(string(bench_separators), true)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_, s := m.Index(bench_pattern)
releaseSegments(s)
}
})
}

55
vendor/github.com/gobwas/glob/match/debug/debug.go generated vendored Normal file
View file

@ -0,0 +1,55 @@
package debug
import (
"bytes"
"fmt"
"github.com/gobwas/glob/match"
"math/rand"
)
func Graphviz(pattern string, m match.Matcher) string {
return fmt.Sprintf(`digraph G {graph[label="%s"];%s}`, pattern, graphviz_internal(m, fmt.Sprintf("%x", rand.Int63())))
}
func graphviz_internal(m match.Matcher, id string) string {
buf := &bytes.Buffer{}
switch matcher := m.(type) {
case match.BTree:
fmt.Fprintf(buf, `"%s"[label="%s"];`, id, matcher.Value.String())
for _, m := range []match.Matcher{matcher.Left, matcher.Right} {
switch n := m.(type) {
case nil:
rnd := rand.Int63()
fmt.Fprintf(buf, `"%x"[label="<nil>"];`, rnd)
fmt.Fprintf(buf, `"%s"->"%x";`, id, rnd)
default:
sub := fmt.Sprintf("%x", rand.Int63())
fmt.Fprintf(buf, `"%s"->"%s";`, id, sub)
fmt.Fprintf(buf, graphviz_internal(n, sub))
}
}
case match.AnyOf:
fmt.Fprintf(buf, `"%s"[label="AnyOf"];`, id)
for _, m := range matcher.Matchers {
rnd := rand.Int63()
fmt.Fprintf(buf, graphviz_internal(m, fmt.Sprintf("%x", rnd)))
fmt.Fprintf(buf, `"%s"->"%x";`, id, rnd)
}
case match.EveryOf:
fmt.Fprintf(buf, `"%s"[label="EveryOf"];`, id)
for _, m := range matcher.Matchers {
rnd := rand.Int63()
fmt.Fprintf(buf, graphviz_internal(m, fmt.Sprintf("%x", rnd)))
fmt.Fprintf(buf, `"%s"->"%x";`, id, rnd)
}
default:
fmt.Fprintf(buf, `"%s"[label="%s"];`, id, m.String())
}
return buf.String()
}

99
vendor/github.com/gobwas/glob/match/every_of.go generated vendored Normal file
View file

@ -0,0 +1,99 @@
package match
import (
"fmt"
)
type EveryOf struct {
Matchers Matchers
}
func NewEveryOf(m ...Matcher) EveryOf {
return EveryOf{Matchers(m)}
}
func (self *EveryOf) Add(m Matcher) error {
self.Matchers = append(self.Matchers, m)
return nil
}
func (self EveryOf) Len() (l int) {
for _, m := range self.Matchers {
if ml := m.Len(); l > 0 {
l += ml
} else {
return -1
}
}
return
}
func (self EveryOf) Index(s string) (int, []int) {
var index int
var offset int
// make `in` with cap as len(s),
// cause it is the maximum size of output segments values
next := acquireSegments(len(s))
current := acquireSegments(len(s))
sub := s
for i, m := range self.Matchers {
idx, seg := m.Index(sub)
if idx == -1 {
releaseSegments(next)
releaseSegments(current)
return -1, nil
}
if i == 0 {
// we use copy here instead of `current = seg`
// cause seg is a slice from reusable buffer `in`
// and it could be overwritten in next iteration
current = append(current, seg...)
} else {
// clear the next
next = next[:0]
delta := index - (idx + offset)
for _, ex := range current {
for _, n := range seg {
if ex+delta == n {
next = append(next, n)
}
}
}
if len(next) == 0 {
releaseSegments(next)
releaseSegments(current)
return -1, nil
}
current = append(current[:0], next...)
}
index = idx + offset
sub = s[index:]
offset += idx
}
releaseSegments(next)
return index, current
}
func (self EveryOf) Match(s string) bool {
for _, m := range self.Matchers {
if !m.Match(s) {
return false
}
}
return true
}
func (self EveryOf) String() string {
return fmt.Sprintf("<every_of:[%s]>", self.Matchers)
}

45
vendor/github.com/gobwas/glob/match/every_of_test.go generated vendored Normal file
View file

@ -0,0 +1,45 @@
package match
import (
"reflect"
"testing"
)
func TestEveryOfIndex(t *testing.T) {
for id, test := range []struct {
matchers Matchers
fixture string
index int
segments []int
}{
{
Matchers{
NewAny(nil),
NewText("b"),
NewText("c"),
},
"dbc",
-1,
nil,
},
{
Matchers{
NewAny(nil),
NewPrefix("b"),
NewSuffix("c"),
},
"abc",
1,
[]int{2},
},
} {
everyOf := NewEveryOf(test.matchers...)
index, segments := everyOf.Index(test.fixture)
if index != test.index {
t.Errorf("#%d unexpected index: exp: %d, act: %d", id, test.index, index)
}
if !reflect.DeepEqual(segments, test.segments) {
t.Errorf("#%d unexpected segments: exp: %v, act: %v", id, test.segments, segments)
}
}
}

49
vendor/github.com/gobwas/glob/match/list.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
package match
import (
"fmt"
"github.com/gobwas/glob/util/runes"
"unicode/utf8"
)
type List struct {
List []rune
Not bool
}
func NewList(list []rune, not bool) List {
return List{list, not}
}
func (self List) Match(s string) bool {
r, w := utf8.DecodeRuneInString(s)
if len(s) > w {
return false
}
inList := runes.IndexRune(self.List, r) != -1
return inList == !self.Not
}
func (self List) Len() int {
return lenOne
}
func (self List) Index(s string) (int, []int) {
for i, r := range s {
if self.Not == (runes.IndexRune(self.List, r) == -1) {
return i, segmentsByRuneLength[utf8.RuneLen(r)]
}
}
return -1, nil
}
func (self List) String() string {
var not string
if self.Not {
not = "!"
}
return fmt.Sprintf("<list:%s[%s]>", not, string(self.List))
}

Some files were not shown because too many files have changed in this diff Show more