diff --git a/Gopkg.lock b/Gopkg.lock
index 1e81eb5c..b8c0dd67 100644
--- a/Gopkg.lock
+++ b/Gopkg.lock
@@ -9,6 +9,22 @@
revision = "a32116e4989e2b0e17c057ee378b4d5246add74e"
version = "v1.1.0"
+[[projects]]
+ branch = "master"
+ digest = "1:1d692605b66a4fbe2a3de27131619e8e2c88ccf142ee43a7a8c902339942e0cc"
+ name = "github.com/antchfx/jsonquery"
+ packages = ["."]
+ pruneopts = "UT"
+ revision = "a2896be8c82bb2229d1cf26204863180e34b2b31"
+
+[[projects]]
+ branch = "master"
+ digest = "1:7e4a9eaa42cb3b4cd48bd0cd2072a029fe25a47af20cfac529ea8c365f8559ac"
+ name = "github.com/antchfx/xpath"
+ packages = ["."]
+ pruneopts = "UT"
+ revision = "c8489ed3251e7d55ec2b7f18a2bc3a9a7222f0af"
+
[[projects]]
branch = "master"
digest = "1:c309b41787813f80ec393023471014b0be16346bba6e16bf5fa01ce1d310a4ea"
@@ -292,6 +308,7 @@
analyzer-version = 1
input-imports = [
"github.com/adrianmo/go-nmea",
+ "github.com/antchfx/jsonquery",
"github.com/bettercap/gatt",
"github.com/bettercap/nrf24",
"github.com/bettercap/readline",
diff --git a/modules/events_stream/events_stream.go b/modules/events_stream/events_stream.go
index 1acbcc52..6dcebccb 100644
--- a/modules/events_stream/events_stream.go
+++ b/modules/events_stream/events_stream.go
@@ -29,6 +29,7 @@ type EventsStream struct {
output *os.File
rotation rotation
ignoreList *IgnoreList
+ triggerList *TriggerList
waitFor string
waitChan chan *session.Event
eventListener <-chan session.Event
@@ -45,6 +46,7 @@ func NewEventsStream(s *session.Session) *EventsStream {
waitChan: make(chan *session.Event),
waitFor: "",
ignoreList: NewIgnoreList(),
+ triggerList: NewTriggerList(),
}
mod.AddHandler(session.NewModuleHandler("events.stream on", "",
@@ -70,6 +72,38 @@ func NewEventsStream(s *session.Session) *EventsStream {
return mod.Show(limit)
}))
+ on := session.NewModuleHandler("events.on TAG COMMANDS", `events\.on ([^\s]+) (.+)`,
+ "Run COMMANDS when an event with the specified TAG is triggered.",
+ func(args []string) error {
+ return mod.addTrigger(args[0], args[1])
+ })
+
+ on.Complete("events.on", s.EventsCompleter)
+
+ mod.AddHandler(on)
+
+ mod.AddHandler(session.NewModuleHandler("events.triggers", "",
+ "Show the list of event triggers created by the events.on command.",
+ func(args []string) error {
+ return mod.showTriggers()
+ }))
+
+ onClear := session.NewModuleHandler("events.trigger.delete TRIGGER_ID", `events\.trigger\.delete ([^\s]+)`,
+ "Remove an event trigger given its TRIGGER_ID (use events.triggers to see the list of triggers).",
+ func(args []string) error {
+ return mod.clearTrigger(args[0])
+ })
+
+ onClear.Complete("events.trigger.delete", mod.triggerList.Completer)
+
+ mod.AddHandler(onClear)
+
+ mod.AddHandler(session.NewModuleHandler("events.triggers.clear", "",
+ "Remove all event triggers (use events.triggers to see the list of triggers).",
+ func(args []string) error {
+ return mod.clearTrigger("")
+ }))
+
mod.AddHandler(session.NewModuleHandler("events.waitfor TAG TIMEOUT?", `events.waitfor ([^\s]+)([\s\d]*)`,
"Wait for an event with the given tag either forever or for a timeout in seconds.",
func(args []string) error {
@@ -242,6 +276,10 @@ func (mod *EventsStream) Start() error {
mod.View(e, true)
}
+ // this could generate sys.log events and lock the whole
+ // events.stream, make it async
+ go mod.dispatchTriggers(e)
+
case <-mod.quit:
return
}
diff --git a/modules/events_stream/events_triggers.go b/modules/events_stream/events_triggers.go
new file mode 100644
index 00000000..0c50c9cc
--- /dev/null
+++ b/modules/events_stream/events_triggers.go
@@ -0,0 +1,62 @@
+package events_stream
+
+import (
+ "os"
+
+ "github.com/bettercap/bettercap/session"
+
+ "github.com/evilsocket/islazy/tui"
+)
+
+func (mod *EventsStream) addTrigger(tag string, command string) error {
+ if err, id := mod.triggerList.Add(tag, command); err != nil {
+ return err
+ } else {
+ mod.Info("trigger for event %s added with identifier '%s'", tui.Green(tag), tui.Bold(id))
+ }
+ return nil
+}
+
+func (mod *EventsStream) clearTrigger(id string) error {
+ if err := mod.triggerList.Del(id); err != nil {
+ return err
+ }
+ return nil
+}
+
+func (mod *EventsStream) showTriggers() error {
+ colNames := []string{
+ "ID",
+ "Event",
+ "Action",
+ }
+ rows := [][]string{}
+
+ mod.triggerList.Each(func(id string, t Trigger) {
+ rows = append(rows, []string{
+ tui.Bold(id),
+ tui.Green(t.For),
+ t.Action,
+ })
+ })
+
+ if len(rows) > 0 {
+ tui.Table(os.Stdout, colNames, rows)
+ mod.Session.Refresh()
+ }
+
+ return nil
+}
+
+func (mod *EventsStream) dispatchTriggers(e session.Event) {
+ if id, cmds, err, found := mod.triggerList.Dispatch(e); err != nil {
+ mod.Error("error while dispatching event %s: %v", e.Tag, err)
+ } else if found {
+ mod.Debug("running trigger %s (cmds:'%s') for event %v", id, cmds, e)
+ for _, cmd := range session.ParseCommands(cmds) {
+ if err := mod.Session.Run(cmd); err != nil {
+ mod.Error("%s", err.Error())
+ }
+ }
+ }
+}
diff --git a/modules/events_stream/events_ignore_list.go b/modules/events_stream/ignore_list.go
similarity index 100%
rename from modules/events_stream/events_ignore_list.go
rename to modules/events_stream/ignore_list.go
diff --git a/modules/events_stream/trigger_list.go b/modules/events_stream/trigger_list.go
new file mode 100644
index 00000000..08bb7ff7
--- /dev/null
+++ b/modules/events_stream/trigger_list.go
@@ -0,0 +1,136 @@
+package events_stream
+
+import (
+ "encoding/json"
+ "fmt"
+ "regexp"
+ "strings"
+ "sync"
+
+ "github.com/bettercap/bettercap/session"
+
+ "github.com/antchfx/jsonquery"
+ "github.com/evilsocket/islazy/str"
+ "github.com/evilsocket/islazy/tui"
+)
+
+var reQueryCapture = regexp.MustCompile(`{{([^}]+)}}`)
+
+type Trigger struct {
+ For string
+ Action string
+}
+
+type TriggerList struct {
+ sync.Mutex
+ triggers map[string]Trigger
+}
+
+func NewTriggerList() *TriggerList {
+ return &TriggerList{
+ triggers: make(map[string]Trigger),
+ }
+}
+
+func (l *TriggerList) Add(tag string, command string) (error, string) {
+ l.Lock()
+ defer l.Unlock()
+
+ idNum := 0
+ command = str.Trim(command)
+
+ for id, t := range l.triggers {
+ if t.For == tag {
+ if t.Action == command {
+ return fmt.Errorf("duplicate: trigger '%s' found for action '%s'", tui.Bold(id), command), ""
+ }
+ idNum++
+ }
+ }
+
+ id := fmt.Sprintf("%s-%d", tag, idNum)
+ l.triggers[id] = Trigger{
+ For: tag,
+ Action: command,
+ }
+
+ return nil, id
+}
+
+func (l *TriggerList) Del(id string) (err error) {
+ l.Lock()
+ defer l.Unlock()
+ if _, found := l.triggers[id]; found {
+ delete(l.triggers, id)
+ } else {
+ err = fmt.Errorf("trigger '%s' not found", tui.Bold(id))
+ }
+ return err
+}
+
+func (l *TriggerList) Each(cb func(id string, t Trigger)) {
+ l.Lock()
+ defer l.Unlock()
+ for id, t := range l.triggers {
+ cb(id, t)
+ }
+}
+
+func (l *TriggerList) Completer(prefix string) []string {
+ ids := []string{}
+ l.Each(func(id string, t Trigger) {
+ if prefix == "" || strings.HasPrefix(id, prefix) {
+ ids = append(ids, id)
+ }
+ })
+ return ids
+}
+
+func (l *TriggerList) Dispatch(e session.Event) (ident string, cmd string, err error, found bool) {
+ l.Lock()
+ defer l.Unlock()
+
+ for id, t := range l.triggers {
+ if e.Tag == t.For {
+ found = true
+ ident = id
+ // this is ugly but it's also the only way to allow
+ // the user to do this easily - since each event Data
+ // field is an interface and type casting is not possible
+ // via golang default text/template system, we transform
+ // the field to JSON, parse it again and then allow the
+ // user to access it in the command via JSON-Query, example:
+ //
+ // events.on wifi.client.new "wifi.deauth {{Client\mac}}"
+ buf := ([]byte)(nil)
+ doc := (*jsonquery.Node)(nil)
+ cmd = t.Action
+ // parse each {EXPR}
+ for _, m := range reQueryCapture.FindAllString(t.Action, -1) {
+ // parse the event Data field as a JSON objects once
+ if doc == nil {
+ if buf, err = json.Marshal(e.Data); err != nil {
+ err = fmt.Errorf("error while encoding event for trigger %s: %v", tui.Bold(id), err)
+ return
+ } else if doc, err = jsonquery.Parse(strings.NewReader(string(buf))); err != nil {
+ err = fmt.Errorf("error while parsing event for trigger %s: %v", tui.Bold(id), err)
+ return
+ }
+ }
+ // {EXPR} -> EXPR
+ expr := strings.Trim(m, "{}")
+ // use EXPR as a JSON query
+ if node := jsonquery.FindOne(doc, expr); node != nil {
+ cmd = strings.Replace(cmd, m, node.InnerText(), -1)
+ } else {
+ err = fmt.Errorf("error while parsing expressionfor trigger %s: '%s' doesn't resolve any object", tui.Bold(id), expr, expr, err)
+ return
+ }
+ }
+
+ return
+ }
+ }
+
+ return
+}
diff --git a/vendor/github.com/antchfx/jsonquery/.gitignore b/vendor/github.com/antchfx/jsonquery/.gitignore
new file mode 100644
index 00000000..4d5d27b1
--- /dev/null
+++ b/vendor/github.com/antchfx/jsonquery/.gitignore
@@ -0,0 +1,32 @@
+# vscode
+.vscode
+debug
+*.test
+
+./build
+
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/jsonquery/.travis.yml b/vendor/github.com/antchfx/jsonquery/.travis.yml
new file mode 100644
index 00000000..91a354fe
--- /dev/null
+++ b/vendor/github.com/antchfx/jsonquery/.travis.yml
@@ -0,0 +1,14 @@
+language: go
+
+go:
+ - 1.6
+ - 1.7
+ - 1.8
+ - 1.9
+
+install:
+ - go get github.com/antchfx/xpath
+ - go get github.com/mattn/goveralls
+
+script:
+ - $HOME/gopath/bin/goveralls -service=travis-ci
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/jsonquery/LICENSE b/vendor/github.com/antchfx/jsonquery/LICENSE
new file mode 100644
index 00000000..e14c3714
--- /dev/null
+++ b/vendor/github.com/antchfx/jsonquery/LICENSE
@@ -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.
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/jsonquery/README.md b/vendor/github.com/antchfx/jsonquery/README.md
new file mode 100644
index 00000000..72d8408b
--- /dev/null
+++ b/vendor/github.com/antchfx/jsonquery/README.md
@@ -0,0 +1,169 @@
+jsonquery
+====
+[](https://travis-ci.org/antchfx/jsonquery)
+[](https://coveralls.io/github/antchfx/jsonquery?branch=master)
+[](https://godoc.org/github.com/antchfx/jsonquery)
+[](https://goreportcard.com/report/github.com/antchfx/jsonquery)
+
+Overview
+===
+
+jsonquery is an XPath query package for JSON document, lets you extract data from JSON documents through an XPath expression.
+
+Getting Started
+===
+
+### Install Package
+
+> $ go get github.com/antchfx/jsonquery
+
+#### Load JSON document from URL.
+
+```go
+doc, err := jsonquery.LoadURL("http://www.example.com/feed?json")
+```
+
+#### Load JSON document from string.
+
+```go
+s :=`{
+ "name":"John",
+ "age":31,
+ "city":"New York"
+ }`
+doc, err := jsonquery.Parse(strings.NewReader(s))
+```
+
+#### Load JSON document from io.Reader.
+
+```go
+f, err := os.Open("./books.json")
+doc, err := jsonquery.Parse(f)
+```
+
+#### Find authors of all books in the store.
+```go
+list := jsonquery.Find(doc, "store/book/*/author")
+// or equal to
+list := jsonquery.Find(doc, "//author")
+```
+
+#### Find the third book.
+
+```go
+book := jsonquery.Find(doc, "//book/*[3]")
+```
+
+#### Find the last book.
+
+```go
+book := jsonquery.Find(doc, "//book/*[last()]")
+```
+
+#### Find all books with isbn number.
+
+```go
+list := jsonquery.Find(doc, "//book/*[isbn]")
+```
+
+#### Find all books cheapier than 10.
+
+```go
+list := jsonquery.Find(doc, "//book/*[price<10]")
+```
+
+Quick Tutorial
+===
+
+```go
+func main() {
+ s := `{
+ "name": "John",
+ "age" : 26,
+ "address" : {
+ "streetAddress": "naist street",
+ "city" : "Nara",
+ "postalCode" : "630-0192"
+ },
+ "phoneNumbers": [
+ {
+ "type" : "iPhone",
+ "number": "0123-4567-8888"
+ },
+ {
+ "type" : "home",
+ "number": "0123-4567-8910"
+ }
+ ]
+ }`
+ doc, _ := jsonquery.Parse(strings.NewReader(s))
+ name := jsonquery.FindOne(doc, "name")
+ fmt.Printf("name: %s\n", name.InnerText())
+ var a []string
+ for _, n := range jsonquery.Find(doc, "phoneNumbers/*/number") {
+ a = append(a, n.InnerText())
+ }
+ fmt.Printf("phone number: %s\n", strings.Join(a, ","))
+ if n := jsonquery.FindOne(doc, "address/streetAddress"); n != nil {
+ fmt.Printf("address: %s\n", n.InnerText())
+ }
+}
+```
+
+Implement Principle
+===
+If you are familiar with XPath and XML, you can quick start to known how
+write your XPath expression.
+
+```json
+{
+"name":"John",
+"age":30,
+"cars": [
+ { "name":"Ford", "models":[ "Fiesta", "Focus", "Mustang" ] },
+ { "name":"BMW", "models":[ "320", "X3", "X5" ] },
+ { "name":"Fiat", "models":[ "500", "Panda" ] }
+]
+}
+```
+The above JSON document will be convert to similar to XML document by the *JSONQuery*, like below:
+
+```XML
+John
+30
+
+
+ Ford
+
+ Fiesta
+ Focus
+ Mustang
+
+
+
+ BMW
+
+ 320
+ X3
+ X5
+
+
+
+ Fiat
+
+ 500
+ Panda
+
+
+
+```
+
+Notes: `element` is empty element that have no any name.
+
+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|
diff --git a/vendor/github.com/antchfx/jsonquery/books.json b/vendor/github.com/antchfx/jsonquery/books.json
new file mode 100644
index 00000000..e4bd9462
--- /dev/null
+++ b/vendor/github.com/antchfx/jsonquery/books.json
@@ -0,0 +1,36 @@
+{
+ "store": {
+ "book": [
+ {
+ "category": "reference",
+ "author": "Nigel Rees",
+ "title": "Sayings of the Century",
+ "price": 8.95
+ },
+ {
+ "category": "fiction",
+ "author": "Evelyn Waugh",
+ "title": "Sword of Honour",
+ "price": 12.99
+ },
+ {
+ "category": "fiction",
+ "author": "Herman Melville",
+ "title": "Moby Dick",
+ "isbn": "0-553-21311-3",
+ "price": 8.99
+ },
+ {
+ "category": "fiction",
+ "author": "J. R. R. Tolkien",
+ "title": "The Lord of the Rings",
+ "isbn": "0-395-19395-8",
+ "price": 22.99
+ }
+ ],
+ "bicycle": {
+ "color": "red",
+ "price": 19.95
+ }
+ }
+}
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/jsonquery/node.go b/vendor/github.com/antchfx/jsonquery/node.go
new file mode 100644
index 00000000..76032bb2
--- /dev/null
+++ b/vendor/github.com/antchfx/jsonquery/node.go
@@ -0,0 +1,157 @@
+package jsonquery
+
+import (
+ "bytes"
+ "encoding/json"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "sort"
+ "strconv"
+)
+
+// 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
+ // ElementNode is an element.
+ ElementNode
+ // TextNode is the text content of a node.
+ TextNode
+)
+
+// 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, PrevSibling, NextSibling, FirstChild, LastChild *Node
+
+ Type NodeType
+ Data string
+
+ level int
+}
+
+// ChildNodes gets all child nodes of the node.
+func (n *Node) ChildNodes() []*Node {
+ var a []*Node
+ for nn := n.FirstChild; nn != nil; nn = nn.NextSibling {
+ a = append(a, nn)
+ }
+ return a
+}
+
+// InnerText gets the value of the node and all its child nodes.
+func (n *Node) InnerText() string {
+ var output func(*bytes.Buffer, *Node)
+ output = func(buf *bytes.Buffer, n *Node) {
+ if n.Type == TextNode {
+ buf.WriteString(n.Data)
+ return
+ }
+ for child := n.FirstChild; child != nil; child = child.NextSibling {
+ output(buf, child)
+ }
+ }
+ var buf bytes.Buffer
+ output(&buf, n)
+ return buf.String()
+}
+
+// SelectElement finds the first of child elements with the
+// specified name.
+func (n *Node) SelectElement(name string) *Node {
+ for nn := n.FirstChild; nn != nil; nn = nn.NextSibling {
+ if nn.Data == name {
+ return nn
+ }
+ }
+ return nil
+}
+
+// LoadURL loads the JSON 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 parseValue(x interface{}, top *Node, level int) {
+ addNode := func(n *Node) {
+ if n.level == top.level {
+ top.NextSibling = n
+ n.PrevSibling = top
+ n.Parent = top.Parent
+ if top.Parent != nil {
+ top.Parent.LastChild = n
+ }
+ } else if n.level > top.level {
+ n.Parent = top
+ if top.FirstChild == nil {
+ top.FirstChild = n
+ top.LastChild = n
+ } else {
+ t := top.LastChild
+ t.NextSibling = n
+ n.PrevSibling = t
+ top.LastChild = n
+ }
+ }
+ }
+ switch v := x.(type) {
+ case []interface{}:
+ for _, vv := range v {
+ n := &Node{Type: ElementNode, level: level}
+ addNode(n)
+ parseValue(vv, n, level+1)
+ }
+ case map[string]interface{}:
+ // The Go’s map iteration order is random.
+ // (https://blog.golang.org/go-maps-in-action#Iteration-order)
+ var keys []string
+ for key := range v {
+ keys = append(keys, key)
+ }
+ sort.Strings(keys)
+ for _, key := range keys {
+ n := &Node{Data: key, Type: ElementNode, level: level}
+ addNode(n)
+ parseValue(v[key], n, level+1)
+ }
+ case string:
+ n := &Node{Data: v, Type: TextNode, level: level}
+ addNode(n)
+ case float64:
+ s := strconv.FormatFloat(v, 'f', -1, 64)
+ n := &Node{Data: s, Type: TextNode, level: level}
+ addNode(n)
+ case bool:
+ s := strconv.FormatBool(v)
+ n := &Node{Data: s, Type: TextNode, level: level}
+ addNode(n)
+ }
+}
+
+func parse(b []byte) (*Node, error) {
+ var v interface{}
+ if err := json.Unmarshal(b, &v); err != nil {
+ return nil, err
+ }
+ doc := &Node{Type: DocumentNode}
+ parseValue(v, doc, 1)
+ return doc, nil
+}
+
+// Parse JSON document.
+func Parse(r io.Reader) (*Node, error) {
+ b, err := ioutil.ReadAll(r)
+ if err != nil {
+ return nil, err
+ }
+ return parse(b)
+}
diff --git a/vendor/github.com/antchfx/jsonquery/query.go b/vendor/github.com/antchfx/jsonquery/query.go
new file mode 100644
index 00000000..04f96d98
--- /dev/null
+++ b/vendor/github.com/antchfx/jsonquery/query.go
@@ -0,0 +1,149 @@
+package jsonquery
+
+import (
+ "fmt"
+
+ "github.com/antchfx/xpath"
+)
+
+var _ xpath.NodeNavigator = &NodeNavigator{}
+
+// CreateXPathNavigator creates a new xpath.NodeNavigator for the specified html.Node.
+func CreateXPathNavigator(top *Node) *NodeNavigator {
+ return &NodeNavigator{cur: top, root: top}
+}
+
+// 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, (t.Current().(*NodeNavigator)).cur)
+ }
+ 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 = (t.Current().(*NodeNavigator)).cur
+ }
+ return elem
+}
+
+// NodeNavigator is for navigating JSON document.
+type NodeNavigator struct {
+ root, cur *Node
+}
+
+func (a *NodeNavigator) Current() *Node {
+ return a.cur
+}
+
+func (a *NodeNavigator) NodeType() xpath.NodeType {
+ switch a.cur.Type {
+ case TextNode:
+ return xpath.TextNode
+ case DocumentNode:
+ return xpath.RootNode
+ case ElementNode:
+ return xpath.ElementNode
+ default:
+ panic(fmt.Sprintf("unknown node type %v", a.cur.Type))
+ }
+}
+
+func (a *NodeNavigator) LocalName() string {
+ return a.cur.Data
+
+}
+
+func (a *NodeNavigator) Prefix() string {
+ return ""
+}
+
+func (a *NodeNavigator) Value() string {
+ switch a.cur.Type {
+ case ElementNode:
+ return a.cur.InnerText()
+ case TextNode:
+ return a.cur.Data
+ }
+ return ""
+}
+
+func (a *NodeNavigator) Copy() xpath.NodeNavigator {
+ n := *a
+ return &n
+}
+
+func (a *NodeNavigator) MoveToRoot() {
+ a.cur = a.root
+}
+
+func (a *NodeNavigator) MoveToParent() bool {
+ if n := a.cur.Parent; n != nil {
+ a.cur = n
+ return true
+ }
+ return false
+}
+
+func (x *NodeNavigator) MoveToNextAttribute() bool {
+ return false
+}
+
+func (a *NodeNavigator) MoveToChild() bool {
+ if n := a.cur.FirstChild; n != nil {
+ a.cur = n
+ return true
+ }
+ return false
+}
+
+func (a *NodeNavigator) MoveToFirst() bool {
+ for n := a.cur.PrevSibling; n != nil; n = n.PrevSibling {
+ a.cur = n
+ }
+ return true
+}
+
+func (a *NodeNavigator) String() string {
+ return a.Value()
+}
+
+func (a *NodeNavigator) MoveToNext() bool {
+ if n := a.cur.NextSibling; n != nil {
+ a.cur = n
+ return true
+ }
+ return false
+}
+
+func (a *NodeNavigator) MoveToPrevious() bool {
+ if n := a.cur.PrevSibling; n != nil {
+ a.cur = n
+ return true
+ }
+ return false
+}
+
+func (a *NodeNavigator) MoveTo(other xpath.NodeNavigator) bool {
+ node, ok := other.(*NodeNavigator)
+ if !ok || node.root != a.root {
+ return false
+ }
+ a.cur = node.cur
+ return true
+}
diff --git a/vendor/github.com/antchfx/xpath/.gitignore b/vendor/github.com/antchfx/xpath/.gitignore
new file mode 100644
index 00000000..4d5d27b1
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/.gitignore
@@ -0,0 +1,32 @@
+# vscode
+.vscode
+debug
+*.test
+
+./build
+
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/xpath/.travis.yml b/vendor/github.com/antchfx/xpath/.travis.yml
new file mode 100644
index 00000000..6b63957a
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/.travis.yml
@@ -0,0 +1,12 @@
+language: go
+
+go:
+ - 1.6
+ - 1.9
+ - '1.10'
+
+install:
+ - go get github.com/mattn/goveralls
+
+script:
+ - $HOME/gopath/bin/goveralls -service=travis-ci
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/xpath/LICENSE b/vendor/github.com/antchfx/xpath/LICENSE
new file mode 100644
index 00000000..e14c3714
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/LICENSE
@@ -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.
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/xpath/README.md b/vendor/github.com/antchfx/xpath/README.md
new file mode 100644
index 00000000..414114dc
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/README.md
@@ -0,0 +1,167 @@
+XPath
+====
+[](https://godoc.org/github.com/antchfx/xpath)
+[](https://coveralls.io/github/antchfx/xpath?branch=master)
+[](https://travis-ci.org/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).
+
+- `(a, b, c)` : Evaluates each of its operands and concatenates the resulting sequences, in order, into a single result sequence
+
+
+#### 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()` | ✗ |
+
+Changelogs
+===
+
+2019-01-29
+- improvement `normalize-space` function. [#32](https://github.com/antchfx/xpath/issues/32)
+
+2018-12-07
+- supports XPath 2.0 Sequence expressions. [#30](https://github.com/antchfx/xpath/pull/30) by [@minherz](https://github.com/minherz).
\ No newline at end of file
diff --git a/vendor/github.com/antchfx/xpath/build.go b/vendor/github.com/antchfx/xpath/build.go
new file mode 100644
index 00000000..74f266b0
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/build.go
@@ -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)
+}
diff --git a/vendor/github.com/antchfx/xpath/func.go b/vendor/github.com/antchfx/xpath/func.go
new file mode 100644
index 00000000..3c0fde9f
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/func.go
@@ -0,0 +1,484 @@
+package xpath
+
+import (
+ "errors"
+ "fmt"
+ "math"
+ "regexp"
+ "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)
+ }
+}
+
+var (
+ regnewline = regexp.MustCompile(`[\r\n\t]`)
+ regseqspace = regexp.MustCompile(`\s{2,}`)
+)
+
+// 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 ""
+ }
+ m = node.Value()
+ }
+ m = strings.TrimSpace(m)
+ m = regnewline.ReplaceAllString(m, " ")
+ m = regseqspace.ReplaceAllString(m, " ")
+ return 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, "")
+ }
+}
diff --git a/vendor/github.com/antchfx/xpath/func_go110.go b/vendor/github.com/antchfx/xpath/func_go110.go
new file mode 100644
index 00000000..500880fa
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/func_go110.go
@@ -0,0 +1,9 @@
+// +build go1.10
+
+package xpath
+
+import "math"
+
+func round(f float64) int {
+ return int(math.Round(f))
+}
diff --git a/vendor/github.com/antchfx/xpath/func_pre_go110.go b/vendor/github.com/antchfx/xpath/func_pre_go110.go
new file mode 100644
index 00000000..043616b3
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/func_pre_go110.go
@@ -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))
+}
diff --git a/vendor/github.com/antchfx/xpath/operator.go b/vendor/github.com/antchfx/xpath/operator.go
new file mode 100644
index 00000000..308d3cbc
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/operator.go
@@ -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))
+ })
+}
diff --git a/vendor/github.com/antchfx/xpath/parse.go b/vendor/github.com/antchfx/xpath/parse.go
new file mode 100644
index 00000000..fb9abe31
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/parse.go
@@ -0,0 +1,1186 @@
+package xpath
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "strconv"
+ "unicode"
+)
+
+// A XPath expression token type.
+type itemType int
+
+const (
+ itemComma itemType = iota // ','
+ itemSlash // '/'
+ itemAt // '@'
+ itemDot // '.'
+ itemLParens // '('
+ itemRParens // ')'
+ itemLBracket // '['
+ itemRBracket // ']'
+ itemStar // '*'
+ itemPlus // '+'
+ itemMinus // '-'
+ itemEq // '='
+ itemLt // '<'
+ itemGt // '>'
+ itemBang // '!'
+ itemDollar // '$'
+ itemApos // '\''
+ itemQuote // '"'
+ itemUnion // '|'
+ itemNe // '!='
+ itemLe // '<='
+ itemGe // '>='
+ itemAnd // '&&'
+ itemOr // '||'
+ itemDotDot // '..'
+ itemSlashSlash // '//'
+ itemName // XML Name
+ itemString // Quoted string constant
+ itemNumber // Number constant
+ itemAxe // Axe (like child::)
+ itemEOF // END
+)
+
+// A node is an XPath node in the parse tree.
+type node interface {
+ Type() nodeType
+}
+
+// nodeType identifies the type of a parse tree node.
+type nodeType int
+
+func (t nodeType) Type() nodeType {
+ return t
+}
+
+const (
+ nodeRoot nodeType = iota
+ nodeAxis
+ nodeFilter
+ nodeFunction
+ nodeOperator
+ nodeVariable
+ nodeConstantOperand
+)
+
+type parser struct {
+ r *scanner
+ d int
+}
+
+// newOperatorNode returns new operator node OperatorNode.
+func newOperatorNode(op string, left, right node) node {
+ return &operatorNode{nodeType: nodeOperator, Op: op, Left: left, Right: right}
+}
+
+// newOperand returns new constant operand node OperandNode.
+func newOperandNode(v interface{}) node {
+ return &operandNode{nodeType: nodeConstantOperand, Val: v}
+}
+
+// newAxisNode returns new axis node AxisNode.
+func newAxisNode(axeTyp, localName, prefix, prop string, n node) node {
+ return &axisNode{
+ nodeType: nodeAxis,
+ LocalName: localName,
+ Prefix: prefix,
+ AxeType: axeTyp,
+ Prop: prop,
+ Input: n,
+ }
+}
+
+// newVariableNode returns new variable node VariableNode.
+func newVariableNode(prefix, name string) node {
+ return &variableNode{nodeType: nodeVariable, Name: name, Prefix: prefix}
+}
+
+// newFilterNode returns a new filter node FilterNode.
+func newFilterNode(n, m node) node {
+ return &filterNode{nodeType: nodeFilter, Input: n, Condition: m}
+}
+
+// newRootNode returns a root node.
+func newRootNode(s string) node {
+ return &rootNode{nodeType: nodeRoot, slash: s}
+}
+
+// newFunctionNode returns function call node.
+func newFunctionNode(name, prefix string, args []node) node {
+ return &functionNode{nodeType: nodeFunction, Prefix: prefix, FuncName: name, Args: args}
+}
+
+// testOp reports whether current item name is an operand op.
+func testOp(r *scanner, op string) bool {
+ return r.typ == itemName && r.prefix == "" && r.name == op
+}
+
+func isPrimaryExpr(r *scanner) bool {
+ switch r.typ {
+ case itemString, itemNumber, itemDollar, itemLParens:
+ return true
+ case itemName:
+ return r.canBeFunc && !isNodeType(r)
+ }
+ return false
+}
+
+func isNodeType(r *scanner) bool {
+ switch r.name {
+ case "node", "text", "processing-instruction", "comment":
+ return r.prefix == ""
+ }
+ return false
+}
+
+func isStep(item itemType) bool {
+ switch item {
+ case itemDot, itemDotDot, itemAt, itemAxe, itemStar, itemName:
+ return true
+ }
+ return false
+}
+
+func checkItem(r *scanner, typ itemType) {
+ if r.typ != typ {
+ panic(fmt.Sprintf("%s has an invalid token", r.text))
+ }
+}
+
+// parseExpression parsing the expression with input node n.
+func (p *parser) parseExpression(n node) node {
+ if p.d = p.d + 1; p.d > 200 {
+ panic("the xpath query is too complex(depth > 200)")
+ }
+ n = p.parseOrExpr(n)
+ p.d--
+ return n
+}
+
+// next scanning next item on forward.
+func (p *parser) next() bool {
+ return p.r.nextItem()
+}
+
+func (p *parser) skipItem(typ itemType) {
+ checkItem(p.r, typ)
+ p.next()
+}
+
+// OrExpr ::= AndExpr | OrExpr 'or' AndExpr
+func (p *parser) parseOrExpr(n node) node {
+ opnd := p.parseAndExpr(n)
+ for {
+ if !testOp(p.r, "or") {
+ break
+ }
+ p.next()
+ opnd = newOperatorNode("or", opnd, p.parseAndExpr(n))
+ }
+ return opnd
+}
+
+// AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr
+func (p *parser) parseAndExpr(n node) node {
+ opnd := p.parseEqualityExpr(n)
+ for {
+ if !testOp(p.r, "and") {
+ break
+ }
+ p.next()
+ opnd = newOperatorNode("and", opnd, p.parseEqualityExpr(n))
+ }
+ return opnd
+}
+
+// EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr | EqualityExpr '!=' RelationalExpr
+func (p *parser) parseEqualityExpr(n node) node {
+ opnd := p.parseRelationalExpr(n)
+Loop:
+ for {
+ var op string
+ switch p.r.typ {
+ case itemEq:
+ op = "="
+ case itemNe:
+ op = "!="
+ default:
+ break Loop
+ }
+ p.next()
+ opnd = newOperatorNode(op, opnd, p.parseRelationalExpr(n))
+ }
+ return opnd
+}
+
+// RelationalExpr ::= AdditiveExpr | RelationalExpr '<' AdditiveExpr | RelationalExpr '>' AdditiveExpr
+// | RelationalExpr '<=' AdditiveExpr
+// | RelationalExpr '>=' AdditiveExpr
+func (p *parser) parseRelationalExpr(n node) node {
+ opnd := p.parseAdditiveExpr(n)
+Loop:
+ for {
+ var op string
+ switch p.r.typ {
+ case itemLt:
+ op = "<"
+ case itemGt:
+ op = ">"
+ case itemLe:
+ op = "<="
+ case itemGe:
+ op = ">="
+ default:
+ break Loop
+ }
+ p.next()
+ opnd = newOperatorNode(op, opnd, p.parseAdditiveExpr(n))
+ }
+ return opnd
+}
+
+// AdditiveExpr ::= MultiplicativeExpr | AdditiveExpr '+' MultiplicativeExpr | AdditiveExpr '-' MultiplicativeExpr
+func (p *parser) parseAdditiveExpr(n node) node {
+ opnd := p.parseMultiplicativeExpr(n)
+Loop:
+ for {
+ var op string
+ switch p.r.typ {
+ case itemPlus:
+ op = "+"
+ case itemMinus:
+ op = "-"
+ default:
+ break Loop
+ }
+ p.next()
+ opnd = newOperatorNode(op, opnd, p.parseMultiplicativeExpr(n))
+ }
+ return opnd
+}
+
+// MultiplicativeExpr ::= UnaryExpr | MultiplicativeExpr MultiplyOperator(*) UnaryExpr
+// | MultiplicativeExpr 'div' UnaryExpr | MultiplicativeExpr 'mod' UnaryExpr
+func (p *parser) parseMultiplicativeExpr(n node) node {
+ opnd := p.parseUnaryExpr(n)
+Loop:
+ for {
+ var op string
+ if p.r.typ == itemStar {
+ op = "*"
+ } else if testOp(p.r, "div") || testOp(p.r, "mod") {
+ op = p.r.name
+ } else {
+ break Loop
+ }
+ p.next()
+ opnd = newOperatorNode(op, opnd, p.parseUnaryExpr(n))
+ }
+ return opnd
+}
+
+// UnaryExpr ::= UnionExpr | '-' UnaryExpr
+func (p *parser) parseUnaryExpr(n node) node {
+ minus := false
+ // ignore '-' sequence
+ for p.r.typ == itemMinus {
+ p.next()
+ minus = !minus
+ }
+ opnd := p.parseUnionExpr(n)
+ if minus {
+ opnd = newOperatorNode("*", opnd, newOperandNode(float64(-1)))
+ }
+ return opnd
+}
+
+// UnionExpr ::= PathExpr | UnionExpr '|' PathExpr
+func (p *parser) parseUnionExpr(n node) node {
+ opnd := p.parsePathExpr(n)
+Loop:
+ for {
+ if p.r.typ != itemUnion {
+ break Loop
+ }
+ p.next()
+ opnd2 := p.parsePathExpr(n)
+ // Checking the node type that must be is node set type?
+ opnd = newOperatorNode("|", opnd, opnd2)
+ }
+ return opnd
+}
+
+// PathExpr ::= LocationPath | FilterExpr | FilterExpr '/' RelativeLocationPath | FilterExpr '//' RelativeLocationPath
+func (p *parser) parsePathExpr(n node) node {
+ var opnd node
+ if isPrimaryExpr(p.r) {
+ opnd = p.parseFilterExpr(n)
+ switch p.r.typ {
+ case itemSlash:
+ p.next()
+ opnd = p.parseRelativeLocationPath(opnd)
+ case itemSlashSlash:
+ p.next()
+ opnd = p.parseRelativeLocationPath(newAxisNode("descendant-or-self", "", "", "", opnd))
+ }
+ } else {
+ opnd = p.parseLocationPath(nil)
+ }
+ return opnd
+}
+
+// FilterExpr ::= PrimaryExpr | FilterExpr Predicate
+func (p *parser) parseFilterExpr(n node) node {
+ opnd := p.parsePrimaryExpr(n)
+ if p.r.typ == itemLBracket {
+ opnd = newFilterNode(opnd, p.parsePredicate(opnd))
+ }
+ return opnd
+}
+
+// Predicate ::= '[' PredicateExpr ']'
+func (p *parser) parsePredicate(n node) node {
+ p.skipItem(itemLBracket)
+ opnd := p.parseExpression(n)
+ p.skipItem(itemRBracket)
+ return opnd
+}
+
+// LocationPath ::= RelativeLocationPath | AbsoluteLocationPath
+func (p *parser) parseLocationPath(n node) (opnd node) {
+ switch p.r.typ {
+ case itemSlash:
+ p.next()
+ opnd = newRootNode("/")
+ if isStep(p.r.typ) {
+ opnd = p.parseRelativeLocationPath(opnd) // ?? child:: or self ??
+ }
+ case itemSlashSlash:
+ p.next()
+ opnd = newRootNode("//")
+ opnd = p.parseRelativeLocationPath(newAxisNode("descendant-or-self", "", "", "", opnd))
+ default:
+ opnd = p.parseRelativeLocationPath(n)
+ }
+ return opnd
+}
+
+// RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | AbbreviatedRelativeLocationPath
+func (p *parser) parseRelativeLocationPath(n node) node {
+ opnd := n
+Loop:
+ for {
+ opnd = p.parseStep(opnd)
+ switch p.r.typ {
+ case itemSlashSlash:
+ p.next()
+ opnd = newAxisNode("descendant-or-self", "", "", "", opnd)
+ case itemSlash:
+ p.next()
+ default:
+ break Loop
+ }
+ }
+ return opnd
+}
+
+// Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
+func (p *parser) parseStep(n node) (opnd node) {
+ axeTyp := "child" // default axes value.
+ if p.r.typ == itemDot || p.r.typ == itemDotDot {
+ if p.r.typ == itemDot {
+ axeTyp = "self"
+ } else {
+ axeTyp = "parent"
+ }
+ p.next()
+ opnd = newAxisNode(axeTyp, "", "", "", n)
+ if p.r.typ != itemLBracket {
+ return opnd
+ }
+ } else {
+ switch p.r.typ {
+ case itemAt:
+ p.next()
+ axeTyp = "attribute"
+ case itemAxe:
+ axeTyp = p.r.name
+ p.next()
+ case itemLParens:
+ return p.parseSequence(n)
+ }
+ opnd = p.parseNodeTest(n, axeTyp)
+ }
+ for p.r.typ == itemLBracket {
+ opnd = newFilterNode(opnd, p.parsePredicate(opnd))
+ }
+ return opnd
+}
+
+// Expr ::= '(' Step ("," Step)* ')'
+func (p *parser) parseSequence(n node) (opnd node) {
+ p.skipItem(itemLParens)
+ opnd = p.parseStep(n)
+ for {
+ if p.r.typ != itemComma {
+ break
+ }
+ p.next()
+ opnd2 := p.parseStep(n)
+ opnd = newOperatorNode("|", opnd, opnd2)
+ }
+ p.skipItem(itemRParens)
+ return opnd
+}
+
+// NodeTest ::= NameTest | nodeType '(' ')' | 'processing-instruction' '(' Literal ')'
+func (p *parser) parseNodeTest(n node, axeTyp string) (opnd node) {
+ switch p.r.typ {
+ case itemName:
+ if p.r.canBeFunc && isNodeType(p.r) {
+ var prop string
+ switch p.r.name {
+ case "comment", "text", "processing-instruction", "node":
+ prop = p.r.name
+ }
+ var name string
+ p.next()
+ p.skipItem(itemLParens)
+ if prop == "processing-instruction" && p.r.typ != itemRParens {
+ checkItem(p.r, itemString)
+ name = p.r.strval
+ p.next()
+ }
+ p.skipItem(itemRParens)
+ opnd = newAxisNode(axeTyp, name, "", prop, n)
+ } else {
+ prefix := p.r.prefix
+ name := p.r.name
+ p.next()
+ if p.r.name == "*" {
+ name = ""
+ }
+ opnd = newAxisNode(axeTyp, name, prefix, "", n)
+ }
+ case itemStar:
+ opnd = newAxisNode(axeTyp, "", "", "", n)
+ p.next()
+ default:
+ panic("expression must evaluate to a node-set")
+ }
+ return opnd
+}
+
+// PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall
+func (p *parser) parsePrimaryExpr(n node) (opnd node) {
+ switch p.r.typ {
+ case itemString:
+ opnd = newOperandNode(p.r.strval)
+ p.next()
+ case itemNumber:
+ opnd = newOperandNode(p.r.numval)
+ p.next()
+ case itemDollar:
+ p.next()
+ checkItem(p.r, itemName)
+ opnd = newVariableNode(p.r.prefix, p.r.name)
+ p.next()
+ case itemLParens:
+ p.next()
+ opnd = p.parseExpression(n)
+ p.skipItem(itemRParens)
+ case itemName:
+ if p.r.canBeFunc && !isNodeType(p.r) {
+ opnd = p.parseMethod(nil)
+ }
+ }
+ return opnd
+}
+
+// FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument )* )? ')'
+func (p *parser) parseMethod(n node) node {
+ var args []node
+ name := p.r.name
+ prefix := p.r.prefix
+
+ p.skipItem(itemName)
+ p.skipItem(itemLParens)
+ if p.r.typ != itemRParens {
+ for {
+ args = append(args, p.parseExpression(n))
+ if p.r.typ == itemRParens {
+ break
+ }
+ p.skipItem(itemComma)
+ }
+ }
+ p.skipItem(itemRParens)
+ return newFunctionNode(name, prefix, args)
+}
+
+// Parse parsing the XPath express string expr and returns a tree node.
+func parse(expr string) node {
+ r := &scanner{text: expr}
+ r.nextChar()
+ r.nextItem()
+ p := &parser{r: r}
+ return p.parseExpression(nil)
+}
+
+// rootNode holds a top-level node of tree.
+type rootNode struct {
+ nodeType
+ slash string
+}
+
+func (r *rootNode) String() string {
+ return r.slash
+}
+
+// operatorNode holds two Nodes operator.
+type operatorNode struct {
+ nodeType
+ Op string
+ Left, Right node
+}
+
+func (o *operatorNode) String() string {
+ return fmt.Sprintf("%v%s%v", o.Left, o.Op, o.Right)
+}
+
+// axisNode holds a location step.
+type axisNode struct {
+ nodeType
+ Input node
+ Prop string // node-test name.[comment|text|processing-instruction|node]
+ AxeType string // name of the axes.[attribute|ancestor|child|....]
+ LocalName string // local part name of node.
+ Prefix string // prefix name of node.
+}
+
+func (a *axisNode) String() string {
+ var b bytes.Buffer
+ if a.AxeType != "" {
+ b.Write([]byte(a.AxeType + "::"))
+ }
+ if a.Prefix != "" {
+ b.Write([]byte(a.Prefix + ":"))
+ }
+ b.Write([]byte(a.LocalName))
+ if a.Prop != "" {
+ b.Write([]byte("/" + a.Prop + "()"))
+ }
+ return b.String()
+}
+
+// operandNode holds a constant operand.
+type operandNode struct {
+ nodeType
+ Val interface{}
+}
+
+func (o *operandNode) String() string {
+ return fmt.Sprintf("%v", o.Val)
+}
+
+// filterNode holds a condition filter.
+type filterNode struct {
+ nodeType
+ Input, Condition node
+}
+
+func (f *filterNode) String() string {
+ return fmt.Sprintf("%s[%s]", f.Input, f.Condition)
+}
+
+// variableNode holds a variable.
+type variableNode struct {
+ nodeType
+ Name, Prefix string
+}
+
+func (v *variableNode) String() string {
+ if v.Prefix == "" {
+ return v.Name
+ }
+ return fmt.Sprintf("%s:%s", v.Prefix, v.Name)
+}
+
+// functionNode holds a function call.
+type functionNode struct {
+ nodeType
+ Args []node
+ Prefix string
+ FuncName string // function name
+}
+
+func (f *functionNode) String() string {
+ var b bytes.Buffer
+ // fun(arg1, ..., argn)
+ b.Write([]byte(f.FuncName))
+ b.Write([]byte("("))
+ for i, arg := range f.Args {
+ if i > 0 {
+ b.Write([]byte(","))
+ }
+ b.Write([]byte(fmt.Sprintf("%s", arg)))
+ }
+ b.Write([]byte(")"))
+ return b.String()
+}
+
+type scanner struct {
+ text, name, prefix string
+
+ pos int
+ curr rune
+ typ itemType
+ strval string // text value at current pos
+ numval float64 // number value at current pos
+ canBeFunc bool
+}
+
+func (s *scanner) nextChar() bool {
+ if s.pos >= len(s.text) {
+ s.curr = rune(0)
+ return false
+ }
+ s.curr = rune(s.text[s.pos])
+ s.pos++
+ return true
+}
+
+func (s *scanner) nextItem() bool {
+ s.skipSpace()
+ switch s.curr {
+ case 0:
+ s.typ = itemEOF
+ return false
+ case ',', '@', '(', ')', '|', '*', '[', ']', '+', '-', '=', '#', '$':
+ s.typ = asItemType(s.curr)
+ s.nextChar()
+ case '<':
+ s.typ = itemLt
+ s.nextChar()
+ if s.curr == '=' {
+ s.typ = itemLe
+ s.nextChar()
+ }
+ case '>':
+ s.typ = itemGt
+ s.nextChar()
+ if s.curr == '=' {
+ s.typ = itemGe
+ s.nextChar()
+ }
+ case '!':
+ s.typ = itemBang
+ s.nextChar()
+ if s.curr == '=' {
+ s.typ = itemNe
+ s.nextChar()
+ }
+ case '.':
+ s.typ = itemDot
+ s.nextChar()
+ if s.curr == '.' {
+ s.typ = itemDotDot
+ s.nextChar()
+ } else if isDigit(s.curr) {
+ s.typ = itemNumber
+ s.numval = s.scanFraction()
+ }
+ case '/':
+ s.typ = itemSlash
+ s.nextChar()
+ if s.curr == '/' {
+ s.typ = itemSlashSlash
+ s.nextChar()
+ }
+ case '"', '\'':
+ s.typ = itemString
+ s.strval = s.scanString()
+ default:
+ if isDigit(s.curr) {
+ s.typ = itemNumber
+ s.numval = s.scanNumber()
+ } else if isName(s.curr) {
+ s.typ = itemName
+ s.name = s.scanName()
+ s.prefix = ""
+ // "foo:bar" is one itemem not three because it doesn't allow spaces in between
+ // We should distinct it from "foo::" and need process "foo ::" as well
+ if s.curr == ':' {
+ s.nextChar()
+ // can be "foo:bar" or "foo::"
+ if s.curr == ':' {
+ // "foo::"
+ s.nextChar()
+ s.typ = itemAxe
+ } else { // "foo:*", "foo:bar" or "foo: "
+ s.prefix = s.name
+ if s.curr == '*' {
+ s.nextChar()
+ s.name = "*"
+ } else if isName(s.curr) {
+ s.name = s.scanName()
+ } else {
+ panic(fmt.Sprintf("%s has an invalid qualified name.", s.text))
+ }
+ }
+ } else {
+ s.skipSpace()
+ if s.curr == ':' {
+ s.nextChar()
+ // it can be "foo ::" or just "foo :"
+ if s.curr == ':' {
+ s.nextChar()
+ s.typ = itemAxe
+ } else {
+ panic(fmt.Sprintf("%s has an invalid qualified name.", s.text))
+ }
+ }
+ }
+ s.skipSpace()
+ s.canBeFunc = s.curr == '('
+ } else {
+ panic(fmt.Sprintf("%s has an invalid token.", s.text))
+ }
+ }
+ return true
+}
+
+func (s *scanner) skipSpace() {
+Loop:
+ for {
+ if !unicode.IsSpace(s.curr) || !s.nextChar() {
+ break Loop
+ }
+ }
+}
+
+func (s *scanner) scanFraction() float64 {
+ var (
+ i = s.pos - 2
+ c = 1 // '.'
+ )
+ for isDigit(s.curr) {
+ s.nextChar()
+ c++
+ }
+ v, err := strconv.ParseFloat(s.text[i:i+c], 64)
+ if err != nil {
+ panic(fmt.Errorf("xpath: scanFraction parse float got error: %v", err))
+ }
+ return v
+}
+
+func (s *scanner) scanNumber() float64 {
+ var (
+ c int
+ i = s.pos - 1
+ )
+ for isDigit(s.curr) {
+ s.nextChar()
+ c++
+ }
+ if s.curr == '.' {
+ s.nextChar()
+ c++
+ for isDigit(s.curr) {
+ s.nextChar()
+ c++
+ }
+ }
+ v, err := strconv.ParseFloat(s.text[i:i+c], 64)
+ if err != nil {
+ panic(fmt.Errorf("xpath: scanNumber parse float got error: %v", err))
+ }
+ return v
+}
+
+func (s *scanner) scanString() string {
+ var (
+ c = 0
+ end = s.curr
+ )
+ s.nextChar()
+ i := s.pos - 1
+ for s.curr != end {
+ if !s.nextChar() {
+ panic(errors.New("xpath: scanString got unclosed string"))
+ }
+ c++
+ }
+ s.nextChar()
+ return s.text[i : i+c]
+}
+
+func (s *scanner) scanName() string {
+ var (
+ c int
+ i = s.pos - 1
+ )
+ for isName(s.curr) {
+ c++
+ if !s.nextChar() {
+ break
+ }
+ }
+ return s.text[i : i+c]
+}
+
+func isName(r rune) bool {
+ return string(r) != ":" && string(r) != "/" &&
+ (unicode.Is(first, r) || unicode.Is(second, r) || string(r) == "*")
+}
+
+func isDigit(r rune) bool {
+ return unicode.IsDigit(r)
+}
+
+func asItemType(r rune) itemType {
+ switch r {
+ case ',':
+ return itemComma
+ case '@':
+ return itemAt
+ case '(':
+ return itemLParens
+ case ')':
+ return itemRParens
+ case '|':
+ return itemUnion
+ case '*':
+ return itemStar
+ case '[':
+ return itemLBracket
+ case ']':
+ return itemRBracket
+ case '+':
+ return itemPlus
+ case '-':
+ return itemMinus
+ case '=':
+ return itemEq
+ case '$':
+ return itemDollar
+ }
+ panic(fmt.Errorf("unknown item: %v", r))
+}
+
+var first = &unicode.RangeTable{
+ R16: []unicode.Range16{
+ {0x003A, 0x003A, 1},
+ {0x0041, 0x005A, 1},
+ {0x005F, 0x005F, 1},
+ {0x0061, 0x007A, 1},
+ {0x00C0, 0x00D6, 1},
+ {0x00D8, 0x00F6, 1},
+ {0x00F8, 0x00FF, 1},
+ {0x0100, 0x0131, 1},
+ {0x0134, 0x013E, 1},
+ {0x0141, 0x0148, 1},
+ {0x014A, 0x017E, 1},
+ {0x0180, 0x01C3, 1},
+ {0x01CD, 0x01F0, 1},
+ {0x01F4, 0x01F5, 1},
+ {0x01FA, 0x0217, 1},
+ {0x0250, 0x02A8, 1},
+ {0x02BB, 0x02C1, 1},
+ {0x0386, 0x0386, 1},
+ {0x0388, 0x038A, 1},
+ {0x038C, 0x038C, 1},
+ {0x038E, 0x03A1, 1},
+ {0x03A3, 0x03CE, 1},
+ {0x03D0, 0x03D6, 1},
+ {0x03DA, 0x03E0, 2},
+ {0x03E2, 0x03F3, 1},
+ {0x0401, 0x040C, 1},
+ {0x040E, 0x044F, 1},
+ {0x0451, 0x045C, 1},
+ {0x045E, 0x0481, 1},
+ {0x0490, 0x04C4, 1},
+ {0x04C7, 0x04C8, 1},
+ {0x04CB, 0x04CC, 1},
+ {0x04D0, 0x04EB, 1},
+ {0x04EE, 0x04F5, 1},
+ {0x04F8, 0x04F9, 1},
+ {0x0531, 0x0556, 1},
+ {0x0559, 0x0559, 1},
+ {0x0561, 0x0586, 1},
+ {0x05D0, 0x05EA, 1},
+ {0x05F0, 0x05F2, 1},
+ {0x0621, 0x063A, 1},
+ {0x0641, 0x064A, 1},
+ {0x0671, 0x06B7, 1},
+ {0x06BA, 0x06BE, 1},
+ {0x06C0, 0x06CE, 1},
+ {0x06D0, 0x06D3, 1},
+ {0x06D5, 0x06D5, 1},
+ {0x06E5, 0x06E6, 1},
+ {0x0905, 0x0939, 1},
+ {0x093D, 0x093D, 1},
+ {0x0958, 0x0961, 1},
+ {0x0985, 0x098C, 1},
+ {0x098F, 0x0990, 1},
+ {0x0993, 0x09A8, 1},
+ {0x09AA, 0x09B0, 1},
+ {0x09B2, 0x09B2, 1},
+ {0x09B6, 0x09B9, 1},
+ {0x09DC, 0x09DD, 1},
+ {0x09DF, 0x09E1, 1},
+ {0x09F0, 0x09F1, 1},
+ {0x0A05, 0x0A0A, 1},
+ {0x0A0F, 0x0A10, 1},
+ {0x0A13, 0x0A28, 1},
+ {0x0A2A, 0x0A30, 1},
+ {0x0A32, 0x0A33, 1},
+ {0x0A35, 0x0A36, 1},
+ {0x0A38, 0x0A39, 1},
+ {0x0A59, 0x0A5C, 1},
+ {0x0A5E, 0x0A5E, 1},
+ {0x0A72, 0x0A74, 1},
+ {0x0A85, 0x0A8B, 1},
+ {0x0A8D, 0x0A8D, 1},
+ {0x0A8F, 0x0A91, 1},
+ {0x0A93, 0x0AA8, 1},
+ {0x0AAA, 0x0AB0, 1},
+ {0x0AB2, 0x0AB3, 1},
+ {0x0AB5, 0x0AB9, 1},
+ {0x0ABD, 0x0AE0, 0x23},
+ {0x0B05, 0x0B0C, 1},
+ {0x0B0F, 0x0B10, 1},
+ {0x0B13, 0x0B28, 1},
+ {0x0B2A, 0x0B30, 1},
+ {0x0B32, 0x0B33, 1},
+ {0x0B36, 0x0B39, 1},
+ {0x0B3D, 0x0B3D, 1},
+ {0x0B5C, 0x0B5D, 1},
+ {0x0B5F, 0x0B61, 1},
+ {0x0B85, 0x0B8A, 1},
+ {0x0B8E, 0x0B90, 1},
+ {0x0B92, 0x0B95, 1},
+ {0x0B99, 0x0B9A, 1},
+ {0x0B9C, 0x0B9C, 1},
+ {0x0B9E, 0x0B9F, 1},
+ {0x0BA3, 0x0BA4, 1},
+ {0x0BA8, 0x0BAA, 1},
+ {0x0BAE, 0x0BB5, 1},
+ {0x0BB7, 0x0BB9, 1},
+ {0x0C05, 0x0C0C, 1},
+ {0x0C0E, 0x0C10, 1},
+ {0x0C12, 0x0C28, 1},
+ {0x0C2A, 0x0C33, 1},
+ {0x0C35, 0x0C39, 1},
+ {0x0C60, 0x0C61, 1},
+ {0x0C85, 0x0C8C, 1},
+ {0x0C8E, 0x0C90, 1},
+ {0x0C92, 0x0CA8, 1},
+ {0x0CAA, 0x0CB3, 1},
+ {0x0CB5, 0x0CB9, 1},
+ {0x0CDE, 0x0CDE, 1},
+ {0x0CE0, 0x0CE1, 1},
+ {0x0D05, 0x0D0C, 1},
+ {0x0D0E, 0x0D10, 1},
+ {0x0D12, 0x0D28, 1},
+ {0x0D2A, 0x0D39, 1},
+ {0x0D60, 0x0D61, 1},
+ {0x0E01, 0x0E2E, 1},
+ {0x0E30, 0x0E30, 1},
+ {0x0E32, 0x0E33, 1},
+ {0x0E40, 0x0E45, 1},
+ {0x0E81, 0x0E82, 1},
+ {0x0E84, 0x0E84, 1},
+ {0x0E87, 0x0E88, 1},
+ {0x0E8A, 0x0E8D, 3},
+ {0x0E94, 0x0E97, 1},
+ {0x0E99, 0x0E9F, 1},
+ {0x0EA1, 0x0EA3, 1},
+ {0x0EA5, 0x0EA7, 2},
+ {0x0EAA, 0x0EAB, 1},
+ {0x0EAD, 0x0EAE, 1},
+ {0x0EB0, 0x0EB0, 1},
+ {0x0EB2, 0x0EB3, 1},
+ {0x0EBD, 0x0EBD, 1},
+ {0x0EC0, 0x0EC4, 1},
+ {0x0F40, 0x0F47, 1},
+ {0x0F49, 0x0F69, 1},
+ {0x10A0, 0x10C5, 1},
+ {0x10D0, 0x10F6, 1},
+ {0x1100, 0x1100, 1},
+ {0x1102, 0x1103, 1},
+ {0x1105, 0x1107, 1},
+ {0x1109, 0x1109, 1},
+ {0x110B, 0x110C, 1},
+ {0x110E, 0x1112, 1},
+ {0x113C, 0x1140, 2},
+ {0x114C, 0x1150, 2},
+ {0x1154, 0x1155, 1},
+ {0x1159, 0x1159, 1},
+ {0x115F, 0x1161, 1},
+ {0x1163, 0x1169, 2},
+ {0x116D, 0x116E, 1},
+ {0x1172, 0x1173, 1},
+ {0x1175, 0x119E, 0x119E - 0x1175},
+ {0x11A8, 0x11AB, 0x11AB - 0x11A8},
+ {0x11AE, 0x11AF, 1},
+ {0x11B7, 0x11B8, 1},
+ {0x11BA, 0x11BA, 1},
+ {0x11BC, 0x11C2, 1},
+ {0x11EB, 0x11F0, 0x11F0 - 0x11EB},
+ {0x11F9, 0x11F9, 1},
+ {0x1E00, 0x1E9B, 1},
+ {0x1EA0, 0x1EF9, 1},
+ {0x1F00, 0x1F15, 1},
+ {0x1F18, 0x1F1D, 1},
+ {0x1F20, 0x1F45, 1},
+ {0x1F48, 0x1F4D, 1},
+ {0x1F50, 0x1F57, 1},
+ {0x1F59, 0x1F5B, 0x1F5B - 0x1F59},
+ {0x1F5D, 0x1F5D, 1},
+ {0x1F5F, 0x1F7D, 1},
+ {0x1F80, 0x1FB4, 1},
+ {0x1FB6, 0x1FBC, 1},
+ {0x1FBE, 0x1FBE, 1},
+ {0x1FC2, 0x1FC4, 1},
+ {0x1FC6, 0x1FCC, 1},
+ {0x1FD0, 0x1FD3, 1},
+ {0x1FD6, 0x1FDB, 1},
+ {0x1FE0, 0x1FEC, 1},
+ {0x1FF2, 0x1FF4, 1},
+ {0x1FF6, 0x1FFC, 1},
+ {0x2126, 0x2126, 1},
+ {0x212A, 0x212B, 1},
+ {0x212E, 0x212E, 1},
+ {0x2180, 0x2182, 1},
+ {0x3007, 0x3007, 1},
+ {0x3021, 0x3029, 1},
+ {0x3041, 0x3094, 1},
+ {0x30A1, 0x30FA, 1},
+ {0x3105, 0x312C, 1},
+ {0x4E00, 0x9FA5, 1},
+ {0xAC00, 0xD7A3, 1},
+ },
+}
+
+var second = &unicode.RangeTable{
+ R16: []unicode.Range16{
+ {0x002D, 0x002E, 1},
+ {0x0030, 0x0039, 1},
+ {0x00B7, 0x00B7, 1},
+ {0x02D0, 0x02D1, 1},
+ {0x0300, 0x0345, 1},
+ {0x0360, 0x0361, 1},
+ {0x0387, 0x0387, 1},
+ {0x0483, 0x0486, 1},
+ {0x0591, 0x05A1, 1},
+ {0x05A3, 0x05B9, 1},
+ {0x05BB, 0x05BD, 1},
+ {0x05BF, 0x05BF, 1},
+ {0x05C1, 0x05C2, 1},
+ {0x05C4, 0x0640, 0x0640 - 0x05C4},
+ {0x064B, 0x0652, 1},
+ {0x0660, 0x0669, 1},
+ {0x0670, 0x0670, 1},
+ {0x06D6, 0x06DC, 1},
+ {0x06DD, 0x06DF, 1},
+ {0x06E0, 0x06E4, 1},
+ {0x06E7, 0x06E8, 1},
+ {0x06EA, 0x06ED, 1},
+ {0x06F0, 0x06F9, 1},
+ {0x0901, 0x0903, 1},
+ {0x093C, 0x093C, 1},
+ {0x093E, 0x094C, 1},
+ {0x094D, 0x094D, 1},
+ {0x0951, 0x0954, 1},
+ {0x0962, 0x0963, 1},
+ {0x0966, 0x096F, 1},
+ {0x0981, 0x0983, 1},
+ {0x09BC, 0x09BC, 1},
+ {0x09BE, 0x09BF, 1},
+ {0x09C0, 0x09C4, 1},
+ {0x09C7, 0x09C8, 1},
+ {0x09CB, 0x09CD, 1},
+ {0x09D7, 0x09D7, 1},
+ {0x09E2, 0x09E3, 1},
+ {0x09E6, 0x09EF, 1},
+ {0x0A02, 0x0A3C, 0x3A},
+ {0x0A3E, 0x0A3F, 1},
+ {0x0A40, 0x0A42, 1},
+ {0x0A47, 0x0A48, 1},
+ {0x0A4B, 0x0A4D, 1},
+ {0x0A66, 0x0A6F, 1},
+ {0x0A70, 0x0A71, 1},
+ {0x0A81, 0x0A83, 1},
+ {0x0ABC, 0x0ABC, 1},
+ {0x0ABE, 0x0AC5, 1},
+ {0x0AC7, 0x0AC9, 1},
+ {0x0ACB, 0x0ACD, 1},
+ {0x0AE6, 0x0AEF, 1},
+ {0x0B01, 0x0B03, 1},
+ {0x0B3C, 0x0B3C, 1},
+ {0x0B3E, 0x0B43, 1},
+ {0x0B47, 0x0B48, 1},
+ {0x0B4B, 0x0B4D, 1},
+ {0x0B56, 0x0B57, 1},
+ {0x0B66, 0x0B6F, 1},
+ {0x0B82, 0x0B83, 1},
+ {0x0BBE, 0x0BC2, 1},
+ {0x0BC6, 0x0BC8, 1},
+ {0x0BCA, 0x0BCD, 1},
+ {0x0BD7, 0x0BD7, 1},
+ {0x0BE7, 0x0BEF, 1},
+ {0x0C01, 0x0C03, 1},
+ {0x0C3E, 0x0C44, 1},
+ {0x0C46, 0x0C48, 1},
+ {0x0C4A, 0x0C4D, 1},
+ {0x0C55, 0x0C56, 1},
+ {0x0C66, 0x0C6F, 1},
+ {0x0C82, 0x0C83, 1},
+ {0x0CBE, 0x0CC4, 1},
+ {0x0CC6, 0x0CC8, 1},
+ {0x0CCA, 0x0CCD, 1},
+ {0x0CD5, 0x0CD6, 1},
+ {0x0CE6, 0x0CEF, 1},
+ {0x0D02, 0x0D03, 1},
+ {0x0D3E, 0x0D43, 1},
+ {0x0D46, 0x0D48, 1},
+ {0x0D4A, 0x0D4D, 1},
+ {0x0D57, 0x0D57, 1},
+ {0x0D66, 0x0D6F, 1},
+ {0x0E31, 0x0E31, 1},
+ {0x0E34, 0x0E3A, 1},
+ {0x0E46, 0x0E46, 1},
+ {0x0E47, 0x0E4E, 1},
+ {0x0E50, 0x0E59, 1},
+ {0x0EB1, 0x0EB1, 1},
+ {0x0EB4, 0x0EB9, 1},
+ {0x0EBB, 0x0EBC, 1},
+ {0x0EC6, 0x0EC6, 1},
+ {0x0EC8, 0x0ECD, 1},
+ {0x0ED0, 0x0ED9, 1},
+ {0x0F18, 0x0F19, 1},
+ {0x0F20, 0x0F29, 1},
+ {0x0F35, 0x0F39, 2},
+ {0x0F3E, 0x0F3F, 1},
+ {0x0F71, 0x0F84, 1},
+ {0x0F86, 0x0F8B, 1},
+ {0x0F90, 0x0F95, 1},
+ {0x0F97, 0x0F97, 1},
+ {0x0F99, 0x0FAD, 1},
+ {0x0FB1, 0x0FB7, 1},
+ {0x0FB9, 0x0FB9, 1},
+ {0x20D0, 0x20DC, 1},
+ {0x20E1, 0x3005, 0x3005 - 0x20E1},
+ {0x302A, 0x302F, 1},
+ {0x3031, 0x3035, 1},
+ {0x3099, 0x309A, 1},
+ {0x309D, 0x309E, 1},
+ {0x30FC, 0x30FE, 1},
+ },
+}
diff --git a/vendor/github.com/antchfx/xpath/query.go b/vendor/github.com/antchfx/xpath/query.go
new file mode 100644
index 00000000..9735ef09
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/query.go
@@ -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) {
+ continue
+ }
+ 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
+}
diff --git a/vendor/github.com/antchfx/xpath/xpath.go b/vendor/github.com/antchfx/xpath/xpath.go
new file mode 100644
index 00000000..7e3f52c3
--- /dev/null
+++ b/vendor/github.com/antchfx/xpath/xpath.go
@@ -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 .
+ 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
+ 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
+}