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"
|
revision = "a32116e4989e2b0e17c057ee378b4d5246add74e"
|
||||||
version = "v1.1.0"
|
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]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
digest = "1:c309b41787813f80ec393023471014b0be16346bba6e16bf5fa01ce1d310a4ea"
|
digest = "1:c309b41787813f80ec393023471014b0be16346bba6e16bf5fa01ce1d310a4ea"
|
||||||
|
@ -292,6 +308,7 @@
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
input-imports = [
|
input-imports = [
|
||||||
"github.com/adrianmo/go-nmea",
|
"github.com/adrianmo/go-nmea",
|
||||||
|
"github.com/antchfx/jsonquery",
|
||||||
"github.com/bettercap/gatt",
|
"github.com/bettercap/gatt",
|
||||||
"github.com/bettercap/nrf24",
|
"github.com/bettercap/nrf24",
|
||||||
"github.com/bettercap/readline",
|
"github.com/bettercap/readline",
|
||||||
|
|
|
@ -29,6 +29,7 @@ type EventsStream struct {
|
||||||
output *os.File
|
output *os.File
|
||||||
rotation rotation
|
rotation rotation
|
||||||
ignoreList *IgnoreList
|
ignoreList *IgnoreList
|
||||||
|
triggerList *TriggerList
|
||||||
waitFor string
|
waitFor string
|
||||||
waitChan chan *session.Event
|
waitChan chan *session.Event
|
||||||
eventListener <-chan session.Event
|
eventListener <-chan session.Event
|
||||||
|
@ -45,6 +46,7 @@ func NewEventsStream(s *session.Session) *EventsStream {
|
||||||
waitChan: make(chan *session.Event),
|
waitChan: make(chan *session.Event),
|
||||||
waitFor: "",
|
waitFor: "",
|
||||||
ignoreList: NewIgnoreList(),
|
ignoreList: NewIgnoreList(),
|
||||||
|
triggerList: NewTriggerList(),
|
||||||
}
|
}
|
||||||
|
|
||||||
mod.AddHandler(session.NewModuleHandler("events.stream on", "",
|
mod.AddHandler(session.NewModuleHandler("events.stream on", "",
|
||||||
|
@ -70,6 +72,38 @@ func NewEventsStream(s *session.Session) *EventsStream {
|
||||||
return mod.Show(limit)
|
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]*)`,
|
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.",
|
"Wait for an event with the given tag either forever or for a timeout in seconds.",
|
||||||
func(args []string) error {
|
func(args []string) error {
|
||||||
|
@ -242,6 +276,10 @@ func (mod *EventsStream) Start() error {
|
||||||
mod.View(e, true)
|
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:
|
case <-mod.quit:
|
||||||
return
|
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