mirror of
https://github.com/bettercap/bettercap
synced 2025-07-07 05:22:04 -07:00
new: added events.on (and other related commands) to trigger specific actions when an events happens
This commit is contained in:
parent
78c341c2b3
commit
1492bf5e40
24 changed files with 4475 additions and 0 deletions
17
Gopkg.lock
generated
17
Gopkg.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
62
modules/events_stream/events_triggers.go
Normal file
62
modules/events_stream/events_triggers.go
Normal file
|
@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
136
modules/events_stream/trigger_list.go
Normal file
136
modules/events_stream/trigger_list.go
Normal file
|
@ -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
|
||||
}
|
32
vendor/github.com/antchfx/jsonquery/.gitignore
generated
vendored
Normal file
32
vendor/github.com/antchfx/jsonquery/.gitignore
generated
vendored
Normal file
|
@ -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
|
14
vendor/github.com/antchfx/jsonquery/.travis.yml
generated
vendored
Normal file
14
vendor/github.com/antchfx/jsonquery/.travis.yml
generated
vendored
Normal file
|
@ -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
|
17
vendor/github.com/antchfx/jsonquery/LICENSE
generated
vendored
Normal file
17
vendor/github.com/antchfx/jsonquery/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
169
vendor/github.com/antchfx/jsonquery/README.md
generated
vendored
Normal file
169
vendor/github.com/antchfx/jsonquery/README.md
generated
vendored
Normal file
|
@ -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
|
||||
<name>John</name>
|
||||
<age>30</age>
|
||||
<cars>
|
||||
<element>
|
||||
<name>Ford</name>
|
||||
<models>
|
||||
<element>Fiesta</element>
|
||||
<element>Focus</element>
|
||||
<element>Mustang</element>
|
||||
</models>
|
||||
</element>
|
||||
<element>
|
||||
<name>BMW</name>
|
||||
<models>
|
||||
<element>320</element>
|
||||
<element>X3</element>
|
||||
<element>X5</element>
|
||||
</models>
|
||||
</element>
|
||||
<element>
|
||||
<name>Fiat</name>
|
||||
<models>
|
||||
<element>500</element>
|
||||
<element>Panda</element>
|
||||
</models>
|
||||
</element>
|
||||
</cars>
|
||||
```
|
||||
|
||||
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|
|
36
vendor/github.com/antchfx/jsonquery/books.json
generated
vendored
Normal file
36
vendor/github.com/antchfx/jsonquery/books.json
generated
vendored
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
157
vendor/github.com/antchfx/jsonquery/node.go
generated
vendored
Normal file
157
vendor/github.com/antchfx/jsonquery/node.go
generated
vendored
Normal file
|
@ -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)
|
||||
}
|
149
vendor/github.com/antchfx/jsonquery/query.go
generated
vendored
Normal file
149
vendor/github.com/antchfx/jsonquery/query.go
generated
vendored
Normal file
|
@ -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
|
||||
}
|
32
vendor/github.com/antchfx/xpath/.gitignore
generated
vendored
Normal file
32
vendor/github.com/antchfx/xpath/.gitignore
generated
vendored
Normal file
|
@ -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
|
12
vendor/github.com/antchfx/xpath/.travis.yml
generated
vendored
Normal file
12
vendor/github.com/antchfx/xpath/.travis.yml
generated
vendored
Normal file
|
@ -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
|
17
vendor/github.com/antchfx/xpath/LICENSE
generated
vendored
Normal file
17
vendor/github.com/antchfx/xpath/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
167
vendor/github.com/antchfx/xpath/README.md
generated
vendored
Normal file
167
vendor/github.com/antchfx/xpath/README.md
generated
vendored
Normal file
|
@ -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).
|
483
vendor/github.com/antchfx/xpath/build.go
generated
vendored
Normal file
483
vendor/github.com/antchfx/xpath/build.go
generated
vendored
Normal file
|
@ -0,0 +1,483 @@
|
|||
package xpath
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type flag int
|
||||
|
||||
const (
|
||||
noneFlag flag = iota
|
||||
filterFlag
|
||||
)
|
||||
|
||||
// builder provides building an XPath expressions.
|
||||
type builder struct {
|
||||
depth int
|
||||
flag flag
|
||||
firstInput query
|
||||
}
|
||||
|
||||
// axisPredicate creates a predicate to predicating for this axis node.
|
||||
func axisPredicate(root *axisNode) func(NodeNavigator) bool {
|
||||
// get current axix node type.
|
||||
typ := ElementNode
|
||||
switch root.AxeType {
|
||||
case "attribute":
|
||||
typ = AttributeNode
|
||||
case "self", "parent":
|
||||
typ = allNode
|
||||
default:
|
||||
switch root.Prop {
|
||||
case "comment":
|
||||
typ = CommentNode
|
||||
case "text":
|
||||
typ = TextNode
|
||||
// case "processing-instruction":
|
||||
// typ = ProcessingInstructionNode
|
||||
case "node":
|
||||
typ = allNode
|
||||
}
|
||||
}
|
||||
nametest := root.LocalName != "" || root.Prefix != ""
|
||||
predicate := func(n NodeNavigator) bool {
|
||||
if typ == n.NodeType() || typ == allNode || typ == TextNode {
|
||||
if nametest {
|
||||
if root.LocalName == n.LocalName() && root.Prefix == n.Prefix() {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return predicate
|
||||
}
|
||||
|
||||
// processAxisNode processes a query for the XPath axis node.
|
||||
func (b *builder) processAxisNode(root *axisNode) (query, error) {
|
||||
var (
|
||||
err error
|
||||
qyInput query
|
||||
qyOutput query
|
||||
predicate = axisPredicate(root)
|
||||
)
|
||||
|
||||
if root.Input == nil {
|
||||
qyInput = &contextQuery{}
|
||||
} else {
|
||||
if root.AxeType == "child" && (root.Input.Type() == nodeAxis) {
|
||||
if input := root.Input.(*axisNode); input.AxeType == "descendant-or-self" {
|
||||
var qyGrandInput query
|
||||
if input.Input != nil {
|
||||
qyGrandInput, _ = b.processNode(input.Input)
|
||||
} else {
|
||||
qyGrandInput = &contextQuery{}
|
||||
}
|
||||
qyOutput = &descendantQuery{Input: qyGrandInput, Predicate: predicate, Self: true}
|
||||
return qyOutput, nil
|
||||
}
|
||||
}
|
||||
qyInput, err = b.processNode(root.Input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
switch root.AxeType {
|
||||
case "ancestor":
|
||||
qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate}
|
||||
case "ancestor-or-self":
|
||||
qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate, Self: true}
|
||||
case "attribute":
|
||||
qyOutput = &attributeQuery{Input: qyInput, Predicate: predicate}
|
||||
case "child":
|
||||
filter := func(n NodeNavigator) bool {
|
||||
v := predicate(n)
|
||||
switch root.Prop {
|
||||
case "text":
|
||||
v = v && n.NodeType() == TextNode
|
||||
case "node":
|
||||
v = v && (n.NodeType() == ElementNode || n.NodeType() == TextNode)
|
||||
case "comment":
|
||||
v = v && n.NodeType() == CommentNode
|
||||
}
|
||||
return v
|
||||
}
|
||||
qyOutput = &childQuery{Input: qyInput, Predicate: filter}
|
||||
case "descendant":
|
||||
qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate}
|
||||
case "descendant-or-self":
|
||||
qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate, Self: true}
|
||||
case "following":
|
||||
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate}
|
||||
case "following-sibling":
|
||||
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
|
||||
case "parent":
|
||||
qyOutput = &parentQuery{Input: qyInput, Predicate: predicate}
|
||||
case "preceding":
|
||||
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate}
|
||||
case "preceding-sibling":
|
||||
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
|
||||
case "self":
|
||||
qyOutput = &selfQuery{Input: qyInput, Predicate: predicate}
|
||||
case "namespace":
|
||||
// haha,what will you do someting??
|
||||
default:
|
||||
err = fmt.Errorf("unknown axe type: %s", root.AxeType)
|
||||
return nil, err
|
||||
}
|
||||
return qyOutput, nil
|
||||
}
|
||||
|
||||
// processFilterNode builds query for the XPath filter predicate.
|
||||
func (b *builder) processFilterNode(root *filterNode) (query, error) {
|
||||
b.flag |= filterFlag
|
||||
|
||||
qyInput, err := b.processNode(root.Input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyCond, err := b.processNode(root.Condition)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput := &filterQuery{Input: qyInput, Predicate: qyCond}
|
||||
return qyOutput, nil
|
||||
}
|
||||
|
||||
// processFunctionNode processes query for the XPath function node.
|
||||
func (b *builder) processFunctionNode(root *functionNode) (query, error) {
|
||||
var qyOutput query
|
||||
switch root.FuncName {
|
||||
case "starts-with":
|
||||
arg1, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arg2, err := b.processNode(root.Args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: startwithFunc(arg1, arg2)}
|
||||
case "ends-with":
|
||||
arg1, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arg2, err := b.processNode(root.Args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: endwithFunc(arg1, arg2)}
|
||||
case "contains":
|
||||
arg1, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arg2, err := b.processNode(root.Args[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: containsFunc(arg1, arg2)}
|
||||
case "substring":
|
||||
//substring( string , start [, length] )
|
||||
if len(root.Args) < 2 {
|
||||
return nil, errors.New("xpath: substring function must have at least two parameter")
|
||||
}
|
||||
var (
|
||||
arg1, arg2, arg3 query
|
||||
err error
|
||||
)
|
||||
if arg1, err = b.processNode(root.Args[0]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if arg2, err = b.processNode(root.Args[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(root.Args) == 3 {
|
||||
if arg3, err = b.processNode(root.Args[2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: substringFunc(arg1, arg2, arg3)}
|
||||
case "substring-before", "substring-after":
|
||||
//substring-xxxx( haystack, needle )
|
||||
if len(root.Args) != 2 {
|
||||
return nil, errors.New("xpath: substring-before function must have two parameters")
|
||||
}
|
||||
var (
|
||||
arg1, arg2 query
|
||||
err error
|
||||
)
|
||||
if arg1, err = b.processNode(root.Args[0]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if arg2, err = b.processNode(root.Args[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{
|
||||
Input: b.firstInput,
|
||||
Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"),
|
||||
}
|
||||
case "string-length":
|
||||
// string-length( [string] )
|
||||
if len(root.Args) < 1 {
|
||||
return nil, errors.New("xpath: string-length function must have at least one parameter")
|
||||
}
|
||||
arg1, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: stringLengthFunc(arg1)}
|
||||
case "normalize-space":
|
||||
if len(root.Args) == 0 {
|
||||
return nil, errors.New("xpath: normalize-space function must have at least one parameter")
|
||||
}
|
||||
argQuery, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{Input: argQuery, Func: normalizespaceFunc}
|
||||
case "translate":
|
||||
//translate( string , string, string )
|
||||
if len(root.Args) != 3 {
|
||||
return nil, errors.New("xpath: translate function must have three parameters")
|
||||
}
|
||||
var (
|
||||
arg1, arg2, arg3 query
|
||||
err error
|
||||
)
|
||||
if arg1, err = b.processNode(root.Args[0]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if arg2, err = b.processNode(root.Args[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if arg3, err = b.processNode(root.Args[2]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: translateFunc(arg1, arg2, arg3)}
|
||||
case "not":
|
||||
if len(root.Args) == 0 {
|
||||
return nil, errors.New("xpath: not function must have at least one parameter")
|
||||
}
|
||||
argQuery, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{Input: argQuery, Func: notFunc}
|
||||
case "name", "local-name", "namespace-uri":
|
||||
inp := b.firstInput
|
||||
if len(root.Args) > 1 {
|
||||
return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName)
|
||||
}
|
||||
if len(root.Args) == 1 {
|
||||
argQuery, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inp = argQuery
|
||||
}
|
||||
f := &functionQuery{Input: inp}
|
||||
switch root.FuncName {
|
||||
case "name":
|
||||
f.Func = nameFunc
|
||||
case "local-name":
|
||||
f.Func = localNameFunc
|
||||
case "namespace-uri":
|
||||
f.Func = namespaceFunc
|
||||
}
|
||||
qyOutput = f
|
||||
case "true", "false":
|
||||
val := root.FuncName == "true"
|
||||
qyOutput = &functionQuery{
|
||||
Input: b.firstInput,
|
||||
Func: func(_ query, _ iterator) interface{} {
|
||||
return val
|
||||
},
|
||||
}
|
||||
case "last":
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc}
|
||||
case "position":
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc}
|
||||
case "boolean", "number", "string":
|
||||
inp := b.firstInput
|
||||
if len(root.Args) > 1 {
|
||||
return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName)
|
||||
}
|
||||
if len(root.Args) == 1 {
|
||||
argQuery, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inp = argQuery
|
||||
}
|
||||
f := &functionQuery{Input: inp}
|
||||
switch root.FuncName {
|
||||
case "boolean":
|
||||
f.Func = booleanFunc
|
||||
case "string":
|
||||
f.Func = stringFunc
|
||||
case "number":
|
||||
f.Func = numberFunc
|
||||
}
|
||||
qyOutput = f
|
||||
case "count":
|
||||
//if b.firstInput == nil {
|
||||
// return nil, errors.New("xpath: expression must evaluate to node-set")
|
||||
//}
|
||||
if len(root.Args) == 0 {
|
||||
return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets")
|
||||
}
|
||||
argQuery, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{Input: argQuery, Func: countFunc}
|
||||
case "sum":
|
||||
if len(root.Args) == 0 {
|
||||
return nil, fmt.Errorf("xpath: sum(node-sets) function must with have parameters node-sets")
|
||||
}
|
||||
argQuery, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
qyOutput = &functionQuery{Input: argQuery, Func: sumFunc}
|
||||
case "ceiling", "floor", "round":
|
||||
if len(root.Args) == 0 {
|
||||
return nil, fmt.Errorf("xpath: ceiling(node-sets) function must with have parameters node-sets")
|
||||
}
|
||||
argQuery, err := b.processNode(root.Args[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f := &functionQuery{Input: argQuery}
|
||||
switch root.FuncName {
|
||||
case "ceiling":
|
||||
f.Func = ceilingFunc
|
||||
case "floor":
|
||||
f.Func = floorFunc
|
||||
case "round":
|
||||
f.Func = roundFunc
|
||||
}
|
||||
qyOutput = f
|
||||
case "concat":
|
||||
if len(root.Args) < 2 {
|
||||
return nil, fmt.Errorf("xpath: concat() must have at least two arguments")
|
||||
}
|
||||
var args []query
|
||||
for _, v := range root.Args {
|
||||
q, err := b.processNode(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args = append(args, q)
|
||||
}
|
||||
qyOutput = &functionQuery{Input: b.firstInput, Func: concatFunc(args...)}
|
||||
default:
|
||||
return nil, fmt.Errorf("not yet support this function %s()", root.FuncName)
|
||||
}
|
||||
return qyOutput, nil
|
||||
}
|
||||
|
||||
func (b *builder) processOperatorNode(root *operatorNode) (query, error) {
|
||||
left, err := b.processNode(root.Left)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
right, err := b.processNode(root.Right)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var qyOutput query
|
||||
switch root.Op {
|
||||
case "+", "-", "div", "mod": // Numeric operator
|
||||
var exprFunc func(interface{}, interface{}) interface{}
|
||||
switch root.Op {
|
||||
case "+":
|
||||
exprFunc = plusFunc
|
||||
case "-":
|
||||
exprFunc = minusFunc
|
||||
case "div":
|
||||
exprFunc = divFunc
|
||||
case "mod":
|
||||
exprFunc = modFunc
|
||||
}
|
||||
qyOutput = &numericQuery{Left: left, Right: right, Do: exprFunc}
|
||||
case "=", ">", ">=", "<", "<=", "!=":
|
||||
var exprFunc func(iterator, interface{}, interface{}) interface{}
|
||||
switch root.Op {
|
||||
case "=":
|
||||
exprFunc = eqFunc
|
||||
case ">":
|
||||
exprFunc = gtFunc
|
||||
case ">=":
|
||||
exprFunc = geFunc
|
||||
case "<":
|
||||
exprFunc = ltFunc
|
||||
case "<=":
|
||||
exprFunc = leFunc
|
||||
case "!=":
|
||||
exprFunc = neFunc
|
||||
}
|
||||
qyOutput = &logicalQuery{Left: left, Right: right, Do: exprFunc}
|
||||
case "or", "and":
|
||||
isOr := false
|
||||
if root.Op == "or" {
|
||||
isOr = true
|
||||
}
|
||||
qyOutput = &booleanQuery{Left: left, Right: right, IsOr: isOr}
|
||||
case "|":
|
||||
qyOutput = &unionQuery{Left: left, Right: right}
|
||||
}
|
||||
return qyOutput, nil
|
||||
}
|
||||
|
||||
func (b *builder) processNode(root node) (q query, err error) {
|
||||
if b.depth = b.depth + 1; b.depth > 1024 {
|
||||
err = errors.New("the xpath expressions is too complex")
|
||||
return
|
||||
}
|
||||
|
||||
switch root.Type() {
|
||||
case nodeConstantOperand:
|
||||
n := root.(*operandNode)
|
||||
q = &constantQuery{Val: n.Val}
|
||||
case nodeRoot:
|
||||
q = &contextQuery{Root: true}
|
||||
case nodeAxis:
|
||||
q, err = b.processAxisNode(root.(*axisNode))
|
||||
b.firstInput = q
|
||||
case nodeFilter:
|
||||
q, err = b.processFilterNode(root.(*filterNode))
|
||||
case nodeFunction:
|
||||
q, err = b.processFunctionNode(root.(*functionNode))
|
||||
case nodeOperator:
|
||||
q, err = b.processOperatorNode(root.(*operatorNode))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// build builds a specified XPath expressions expr.
|
||||
func build(expr string) (q query, err error) {
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
switch x := e.(type) {
|
||||
case string:
|
||||
err = errors.New(x)
|
||||
case error:
|
||||
err = x
|
||||
default:
|
||||
err = errors.New("unknown panic")
|
||||
}
|
||||
}
|
||||
}()
|
||||
root := parse(expr)
|
||||
b := &builder{}
|
||||
return b.processNode(root)
|
||||
}
|
484
vendor/github.com/antchfx/xpath/func.go
generated
vendored
Normal file
484
vendor/github.com/antchfx/xpath/func.go
generated
vendored
Normal file
|
@ -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, "")
|
||||
}
|
||||
}
|
9
vendor/github.com/antchfx/xpath/func_go110.go
generated
vendored
Normal file
9
vendor/github.com/antchfx/xpath/func_go110.go
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
// +build go1.10
|
||||
|
||||
package xpath
|
||||
|
||||
import "math"
|
||||
|
||||
func round(f float64) int {
|
||||
return int(math.Round(f))
|
||||
}
|
15
vendor/github.com/antchfx/xpath/func_pre_go110.go
generated
vendored
Normal file
15
vendor/github.com/antchfx/xpath/func_pre_go110.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
// +build !go1.10
|
||||
|
||||
package xpath
|
||||
|
||||
import "math"
|
||||
|
||||
// math.Round() is supported by Go 1.10+,
|
||||
// This method just compatible for version <1.10.
|
||||
// https://github.com/golang/go/issues/20100
|
||||
func round(f float64) int {
|
||||
if math.Abs(f) < 0.5 {
|
||||
return 0
|
||||
}
|
||||
return int(f + math.Copysign(0.5, f))
|
||||
}
|
295
vendor/github.com/antchfx/xpath/operator.go
generated
vendored
Normal file
295
vendor/github.com/antchfx/xpath/operator.go
generated
vendored
Normal file
|
@ -0,0 +1,295 @@
|
|||
package xpath
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// The XPath number operator function list.
|
||||
|
||||
// valueType is a return value type.
|
||||
type valueType int
|
||||
|
||||
const (
|
||||
booleanType valueType = iota
|
||||
numberType
|
||||
stringType
|
||||
nodeSetType
|
||||
)
|
||||
|
||||
func getValueType(i interface{}) valueType {
|
||||
v := reflect.ValueOf(i)
|
||||
switch v.Kind() {
|
||||
case reflect.Float64:
|
||||
return numberType
|
||||
case reflect.String:
|
||||
return stringType
|
||||
case reflect.Bool:
|
||||
return booleanType
|
||||
default:
|
||||
if _, ok := i.(query); ok {
|
||||
return nodeSetType
|
||||
}
|
||||
}
|
||||
panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
|
||||
}
|
||||
|
||||
type logical func(iterator, string, interface{}, interface{}) bool
|
||||
|
||||
var logicalFuncs = [][]logical{
|
||||
{cmpBooleanBoolean, nil, nil, nil},
|
||||
{nil, cmpNumericNumeric, cmpNumericString, cmpNumericNodeSet},
|
||||
{nil, cmpStringNumeric, cmpStringString, cmpStringNodeSet},
|
||||
{nil, cmpNodeSetNumeric, cmpNodeSetString, cmpNodeSetNodeSet},
|
||||
}
|
||||
|
||||
// number vs number
|
||||
func cmpNumberNumberF(op string, a, b float64) bool {
|
||||
switch op {
|
||||
case "=":
|
||||
return a == b
|
||||
case ">":
|
||||
return a > b
|
||||
case "<":
|
||||
return a < b
|
||||
case ">=":
|
||||
return a >= b
|
||||
case "<=":
|
||||
return a <= b
|
||||
case "!=":
|
||||
return a != b
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// string vs string
|
||||
func cmpStringStringF(op string, a, b string) bool {
|
||||
switch op {
|
||||
case "=":
|
||||
return a == b
|
||||
case ">":
|
||||
return a > b
|
||||
case "<":
|
||||
return a < b
|
||||
case ">=":
|
||||
return a >= b
|
||||
case "<=":
|
||||
return a <= b
|
||||
case "!=":
|
||||
return a != b
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmpBooleanBooleanF(op string, a, b bool) bool {
|
||||
switch op {
|
||||
case "or":
|
||||
return a || b
|
||||
case "and":
|
||||
return a && b
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmpNumericNumeric(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(float64)
|
||||
b := n.(float64)
|
||||
return cmpNumberNumberF(op, a, b)
|
||||
}
|
||||
|
||||
func cmpNumericString(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(float64)
|
||||
b := n.(string)
|
||||
num, err := strconv.ParseFloat(b, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cmpNumberNumberF(op, a, num)
|
||||
}
|
||||
|
||||
func cmpNumericNodeSet(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(float64)
|
||||
b := n.(query)
|
||||
|
||||
for {
|
||||
node := b.Select(t)
|
||||
if node == nil {
|
||||
break
|
||||
}
|
||||
num, err := strconv.ParseFloat(node.Value(), 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if cmpNumberNumberF(op, a, num) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmpNodeSetNumeric(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(query)
|
||||
b := n.(float64)
|
||||
for {
|
||||
node := a.Select(t)
|
||||
if node == nil {
|
||||
break
|
||||
}
|
||||
num, err := strconv.ParseFloat(node.Value(), 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if cmpNumberNumberF(op, num, b) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmpNodeSetString(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(query)
|
||||
b := n.(string)
|
||||
for {
|
||||
node := a.Select(t)
|
||||
if node == nil {
|
||||
break
|
||||
}
|
||||
if cmpStringStringF(op, b, node.Value()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmpNodeSetNodeSet(t iterator, op string, m, n interface{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func cmpStringNumeric(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(string)
|
||||
b := n.(float64)
|
||||
num, err := strconv.ParseFloat(a, 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return cmpNumberNumberF(op, b, num)
|
||||
}
|
||||
|
||||
func cmpStringString(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(string)
|
||||
b := n.(string)
|
||||
return cmpStringStringF(op, a, b)
|
||||
}
|
||||
|
||||
func cmpStringNodeSet(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(string)
|
||||
b := n.(query)
|
||||
for {
|
||||
node := b.Select(t)
|
||||
if node == nil {
|
||||
break
|
||||
}
|
||||
if cmpStringStringF(op, a, node.Value()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cmpBooleanBoolean(t iterator, op string, m, n interface{}) bool {
|
||||
a := m.(bool)
|
||||
b := n.(bool)
|
||||
return cmpBooleanBooleanF(op, a, b)
|
||||
}
|
||||
|
||||
// eqFunc is an `=` operator.
|
||||
func eqFunc(t iterator, m, n interface{}) interface{} {
|
||||
t1 := getValueType(m)
|
||||
t2 := getValueType(n)
|
||||
return logicalFuncs[t1][t2](t, "=", m, n)
|
||||
}
|
||||
|
||||
// gtFunc is an `>` operator.
|
||||
func gtFunc(t iterator, m, n interface{}) interface{} {
|
||||
t1 := getValueType(m)
|
||||
t2 := getValueType(n)
|
||||
return logicalFuncs[t1][t2](t, ">", m, n)
|
||||
}
|
||||
|
||||
// geFunc is an `>=` operator.
|
||||
func geFunc(t iterator, m, n interface{}) interface{} {
|
||||
t1 := getValueType(m)
|
||||
t2 := getValueType(n)
|
||||
return logicalFuncs[t1][t2](t, ">=", m, n)
|
||||
}
|
||||
|
||||
// ltFunc is an `<` operator.
|
||||
func ltFunc(t iterator, m, n interface{}) interface{} {
|
||||
t1 := getValueType(m)
|
||||
t2 := getValueType(n)
|
||||
return logicalFuncs[t1][t2](t, "<", m, n)
|
||||
}
|
||||
|
||||
// leFunc is an `<=` operator.
|
||||
func leFunc(t iterator, m, n interface{}) interface{} {
|
||||
t1 := getValueType(m)
|
||||
t2 := getValueType(n)
|
||||
return logicalFuncs[t1][t2](t, "<=", m, n)
|
||||
}
|
||||
|
||||
// neFunc is an `!=` operator.
|
||||
func neFunc(t iterator, m, n interface{}) interface{} {
|
||||
t1 := getValueType(m)
|
||||
t2 := getValueType(n)
|
||||
return logicalFuncs[t1][t2](t, "!=", m, n)
|
||||
}
|
||||
|
||||
// orFunc is an `or` operator.
|
||||
var orFunc = func(t iterator, m, n interface{}) interface{} {
|
||||
t1 := getValueType(m)
|
||||
t2 := getValueType(n)
|
||||
return logicalFuncs[t1][t2](t, "or", m, n)
|
||||
}
|
||||
|
||||
func numericExpr(m, n interface{}, cb func(float64, float64) float64) float64 {
|
||||
typ := reflect.TypeOf(float64(0))
|
||||
a := reflect.ValueOf(m).Convert(typ)
|
||||
b := reflect.ValueOf(n).Convert(typ)
|
||||
return cb(a.Float(), b.Float())
|
||||
}
|
||||
|
||||
// plusFunc is an `+` operator.
|
||||
var plusFunc = func(m, n interface{}) interface{} {
|
||||
return numericExpr(m, n, func(a, b float64) float64 {
|
||||
return a + b
|
||||
})
|
||||
}
|
||||
|
||||
// minusFunc is an `-` operator.
|
||||
var minusFunc = func(m, n interface{}) interface{} {
|
||||
return numericExpr(m, n, func(a, b float64) float64 {
|
||||
return a - b
|
||||
})
|
||||
}
|
||||
|
||||
// mulFunc is an `*` operator.
|
||||
var mulFunc = func(m, n interface{}) interface{} {
|
||||
return numericExpr(m, n, func(a, b float64) float64 {
|
||||
return a * b
|
||||
})
|
||||
}
|
||||
|
||||
// divFunc is an `DIV` operator.
|
||||
var divFunc = func(m, n interface{}) interface{} {
|
||||
return numericExpr(m, n, func(a, b float64) float64 {
|
||||
return a / b
|
||||
})
|
||||
}
|
||||
|
||||
// modFunc is an 'MOD' operator.
|
||||
var modFunc = func(m, n interface{}) interface{} {
|
||||
return numericExpr(m, n, func(a, b float64) float64 {
|
||||
return float64(int(a) % int(b))
|
||||
})
|
||||
}
|
1186
vendor/github.com/antchfx/xpath/parse.go
generated
vendored
Normal file
1186
vendor/github.com/antchfx/xpath/parse.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
791
vendor/github.com/antchfx/xpath/query.go
generated
vendored
Normal file
791
vendor/github.com/antchfx/xpath/query.go
generated
vendored
Normal file
|
@ -0,0 +1,791 @@
|
|||
package xpath
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type iterator interface {
|
||||
Current() NodeNavigator
|
||||
}
|
||||
|
||||
// An XPath query interface.
|
||||
type query interface {
|
||||
// Select traversing iterator returns a query matched node NodeNavigator.
|
||||
Select(iterator) NodeNavigator
|
||||
|
||||
// Evaluate evaluates query and returns values of the current query.
|
||||
Evaluate(iterator) interface{}
|
||||
|
||||
Clone() query
|
||||
}
|
||||
|
||||
// contextQuery is returns current node on the iterator object query.
|
||||
type contextQuery struct {
|
||||
count int
|
||||
Root bool // Moving to root-level node in the current context iterator.
|
||||
}
|
||||
|
||||
func (c *contextQuery) Select(t iterator) (n NodeNavigator) {
|
||||
if c.count == 0 {
|
||||
c.count++
|
||||
n = t.Current().Copy()
|
||||
if c.Root {
|
||||
n.MoveToRoot()
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (c *contextQuery) Evaluate(iterator) interface{} {
|
||||
c.count = 0
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *contextQuery) Clone() query {
|
||||
return &contextQuery{count: 0, Root: c.Root}
|
||||
}
|
||||
|
||||
// ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
|
||||
type ancestorQuery struct {
|
||||
iterator func() NodeNavigator
|
||||
|
||||
Self bool
|
||||
Input query
|
||||
Predicate func(NodeNavigator) bool
|
||||
}
|
||||
|
||||
func (a *ancestorQuery) Select(t iterator) NodeNavigator {
|
||||
for {
|
||||
if a.iterator == nil {
|
||||
node := a.Input.Select(t)
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
first := true
|
||||
a.iterator = func() NodeNavigator {
|
||||
if first && a.Self {
|
||||
first = false
|
||||
if a.Predicate(node) {
|
||||
return node
|
||||
}
|
||||
}
|
||||
for node.MoveToParent() {
|
||||
if !a.Predicate(node) {
|
||||
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
|
||||
}
|
157
vendor/github.com/antchfx/xpath/xpath.go
generated
vendored
Normal file
157
vendor/github.com/antchfx/xpath/xpath.go
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
package xpath
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// NodeType represents a type of XPath node.
|
||||
type NodeType int
|
||||
|
||||
const (
|
||||
// RootNode is a root node of the XML document or node tree.
|
||||
RootNode NodeType = iota
|
||||
|
||||
// ElementNode is an element, such as <element>.
|
||||
ElementNode
|
||||
|
||||
// AttributeNode is an attribute, such as id='123'.
|
||||
AttributeNode
|
||||
|
||||
// TextNode is the text content of a node.
|
||||
TextNode
|
||||
|
||||
// CommentNode is a comment node, such as <!-- my comment -->
|
||||
CommentNode
|
||||
|
||||
// allNode is any types of node, used by xpath package only to predicate match.
|
||||
allNode
|
||||
)
|
||||
|
||||
// NodeNavigator provides cursor model for navigating XML data.
|
||||
type NodeNavigator interface {
|
||||
// NodeType returns the XPathNodeType of the current node.
|
||||
NodeType() NodeType
|
||||
|
||||
// LocalName gets the Name of the current node.
|
||||
LocalName() string
|
||||
|
||||
// Prefix returns namespace prefix associated with the current node.
|
||||
Prefix() string
|
||||
|
||||
// Value gets the value of current node.
|
||||
Value() string
|
||||
|
||||
// Copy does a deep copy of the NodeNavigator and all its components.
|
||||
Copy() NodeNavigator
|
||||
|
||||
// MoveToRoot moves the NodeNavigator to the root node of the current node.
|
||||
MoveToRoot()
|
||||
|
||||
// MoveToParent moves the NodeNavigator to the parent node of the current node.
|
||||
MoveToParent() bool
|
||||
|
||||
// MoveToNextAttribute moves the NodeNavigator to the next attribute on current node.
|
||||
MoveToNextAttribute() bool
|
||||
|
||||
// MoveToChild moves the NodeNavigator to the first child node of the current node.
|
||||
MoveToChild() bool
|
||||
|
||||
// MoveToFirst moves the NodeNavigator to the first sibling node of the current node.
|
||||
MoveToFirst() bool
|
||||
|
||||
// MoveToNext moves the NodeNavigator to the next sibling node of the current node.
|
||||
MoveToNext() bool
|
||||
|
||||
// MoveToPrevious moves the NodeNavigator to the previous sibling node of the current node.
|
||||
MoveToPrevious() bool
|
||||
|
||||
// MoveTo moves the NodeNavigator to the same position as the specified NodeNavigator.
|
||||
MoveTo(NodeNavigator) bool
|
||||
}
|
||||
|
||||
// NodeIterator holds all matched Node object.
|
||||
type NodeIterator struct {
|
||||
node NodeNavigator
|
||||
query query
|
||||
}
|
||||
|
||||
// Current returns current node which matched.
|
||||
func (t *NodeIterator) Current() NodeNavigator {
|
||||
return t.node
|
||||
}
|
||||
|
||||
// MoveNext moves Navigator to the next match node.
|
||||
func (t *NodeIterator) MoveNext() bool {
|
||||
n := t.query.Select(t)
|
||||
if n != nil {
|
||||
if !t.node.MoveTo(n) {
|
||||
t.node = n.Copy()
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Select selects a node set using the specified XPath expression.
|
||||
// This method is deprecated, recommend using Expr.Select() method instead.
|
||||
func Select(root NodeNavigator, expr string) *NodeIterator {
|
||||
exp, err := Compile(expr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return exp.Select(root)
|
||||
}
|
||||
|
||||
// Expr is an XPath expression for query.
|
||||
type Expr struct {
|
||||
s string
|
||||
q query
|
||||
}
|
||||
|
||||
type iteratorFunc func() NodeNavigator
|
||||
|
||||
func (f iteratorFunc) Current() NodeNavigator {
|
||||
return f()
|
||||
}
|
||||
|
||||
// Evaluate returns the result of the expression.
|
||||
// The result type of the expression is one of the follow: bool,float64,string,NodeIterator).
|
||||
func (expr *Expr) Evaluate(root NodeNavigator) interface{} {
|
||||
val := expr.q.Evaluate(iteratorFunc(func() NodeNavigator { return root }))
|
||||
switch val.(type) {
|
||||
case query:
|
||||
return &NodeIterator{query: expr.q.Clone(), node: root}
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
// Select selects a node set using the specified XPath expression.
|
||||
func (expr *Expr) Select(root NodeNavigator) *NodeIterator {
|
||||
return &NodeIterator{query: expr.q.Clone(), node: root}
|
||||
}
|
||||
|
||||
// String returns XPath expression string.
|
||||
func (expr *Expr) String() string {
|
||||
return expr.s
|
||||
}
|
||||
|
||||
// Compile compiles an XPath expression string.
|
||||
func Compile(expr string) (*Expr, error) {
|
||||
if expr == "" {
|
||||
return nil, errors.New("expr expression is nil")
|
||||
}
|
||||
qy, err := build(expr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Expr{s: expr, q: qy}, nil
|
||||
}
|
||||
|
||||
// MustCompile compiles an XPath expression string and ignored error.
|
||||
func MustCompile(expr string) *Expr {
|
||||
exp, err := Compile(expr)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return exp
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue