updated dependencies
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Paul 2024-08-19 03:44:45 +02:00
parent 810583964c
commit 6758df2b1d
20 changed files with 1032 additions and 513 deletions

10
go.mod
View File

@ -1,13 +1,13 @@
module git.paulbsd.com/paulbsd/fuelprices
go 1.21
go 1.23
require (
github.com/antchfx/xmlquery v1.3.18
github.com/antchfx/xpath v1.2.5 // indirect
github.com/antchfx/xmlquery v1.4.1
github.com/antchfx/xpath v1.3.1 // indirect
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
golang.org/x/net v0.20.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/text v0.17.0 // indirect
gopkg.in/ini.v1 v1.67.0
)

8
go.sum
View File

@ -4,6 +4,8 @@ github.com/antchfx/xmlquery v1.3.15 h1:aJConNMi1sMha5G8YJoAIF5P+H+qG1L73bSItWHo8
github.com/antchfx/xmlquery v1.3.15/go.mod h1:zMDv5tIGjOxY/JCNNinnle7V/EwthZ5IT8eeCGJKRWA=
github.com/antchfx/xmlquery v1.3.18 h1:FSQ3wMuphnPPGJOFhvc+cRQ2CT/rUj4cyQXkJcjOwz0=
github.com/antchfx/xmlquery v1.3.18/go.mod h1:Afkq4JIeXut75taLSuI31ISJ/zeq+3jG7TunF7noreA=
github.com/antchfx/xmlquery v1.4.1 h1:YgpSwbeWvLp557YFTi8E3z6t6/hYjmFEtiEKbDfEbl0=
github.com/antchfx/xmlquery v1.4.1/go.mod h1:lKezcT8ELGt8kW5L+ckFMTbgdR61/odpPgDv8Gvi1fI=
github.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8=
github.com/antchfx/xpath v1.2.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
@ -11,6 +13,8 @@ github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY=
github.com/antchfx/xpath v1.2.4/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.2.5 h1:hqZ+wtQ+KIOV/S3bGZcIhpgYC26um2bZYP2KVGcR7VY=
github.com/antchfx/xpath v1.2.5/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/antchfx/xpath v1.3.1 h1:PNbFuUqHwWl0xRjvUPjJ95Agbmdj2uzzIwmQKgu4oCk=
github.com/antchfx/xpath v1.3.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -44,6 +48,8 @@ golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -73,6 +79,8 @@ golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

View File

@ -1,17 +0,0 @@
language: go
go:
- 1.9.x
- 1.12.x
- 1.13.x
- 1.14.x
- 1.15.x
install:
- go get golang.org/x/net/html/charset
- go get github.com/antchfx/xpath
- go get github.com/mattn/goveralls
- go get github.com/golang/groupcache
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

View File

@ -1,37 +1,33 @@
xmlquery
====
[![Build Status](https://travis-ci.org/antchfx/xmlquery.svg?branch=master)](https://travis-ci.org/antchfx/xmlquery)
[![Coverage Status](https://coveralls.io/repos/github/antchfx/xmlquery/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xmlquery?branch=master)
# xmlquery
[![Build Status](https://github.com/antchfx/xmlquery/actions/workflows/testing.yml/badge.svg)](https://github.com/antchfx/xmlquery/actions/workflows/testing.yml)
[![GoDoc](https://godoc.org/github.com/antchfx/xmlquery?status.svg)](https://godoc.org/github.com/antchfx/xmlquery)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xmlquery)](https://goreportcard.com/report/github.com/antchfx/xmlquery)
Overview
===
# Overview
`xmlquery` is an XPath query package for XML documents, allowing you to extract
`xmlquery` is an XPath query package for XML documents, allowing you to extract
data or evaluate from XML documents with an XPath expression.
`xmlquery` has a built-in query object caching feature that caches recently used
XPATH query strings. Enabling caching can avoid recompile XPath expression for
each query.
XPATH query strings. Enabling caching can avoid recompile XPath expression for
each query.
You can visit this page to learn about the supported XPath(1.0/2.0) syntax. https://github.com/antchfx/xpath
[htmlquery](https://github.com/antchfx/htmlquery) - Package for the HTML document query.
[htmlquery](https://github.com/antchfx/htmlquery) - Package for the HTML document query.
[xmlquery](https://github.com/antchfx/xmlquery) - Package for the XML document query.
[xmlquery](https://github.com/antchfx/xmlquery) - Package for the XML document query.
[jsonquery](https://github.com/antchfx/jsonquery) - Package for the JSON document query.
[jsonquery](https://github.com/antchfx/jsonquery) - Package for the JSON document query.
# Installation
Installation
====
```
$ go get github.com/antchfx/xmlquery
```
Quick Starts
===
# Quick Starts
```go
import (
@ -75,8 +71,7 @@ func main(){
}
```
Getting Started
===
# Getting Started
### Find specified XPath query.
@ -157,10 +152,17 @@ list := xmlquery.Find(doc, "//author")
book := xmlquery.FindOne(doc, "//book[2]")
```
#### Find all book elements and only get `id` attribute. (New Feature)
#### Find the last book.
```go
book := xmlquery.FindOne(doc, "//book[last()]")
```
#### Find all book elements and only get `id` attribute.
```go
list := xmlquery.Find(doc,"//book/@id")
fmt.Println(list[0].InnerText) // outout @id value
```
#### Find all books with id `bk104`.
@ -183,15 +185,21 @@ price := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64)
fmt.Printf("total price: %f\n", price)
```
#### Evaluate number of all book elements.
#### Count the number of books.
```go
expr, err := xpath.Compile("count(//book)")
count := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64)
```
#### Calculate the total price of all book prices.
```go
expr, err := xpath.Compile("sum(//book/price)")
price := expr.Evaluate(xmlquery.CreateXPathNavigator(doc)).(float64)
```
Advanced Features
====
# Advanced Features
### Parse `UTF-16` XML file with `ParseWithOptions()`.
@ -273,8 +281,7 @@ Output:
<?xml version="1.0"?><rss><channel><title>W3Schools Home Page</title></channel></rss>
```
FAQ
====
# FAQ
#### `Find()` vs `QueryAll()`, which is better?
@ -284,12 +291,12 @@ an error.
#### Can I save my query expression object for the next query?
Yes, you can. We provide `QuerySelector` and `QuerySelectorAll` methods; they
Yes, you can. We provide `QuerySelector` and `QuerySelectorAll` methods; they
accept your query expression object.
Caching a query expression object avoids recompiling the XPath query
Caching a query expression object avoids recompiling the XPath query
expression, improving query performance.
Questions
===
# Questions
Please let me know if you have any questions

View File

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

View File

@ -27,6 +27,8 @@ const (
CommentNode
// AttributeNode is an attribute of element.
AttributeNode
// NotationNode is a directive represents in document (for example, <!text...>).
NotationNode
)
type Attr struct {
@ -99,6 +101,10 @@ func newXMLName(name string) xml.Name {
}
}
func (n *Node) Level() int {
return n.level
}
// InnerText returns the text between the start and end tags of the object.
func (n *Node) InnerText() string {
var output func(*strings.Builder, *Node)
@ -153,6 +159,9 @@ func outputXML(b *strings.Builder, n *Node, preserveSpaces bool, config *outputC
b.WriteString("-->")
}
return
case NotationNode:
fmt.Fprintf(b, "<!%s>", n.Data)
return
case DeclarationNode:
b.WriteString("<?" + n.Data)
default:

View File

@ -8,6 +8,7 @@ import (
"net/http"
"regexp"
"strings"
"sync"
"github.com/antchfx/xpath"
"golang.org/x/net/html/charset"
@ -59,6 +60,13 @@ type parser struct {
streamNode *Node // Need to remember the last target node So we can clean it up upon next Read() call.
streamNodePrev *Node // Need to remember target node's prev so upon target node removal, we can restore correct prev.
reader *cachedReader // Need to maintain a reference to the reader, so we can determine whether a node contains CDATA.
once sync.Once
space2prefix map[string]*xmlnsPrefix
}
type xmlnsPrefix struct {
name string
level int
}
func createParser(r io.Reader) *parser {
@ -77,9 +85,11 @@ func createParser(r io.Reader) *parser {
}
func (p *parser) parse() (*Node, error) {
var streamElementNodeCounter int
space2prefix := map[string]string{"http://www.w3.org/XML/1998/namespace": "xml"}
p.once.Do(func() {
p.space2prefix = map[string]*xmlnsPrefix{"http://www.w3.org/XML/1998/namespace": {name: "xml", level: 0}}
})
var streamElementNodeCounter int
for {
p.reader.StartCaching()
tok, err := p.decoder.Token()
@ -108,16 +118,18 @@ func (p *parser) parse() (*Node, error) {
for _, att := range tok.Attr {
if att.Name.Local == "xmlns" {
space2prefix[att.Value] = "" // reset empty if exist the default namespace
// defaultNamespaceURL = att.Value
// https://github.com/antchfx/xmlquery/issues/67
if prefix, ok := p.space2prefix[att.Value]; !ok || (ok && prefix.level >= p.level) {
p.space2prefix[att.Value] = &xmlnsPrefix{name: "", level: p.level} // reset empty if exist the default namespace
}
} else if att.Name.Space == "xmlns" {
// maybe there are have duplicate NamespaceURL?
space2prefix[att.Value] = att.Name.Local
p.space2prefix[att.Value] = &xmlnsPrefix{name: att.Name.Local, level: p.level}
}
}
if space := tok.Name.Space; space != "" {
if _, found := space2prefix[space]; !found && p.decoder.Strict {
if _, found := p.space2prefix[space]; !found && p.decoder.Strict {
return nil, fmt.Errorf("xmlquery: invalid XML document, namespace %s is missing", space)
}
}
@ -125,8 +137,8 @@ func (p *parser) parse() (*Node, error) {
attributes := make([]Attr, len(tok.Attr))
for i, att := range tok.Attr {
name := att.Name
if prefix, ok := space2prefix[name.Space]; ok {
name.Space = prefix
if prefix, ok := p.space2prefix[name.Space]; ok {
name.Space = prefix.name
}
attributes[i] = Attr{
Name: name,
@ -155,10 +167,10 @@ func (p *parser) parse() (*Node, error) {
}
if node.NamespaceURI != "" {
if v, ok := space2prefix[node.NamespaceURI]; ok {
if v, ok := p.space2prefix[node.NamespaceURI]; ok {
cached := string(p.reader.Cache())
if strings.HasPrefix(cached, fmt.Sprintf("%s:%s", v, node.Data)) || strings.HasPrefix(cached, fmt.Sprintf("<%s:%s", v, node.Data)) {
node.Prefix = v
if strings.HasPrefix(cached, fmt.Sprintf("%s:%s", v.name, node.Data)) || strings.HasPrefix(cached, fmt.Sprintf("<%s:%s", v.name, node.Data)) {
node.Prefix = v.name
}
}
}
@ -269,6 +281,17 @@ func (p *parser) parse() (*Node, error) {
}
p.prev = node
case xml.Directive:
node := &Node{Type: NotationNode, Data: string(tok), level: p.level}
if p.level == p.prev.level {
AddSibling(p.prev, node)
} else if p.level > p.prev.level {
AddChild(p.prev, node)
} else if p.level < p.prev.level {
for i := p.prev.level - p.level; i > 1; i-- {
p.prev = p.prev.Parent
}
AddSibling(p.prev.Parent, node)
}
}
}
}
@ -285,37 +308,43 @@ type StreamParser struct {
// scenarios.
//
// Scenario 1: simple case:
// xml := `<AAA><BBB>b1</BBB><BBB>b2</BBB></AAA>`
// sp, err := CreateStreamParser(strings.NewReader(xml), "/AAA/BBB")
// if err != nil {
// panic(err)
// }
// for {
// n, err := sp.Read()
// if err != nil {
// break
// }
// fmt.Println(n.OutputXML(true))
// }
//
// xml := `<AAA><BBB>b1</BBB><BBB>b2</BBB></AAA>`
// sp, err := CreateStreamParser(strings.NewReader(xml), "/AAA/BBB")
// if err != nil {
// panic(err)
// }
// for {
// n, err := sp.Read()
// if err != nil {
// break
// }
// fmt.Println(n.OutputXML(true))
// }
//
// Output will be:
// <BBB>b1</BBB>
// <BBB>b2</BBB>
//
// <BBB>b1</BBB>
// <BBB>b2</BBB>
//
// Scenario 2: advanced case:
// xml := `<AAA><BBB>b1</BBB><BBB>b2</BBB></AAA>`
// sp, err := CreateStreamParser(strings.NewReader(xml), "/AAA/BBB", "/AAA/BBB[. != 'b1']")
// if err != nil {
// panic(err)
// }
// for {
// n, err := sp.Read()
// if err != nil {
// break
// }
// fmt.Println(n.OutputXML(true))
// }
//
// xml := `<AAA><BBB>b1</BBB><BBB>b2</BBB></AAA>`
// sp, err := CreateStreamParser(strings.NewReader(xml), "/AAA/BBB", "/AAA/BBB[. != 'b1']")
// if err != nil {
// panic(err)
// }
// for {
// n, err := sp.Read()
// if err != nil {
// break
// }
// fmt.Println(n.OutputXML(true))
// }
//
// Output will be:
// <BBB>b2</BBB>
//
// <BBB>b2</BBB>
//
// As the argument names indicate, streamElementXPath should be used for
// providing xpath query pointing to the target element node only, no extra

View File

@ -156,7 +156,7 @@ func (x *NodeNavigator) NodeType() xpath.NodeType {
switch x.curr.Type {
case CommentNode:
return xpath.CommentNode
case TextNode, CharDataNode:
case TextNode, CharDataNode, NotationNode:
return xpath.TextNode
case DeclarationNode, DocumentNode:
return xpath.RootNode

View File

@ -1,12 +0,0 @@
language: go
go:
- 1.6
- 1.9
- '1.10'
install:
- go get github.com/mattn/goveralls
script:
- $HOME/gopath/bin/goveralls -service=travis-ci

View File

@ -1,14 +1,13 @@
XPath
====
# XPath
[![GoDoc](https://godoc.org/github.com/antchfx/xpath?status.svg)](https://godoc.org/github.com/antchfx/xpath)
[![Coverage Status](https://coveralls.io/repos/github/antchfx/xpath/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xpath?branch=master)
[![Build Status](https://travis-ci.org/antchfx/xpath.svg?branch=master)](https://travis-ci.org/antchfx/xpath)
[![Build Status](https://github.com/antchfx/xpath/actions/workflows/testing.yml/badge.svg)](https://github.com/antchfx/xpath/actions/workflows/testing.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xpath)](https://goreportcard.com/report/github.com/antchfx/xpath)
XPath is Go package provides selecting nodes from XML, HTML or other documents using XPath expression.
Implementation
===
# Implementation
- [htmlquery](https://github.com/antchfx/htmlquery) - an XPath query package for HTML document
@ -16,8 +15,7 @@ Implementation
- [jsonquery](https://github.com/antchfx/jsonquery) - an XPath query package for JSON document
Supported Features
===
# Supported Features
#### The basic XPath patterns.
@ -63,11 +61,14 @@ Supported Features
- `child::*` : The child axis selects children of the current node.
- `child::node()`: Selects all the children of the context node.
- `child::text()`: Selects all text node children of the context 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 @*
- `attribute::*` : Selects attributes of the current element. It is equivalent to @\*
- `following-sibling::*` : Selects nodes after the current node.
@ -87,27 +88,27 @@ Supported Features
#### Expressions
The gxpath supported three types: number, boolean, string.
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` : 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` Unary minus
- `a + b` : Addition
- `a - b` : Subtraction
- `a * b` : Multiplication
- `a div b` : Division
- `a mod b` : Modulus (division remainder)
- `a or b` : Boolean `or` operation.
@ -117,49 +118,50 @@ Supported Features
- `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()`| ✓ |
`matches()`| ✓ |
`name()`| ✓ |
`namespace-uri()`| ✓ |
`normalize-space()`| ✓ |
`not()`| ✓ |
`number()`| ✓ |
`position()`| ✓ |
`replace()`| ✓ |
`reverse()`| ✓ |
`round()`| ✓ |
`starts-with()`| ✓ |
`string()`| ✓ |
`string-join()`[^1]| ✓ |
`string-length()`| ✓ |
`substring()`| ✓ |
`substring-after()`| ✓ |
`substring-before()`| ✓ |
`sum()`| ✓ |
`system-property()`| ✗ |
`translate()`| ✓ |
`true()`| ✓ |
`unparsed-entity-url()` | ✗ |
| 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()` | ✓ |
| `lower-case()`[^1] | ✓ |
| `matches()` | ✓ |
| `name()` | ✓ |
| `namespace-uri()` | ✓ |
| `normalize-space()` | ✓ |
| `not()` | ✓ |
| `number()` | ✓ |
| `position()` | ✓ |
| `replace()` | ✓ |
| `reverse()` | ✓ |
| `round()` | ✓ |
| `starts-with()` | ✓ |
| `string()` | ✓ |
| `string-join()`[^1] | ✓ |
| `string-length()` | ✓ |
| `substring()` | ✓ |
| `substring-after()` | ✓ |
| `substring-before()` | ✓ |
| `sum()` | ✓ |
| `system-property()` | ✗ |
| `translate()` | ✓ |
| `true()` | ✓ |
| `unparsed-entity-url()` | ✗ |
[^1]: XPath-2.0 expression
[^1]: XPath-2.0 expression

View File

@ -7,15 +7,39 @@ import (
type flag int
const (
noneFlag flag = iota
filterFlag
)
var flagsEnum = struct {
None flag
SmartDesc flag
PosFilter flag
Filter flag
Condition flag
}{
None: 0,
SmartDesc: 1,
PosFilter: 2,
Filter: 4,
Condition: 8,
}
type builderProp int
var builderProps = struct {
None builderProp
PosFilter builderProp
HasPosition builderProp
HasLast builderProp
NonFlat builderProp
}{
None: 0,
PosFilter: 1,
HasPosition: 2,
HasLast: 4,
NonFlat: 8,
}
// builder provides building an XPath expressions.
type builder struct {
depth int
flag flag
parseDepth int
firstInput query
}
@ -63,23 +87,26 @@ func axisPredicate(root *axisNode) func(NodeNavigator) bool {
return predicate
}
// processAxisNode processes a query for the XPath axis node.
func (b *builder) processAxisNode(root *axisNode) (query, error) {
// processAxis processes a query for the XPath axis node.
func (b *builder) processAxis(root *axisNode, flags flag, props *builderProp) (query, error) {
var (
err error
qyInput query
qyOutput query
predicate = axisPredicate(root)
err error
qyInput query
qyOutput query
)
b.firstInput = nil
predicate := axisPredicate(root)
if root.Input == nil {
qyInput = &contextQuery{}
*props = builderProps.None
} else {
inputFlags := flagsEnum.None
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)
qyGrandInput, _ = b.processNode(input.Input, flagsEnum.SmartDesc, props)
} else {
qyGrandInput = &contextQuery{}
}
@ -94,14 +121,14 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
}
return v
}
// fix `//*[contains(@id,"food")]//*[contains(@id,"food")]`, see https://github.com/antchfx/htmlquery/issues/52
// Skip the current node(Self:false) for the next descendants nodes.
_, ok := qyGrandInput.(*contextQuery)
qyOutput = &descendantQuery{Input: qyGrandInput, Predicate: filter, Self: ok}
qyOutput = &descendantQuery{name: root.LocalName, Input: qyGrandInput, Predicate: filter, Self: false}
*props |= builderProps.NonFlat
return qyOutput, nil
}
} else if ((flags & flagsEnum.Filter) == 0) && (root.AxeType == "descendant" || root.AxeType == "descendant-or-self") {
inputFlags |= flagsEnum.SmartDesc
}
qyInput, err = b.processNode(root.Input)
qyInput, err = b.processNode(root.Input, inputFlags, props)
if err != nil {
return nil, err
}
@ -109,11 +136,13 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
switch root.AxeType {
case "ancestor":
qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate}
qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
*props |= builderProps.NonFlat
case "ancestor-or-self":
qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate, Self: true}
qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true}
*props |= builderProps.NonFlat
case "attribute":
qyOutput = &attributeQuery{Input: qyInput, Predicate: predicate}
qyOutput = &attributeQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
case "child":
filter := func(n NodeNavigator) bool {
v := predicate(n)
@ -127,19 +156,35 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
}
return v
}
qyOutput = &childQuery{Input: qyInput, Predicate: filter}
if (*props & builderProps.NonFlat) == 0 {
qyOutput = &childQuery{name: root.LocalName, Input: qyInput, Predicate: filter}
} else {
qyOutput = &cachedChildQuery{name: root.LocalName, Input: qyInput, Predicate: filter}
}
case "descendant":
qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate}
if (flags & flagsEnum.SmartDesc) != flagsEnum.None {
qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: false, Predicate: predicate}
} else {
qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate}
}
*props |= builderProps.NonFlat
case "descendant-or-self":
qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate, Self: true}
if (flags & flagsEnum.SmartDesc) != flagsEnum.None {
qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: true, Predicate: predicate}
} else {
qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true}
}
*props |= builderProps.NonFlat
case "following":
qyOutput = &followingQuery{Input: qyInput, Predicate: predicate}
*props |= builderProps.NonFlat
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}
*props |= builderProps.NonFlat
case "preceding-sibling":
qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate, Sibling: true}
case "self":
@ -153,56 +198,182 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
return qyOutput, nil
}
// processFilterNode builds query for the XPath filter predicate.
func (b *builder) processFilterNode(root *filterNode) (query, error) {
b.flag |= filterFlag
func canBeNumber(q query) bool {
if q.ValueType() != xpathResultType.Any {
return q.ValueType() == xpathResultType.Number
}
return true
}
qyInput, err := b.processNode(root.Input)
// processFilterNode builds query for the XPath filter predicate.
func (b *builder) processFilter(root *filterNode, flags flag, props *builderProp) (query, error) {
first := (flags & flagsEnum.Filter) == 0
qyInput, err := b.processNode(root.Input, (flags | flagsEnum.Filter), props)
if err != nil {
return nil, err
}
qyCond, err := b.processNode(root.Condition)
firstInput := b.firstInput
var propsCond builderProp
cond, err := b.processNode(root.Condition, flags, &propsCond)
if err != nil {
return nil, err
}
qyOutput := &filterQuery{Input: qyInput, Predicate: qyCond}
return qyOutput, nil
// Checking whether is number
if canBeNumber(cond) || ((propsCond & (builderProps.HasPosition | builderProps.HasLast)) != 0) {
propsCond |= builderProps.HasPosition
flags |= flagsEnum.PosFilter
}
if root.Input.Type() != nodeFilter {
*props &= ^builderProps.PosFilter
}
if (propsCond & builderProps.HasPosition) != 0 {
*props |= builderProps.PosFilter
}
merge := (qyInput.Properties() & queryProps.Merge) != 0
if (propsCond & builderProps.HasPosition) != builderProps.None {
if (propsCond & builderProps.HasLast) != 0 {
// https://github.com/antchfx/xpath/issues/76
// https://github.com/antchfx/xpath/issues/78
if qyFunc, ok := cond.(*functionQuery); ok {
switch qyFunc.Input.(type) {
case *filterQuery:
cond = &lastQuery{Input: qyFunc.Input}
}
}
}
}
if first && firstInput != nil {
if merge && ((*props & builderProps.PosFilter) != 0) {
qyInput = &filterQuery{Input: qyInput, Predicate: cond, NoPosition: false}
var (
rootQuery = &contextQuery{}
parent query
)
switch axisQuery := firstInput.(type) {
case *ancestorQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *attributeQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *childQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *cachedChildQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *descendantQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *followingQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *precedingQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *parentQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *selfQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *groupQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
case *descendantOverDescendantQuery:
if _, ok := axisQuery.Input.(*contextQuery); !ok {
parent = axisQuery.Input
axisQuery.Input = rootQuery
}
}
b.firstInput = nil
if parent != nil {
return &mergeQuery{Input: parent, Child: qyInput}, nil
}
return qyInput, nil
}
b.firstInput = nil
}
resultQuery := &filterQuery{
Input: qyInput,
Predicate: cond,
NoPosition: (propsCond & builderProps.HasPosition) == 0,
}
return resultQuery, nil
}
// processFunctionNode processes query for the XPath function node.
func (b *builder) processFunctionNode(root *functionNode) (query, error) {
func (b *builder) processFunction(root *functionNode, props *builderProp) (query, error) {
// Reset builder props
*props = builderProps.None
var qyOutput query
switch root.FuncName {
case "lower-case":
arg, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: arg, Func: lowerCaseFunc}
case "starts-with":
arg1, err := b.processNode(root.Args[0])
arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
arg2, err := b.processNode(root.Args[1])
arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: startwithFunc(arg1, arg2)}
qyOutput = &functionQuery{Func: startwithFunc(arg1, arg2)}
case "ends-with":
arg1, err := b.processNode(root.Args[0])
arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
arg2, err := b.processNode(root.Args[1])
arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: endwithFunc(arg1, arg2)}
qyOutput = &functionQuery{Func: endwithFunc(arg1, arg2)}
case "contains":
arg1, err := b.processNode(root.Args[0])
arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
arg2, err := b.processNode(root.Args[1])
arg2, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: containsFunc(arg1, arg2)}
qyOutput = &functionQuery{Func: containsFunc(arg1, arg2)}
case "matches":
//matches(string , pattern)
if len(root.Args) != 2 {
@ -212,10 +383,10 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2 query
err error
)
if arg1, err = b.processNode(root.Args[0]); err != nil {
if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
if arg2, err = b.processNode(root.Args[1]); err != nil {
if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
// Issue #92, testing the regular expression before.
@ -224,7 +395,7 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
return nil, fmt.Errorf("matches() got error. %v", err)
}
}
qyOutput = &functionQuery{Input: b.firstInput, Func: matchesFunc(arg1, arg2)}
qyOutput = &functionQuery{Func: matchesFunc(arg1, arg2)}
case "substring":
//substring( string , start [, length] )
if len(root.Args) < 2 {
@ -234,18 +405,18 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query
err error
)
if arg1, err = b.processNode(root.Args[0]); err != nil {
if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
if arg2, err = b.processNode(root.Args[1]); err != nil {
if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
if len(root.Args) == 3 {
if arg3, err = b.processNode(root.Args[2]); err != nil {
if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err
}
}
qyOutput = &functionQuery{Input: b.firstInput, Func: substringFunc(arg1, arg2, arg3)}
qyOutput = &functionQuery{Func: substringFunc(arg1, arg2, arg3)}
case "substring-before", "substring-after":
//substring-xxxx( haystack, needle )
if len(root.Args) != 2 {
@ -255,31 +426,30 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2 query
err error
)
if arg1, err = b.processNode(root.Args[0]); err != nil {
if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
if arg2, err = b.processNode(root.Args[1]); err != nil {
if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
qyOutput = &functionQuery{
Input: b.firstInput,
Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"),
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])
arg1, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: stringLengthFunc(arg1)}
qyOutput = &functionQuery{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])
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@ -293,16 +463,16 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query
err error
)
if arg1, err = b.processNode(root.Args[0]); err != nil {
if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
if arg2, err = b.processNode(root.Args[1]); err != nil {
if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
if arg3, err = b.processNode(root.Args[2]); err != nil {
if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: replaceFunc(arg1, arg2, arg3)}
qyOutput = &functionQuery{Func: replaceFunc(arg1, arg2, arg3)}
case "translate":
//translate( string , string, string )
if len(root.Args) != 3 {
@ -312,21 +482,21 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
arg1, arg2, arg3 query
err error
)
if arg1, err = b.processNode(root.Args[0]); err != nil {
if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil {
return nil, err
}
if arg2, err = b.processNode(root.Args[1]); err != nil {
if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil {
return nil, err
}
if arg3, err = b.processNode(root.Args[2]); err != nil {
if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil {
return nil, err
}
qyOutput = &functionQuery{Input: b.firstInput, Func: translateFunc(arg1, arg2, arg3)}
qyOutput = &functionQuery{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])
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@ -340,46 +510,46 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
err error
)
if len(root.Args) == 1 {
arg, err = b.processNode(root.Args[0])
arg, err = b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
}
switch root.FuncName {
case "name":
qyOutput = &functionQuery{Input: b.firstInput, Func: nameFunc(arg)}
qyOutput = &functionQuery{Func: nameFunc(arg)}
case "local-name":
qyOutput = &functionQuery{Input: b.firstInput, Func: localNameFunc(arg)}
qyOutput = &functionQuery{Func: localNameFunc(arg)}
case "namespace-uri":
qyOutput = &functionQuery{Input: b.firstInput, Func: namespaceFunc(arg)}
qyOutput = &functionQuery{Func: namespaceFunc(arg)}
}
case "true", "false":
val := root.FuncName == "true"
qyOutput = &functionQuery{
Input: b.firstInput,
Func: func(_ query, _ iterator) interface{} {
return val
},
}
case "last":
switch typ := b.firstInput.(type) {
case *groupQuery, *filterQuery:
// https://github.com/antchfx/xpath/issues/76
// https://github.com/antchfx/xpath/issues/78
qyOutput = &lastQuery{Input: typ}
default:
qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc}
}
//switch typ := b.firstInput.(type) {
//case *groupQuery, *filterQuery:
// https://github.com/antchfx/xpath/issues/76
// https://github.com/antchfx/xpath/issues/78
//qyOutput = &lastQuery{Input: typ}
//default:
qyOutput = &functionQuery{Func: lastFunc}
//}
*props |= builderProps.HasLast
case "position":
qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc}
qyOutput = &functionQuery{Func: positionFunc}
*props |= builderProps.HasPosition
case "boolean", "number", "string":
inp := b.firstInput
var inp query
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])
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@ -396,13 +566,10 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
}
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])
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@ -411,7 +578,7 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
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])
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@ -420,7 +587,7 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
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])
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@ -440,18 +607,18 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
}
var args []query
for _, v := range root.Args {
q, err := b.processNode(v)
q, err := b.processNode(v, flagsEnum.None, props)
if err != nil {
return nil, err
}
args = append(args, q)
}
qyOutput = &functionQuery{Input: b.firstInput, Func: concatFunc(args...)}
qyOutput = &functionQuery{Func: concatFunc(args...)}
case "reverse":
if len(root.Args) == 0 {
return nil, fmt.Errorf("xpath: reverse(node-sets) function must with have parameters node-sets")
}
argQuery, err := b.processNode(root.Args[0])
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
@ -460,11 +627,11 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
if len(root.Args) != 2 {
return nil, fmt.Errorf("xpath: string-join(node-sets, separator) function requires node-set and argument")
}
argQuery, err := b.processNode(root.Args[0])
argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props)
if err != nil {
return nil, err
}
arg1, err := b.processNode(root.Args[1])
arg1, err := b.processNode(root.Args[1], flagsEnum.None, props)
if err != nil {
return nil, err
}
@ -472,22 +639,33 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
default:
return nil, fmt.Errorf("not yet support this function %s()", root.FuncName)
}
if funcQuery, ok := qyOutput.(*functionQuery); ok && funcQuery.Input == nil {
funcQuery.Input = b.firstInput
}
return qyOutput, nil
}
func (b *builder) processOperatorNode(root *operatorNode) (query, error) {
left, err := b.processNode(root.Left)
func (b *builder) processOperator(root *operatorNode, props *builderProp) (query, error) {
var (
leftProp builderProp
rightProp builderProp
)
left, err := b.processNode(root.Left, flagsEnum.None, &leftProp)
if err != nil {
return nil, err
}
right, err := b.processNode(root.Right)
right, err := b.processNode(root.Right, flagsEnum.None, &rightProp)
if err != nil {
return nil, err
}
*props = leftProp | rightProp
var qyOutput query
switch root.Op {
case "+", "-", "*", "div", "mod": // Numeric operator
var exprFunc func(interface{}, interface{}) interface{}
var exprFunc func(iterator, interface{}, interface{}) interface{}
switch root.Op {
case "+":
exprFunc = plusFunc
@ -525,41 +703,45 @@ func (b *builder) processOperatorNode(root *operatorNode) (query, error) {
}
qyOutput = &booleanQuery{Left: left, Right: right, IsOr: isOr}
case "|":
*props |= builderProps.NonFlat
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 {
func (b *builder) processNode(root node, flags flag, props *builderProp) (q query, err error) {
if b.parseDepth = b.parseDepth + 1; b.parseDepth > 1024 {
err = errors.New("the xpath expressions is too complex")
return
}
*props = builderProps.None
switch root.Type() {
case nodeConstantOperand:
n := root.(*operandNode)
q = &constantQuery{Val: n.Val}
case nodeRoot:
q = &contextQuery{Root: true}
q = &absoluteQuery{}
case nodeAxis:
q, err = b.processAxisNode(root.(*axisNode))
q, err = b.processAxis(root.(*axisNode), flags, props)
b.firstInput = q
case nodeFilter:
q, err = b.processFilterNode(root.(*filterNode))
q, err = b.processFilter(root.(*filterNode), flags, props)
b.firstInput = q
case nodeFunction:
q, err = b.processFunctionNode(root.(*functionNode))
q, err = b.processFunction(root.(*functionNode), props)
case nodeOperator:
q, err = b.processOperatorNode(root.(*operatorNode))
q, err = b.processOperator(root.(*operatorNode), props)
case nodeGroup:
q, err = b.processNode(root.(*groupNode).Input)
q, err = b.processNode(root.(*groupNode).Input, flagsEnum.None, props)
if err != nil {
return
}
q = &groupQuery{Input: q}
b.firstInput = q
if b.firstInput == nil {
b.firstInput = q
}
}
b.parseDepth--
return
}
@ -579,5 +761,6 @@ func build(expr string, namespaces map[string]string) (q query, err error) {
}()
root := parse(expr, namespaces)
b := &builder{}
return b.processNode(root)
props := builderProps.None
return b.processNode(root, flagsEnum.None, &props)
}

View File

@ -113,7 +113,7 @@ func asNumber(t iterator, o interface{}) float64 {
case query:
node := typ.Select(t)
if node == nil {
return float64(0)
return math.NaN()
}
if v, err := strconv.ParseFloat(node.Value(), 64); err == nil {
return v
@ -645,3 +645,9 @@ func stringJoinFunc(arg1 query) func(query, iterator) interface{} {
return strings.Join(parts, separator)
}
}
// lower-case is XPATH function that converts a string to lower case.
func lowerCaseFunc(q query, t iterator) interface{} {
v := functionArgs(q).Evaluate(t)
return strings.ToLower(asString(t, v))
}

View File

@ -1,40 +1,11 @@
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{
@ -228,91 +199,90 @@ func cmpBooleanBoolean(t iterator, op string, m, n interface{}) bool {
// eqFunc is an `=` operator.
func eqFunc(t iterator, m, n interface{}) interface{} {
t1 := getValueType(m)
t2 := getValueType(n)
t1 := getXPathType(m)
t2 := getXPathType(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)
t1 := getXPathType(m)
t2 := getXPathType(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)
t1 := getXPathType(m)
t2 := getXPathType(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)
t1 := getXPathType(m)
t2 := getXPathType(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)
t1 := getXPathType(m)
t2 := getXPathType(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)
t1 := getXPathType(m)
t2 := getXPathType(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)
t1 := getXPathType(m)
t2 := getXPathType(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())
func numericExpr(t iterator, m, n interface{}, cb func(float64, float64) float64) float64 {
a := asNumber(t, m)
b := asNumber(t, n)
return cb(a, b)
}
// plusFunc is an `+` operator.
var plusFunc = func(m, n interface{}) interface{} {
return numericExpr(m, n, func(a, b float64) float64 {
var plusFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(t, 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 {
var minusFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(t, 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 {
var mulFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(t, 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 {
var divFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(t, 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 {
var modFunc = func(t iterator, m, n interface{}) interface{} {
return numericExpr(t, m, n, func(a, b float64) float64 {
return float64(int(a) % int(b))
})
}

View File

@ -7,6 +7,44 @@ import (
"reflect"
)
// The return type of the XPath expression.
type resultType int
var xpathResultType = struct {
Boolean resultType
// A numeric value
Number resultType
String resultType
// A node collection.
NodeSet resultType
// Any of the XPath node types.
Any resultType
}{
Boolean: 0,
Number: 1,
String: 2,
NodeSet: 3,
Any: 4,
}
type queryProp int
var queryProps = struct {
None queryProp
Position queryProp
Count queryProp
Cached queryProp
Reverse queryProp
Merge queryProp
}{
None: 0,
Position: 1,
Count: 2,
Cached: 4,
Reverse: 8,
Merge: 16,
}
type iterator interface {
Current() NodeNavigator
}
@ -20,12 +58,15 @@ type query interface {
Evaluate(iterator) interface{}
Clone() query
// ValueType returns the value type of the current query.
ValueType() resultType
Properties() queryProp
}
// nopQuery is an empty query that always return nil for any query.
type nopQuery struct {
query
}
type nopQuery struct{}
func (nopQuery) Select(iterator) NodeNavigator { return nil }
@ -33,21 +74,23 @@ func (nopQuery) Evaluate(iterator) interface{} { return nil }
func (nopQuery) Clone() query { return nopQuery{} }
func (nopQuery) ValueType() resultType { return xpathResultType.NodeSet }
func (nopQuery) Properties() queryProp {
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}
// 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()
}
func (c *contextQuery) Select(t iterator) NodeNavigator {
if c.count > 0 {
return nil
}
return n
c.count++
return t.Current().Copy()
}
func (c *contextQuery) Evaluate(iterator) interface{} {
@ -56,12 +99,53 @@ func (c *contextQuery) Evaluate(iterator) interface{} {
}
func (c *contextQuery) Clone() query {
return &contextQuery{Root: c.Root}
return &contextQuery{}
}
func (c *contextQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (c *contextQuery) Properties() queryProp {
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}
type absoluteQuery struct {
count int
}
func (a *absoluteQuery) Select(t iterator) (n NodeNavigator) {
if a.count > 0 {
return
}
a.count++
n = t.Current().Copy()
n.MoveToRoot()
return
}
func (a *absoluteQuery) Evaluate(t iterator) interface{} {
a.count = 0
return a
}
func (a *absoluteQuery) Clone() query {
return &absoluteQuery{}
}
func (a *absoluteQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (a *absoluteQuery) Properties() queryProp {
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
}
// ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
type ancestorQuery struct {
name string
iterator func() NodeNavigator
table map[uint64]bool
Self bool
Input query
@ -69,6 +153,10 @@ type ancestorQuery struct {
}
func (a *ancestorQuery) Select(t iterator) NodeNavigator {
if a.table == nil {
a.table = make(map[uint64]bool)
}
for {
if a.iterator == nil {
node := a.Input.Select(t)
@ -78,24 +166,27 @@ func (a *ancestorQuery) Select(t iterator) NodeNavigator {
first := true
node = node.Copy()
a.iterator = func() NodeNavigator {
if first && a.Self {
if first {
first = false
if a.Predicate(node) {
if a.Self && a.Predicate(node) {
return node
}
}
for node.MoveToParent() {
if !a.Predicate(node) {
continue
if a.Predicate(node) {
return node
}
return node
}
return nil
}
}
if node := a.iterator(); node != nil {
return node
for node := a.iterator(); node != nil; node = a.iterator() {
node_id := getHashCode(node.Copy())
if _, ok := a.table[node_id]; !ok {
a.table[node_id] = true
return node
}
}
a.iterator = nil
}
@ -112,11 +203,20 @@ func (a *ancestorQuery) Test(n NodeNavigator) bool {
}
func (a *ancestorQuery) Clone() query {
return &ancestorQuery{Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate}
return &ancestorQuery{name: a.name, Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate}
}
func (a *ancestorQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (a *ancestorQuery) Properties() queryProp {
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge | queryProps.Reverse
}
// attributeQuery is an XPath attribute node query.(@*)
type attributeQuery struct {
name string
iterator func() NodeNavigator
Input query
@ -162,11 +262,20 @@ func (a *attributeQuery) Test(n NodeNavigator) bool {
}
func (a *attributeQuery) Clone() query {
return &attributeQuery{Input: a.Input.Clone(), Predicate: a.Predicate}
return &attributeQuery{name: a.name, Input: a.Input.Clone(), Predicate: a.Predicate}
}
func (a *attributeQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (a *attributeQuery) Properties() queryProp {
return queryProps.Merge
}
// childQuery is an XPath child node query.(child::*)
type childQuery struct {
name string
posit int
iterator func() NodeNavigator
@ -216,7 +325,15 @@ func (c *childQuery) Test(n NodeNavigator) bool {
}
func (c *childQuery) Clone() query {
return &childQuery{Input: c.Input.Clone(), Predicate: c.Predicate}
return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
}
func (c *childQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (c *childQuery) Properties() queryProp {
return queryProps.Merge
}
// position returns a position of current NodeNavigator.
@ -224,8 +341,75 @@ func (c *childQuery) position() int {
return c.posit
}
type cachedChildQuery struct {
name string
posit int
iterator func() NodeNavigator
Input query
Predicate func(NodeNavigator) bool
}
func (c *cachedChildQuery) 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 *cachedChildQuery) Evaluate(t iterator) interface{} {
c.Input.Evaluate(t)
c.iterator = nil
return c
}
func (c *cachedChildQuery) position() int {
return c.posit
}
func (c *cachedChildQuery) Test(n NodeNavigator) bool {
return c.Predicate(n)
}
func (c *cachedChildQuery) Clone() query {
return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
}
func (c *cachedChildQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (c *cachedChildQuery) Properties() queryProp {
return queryProps.Merge
}
// descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*)
type descendantQuery struct {
name string
iterator func() NodeNavigator
posit int
level int
@ -245,14 +429,11 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
}
node = node.Copy()
d.level = 0
positmap := make(map[int]int)
first := true
d.iterator = func() NodeNavigator {
if first && d.Self {
if first {
first = false
if d.Predicate(node) {
d.posit = 1
positmap[d.level] = 1
if d.Self && d.Predicate(node) {
return node
}
}
@ -260,7 +441,6 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
for {
if node.MoveToChild() {
d.level = d.level + 1
positmap[d.level] = 0
} else {
for {
if d.level == 0 {
@ -274,8 +454,6 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
}
}
if d.Predicate(node) {
positmap[d.level]++
d.posit = positmap[d.level]
return node
}
}
@ -283,6 +461,7 @@ func (d *descendantQuery) Select(t iterator) NodeNavigator {
}
if node := d.iterator(); node != nil {
d.posit++
return node
}
d.iterator = nil
@ -309,7 +488,15 @@ func (d *descendantQuery) depth() int {
}
func (d *descendantQuery) Clone() query {
return &descendantQuery{Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate}
return &descendantQuery{name: d.name, Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate}
}
func (d *descendantQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (d *descendantQuery) Properties() queryProp {
return queryProps.Merge
}
// followingQuery is an XPath following node query.(following::*|following-sibling::*)
@ -390,6 +577,14 @@ func (f *followingQuery) Clone() query {
return &followingQuery{Input: f.Input.Clone(), Sibling: f.Sibling, Predicate: f.Predicate}
}
func (f *followingQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (f *followingQuery) Properties() queryProp {
return queryProps.Merge
}
func (f *followingQuery) position() int {
return f.posit
}
@ -471,6 +666,14 @@ func (p *precedingQuery) Clone() query {
return &precedingQuery{Input: p.Input.Clone(), Sibling: p.Sibling, Predicate: p.Predicate}
}
func (p *precedingQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (p *precedingQuery) Properties() queryProp {
return queryProps.Merge | queryProps.Reverse
}
func (p *precedingQuery) position() int {
return p.posit
}
@ -503,6 +706,14 @@ func (p *parentQuery) Clone() query {
return &parentQuery{Input: p.Input.Clone(), Predicate: p.Predicate}
}
func (p *parentQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (p *parentQuery) Properties() queryProp {
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}
func (p *parentQuery) Test(n NodeNavigator) bool {
return p.Predicate(n)
}
@ -539,12 +750,22 @@ func (s *selfQuery) Clone() query {
return &selfQuery{Input: s.Input.Clone(), Predicate: s.Predicate}
}
func (s *selfQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (s *selfQuery) Properties() queryProp {
return queryProps.Merge
}
// filterQuery is an XPath query for predicate filter.
type filterQuery struct {
Input query
Predicate query
posit int
positmap map[int]int
Input query
Predicate query
NoPosition bool
posit int
positmap map[int]int
}
func (f *filterQuery) do(t iterator) bool {
@ -602,6 +823,14 @@ func (f *filterQuery) Clone() query {
return &filterQuery{Input: f.Input.Clone(), Predicate: f.Predicate.Clone()}
}
func (f *filterQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (f *filterQuery) Properties() queryProp {
return (queryProps.Position | f.Input.Properties()) & (queryProps.Reverse | queryProps.Merge)
}
// functionQuery is an XPath function that returns a computed value for
// the Evaluate call of the current NodeNavigator node. Select call isn't
// applicable for functionQuery.
@ -624,6 +853,14 @@ func (f *functionQuery) Clone() query {
return &functionQuery{Input: f.Input.Clone(), Func: f.Func}
}
func (f *functionQuery) ValueType() resultType {
return xpathResultType.Any
}
func (f *functionQuery) Properties() queryProp {
return queryProps.Merge
}
// transformFunctionQuery diffs from functionQuery where the latter computes a scalar
// value (number,string,boolean) for the current NodeNavigator node while the former
// (transformFunctionQuery) performs a mapping or transform of the current NodeNavigator
@ -652,6 +889,14 @@ func (f *transformFunctionQuery) Clone() query {
return &transformFunctionQuery{Input: f.Input.Clone(), Func: f.Func}
}
func (f *transformFunctionQuery) ValueType() resultType {
return xpathResultType.Any
}
func (f *transformFunctionQuery) Properties() queryProp {
return queryProps.Merge
}
// constantQuery is an XPath constant operand.
type constantQuery struct {
Val interface{}
@ -669,6 +914,14 @@ func (c *constantQuery) Clone() query {
return c
}
func (c *constantQuery) ValueType() resultType {
return getXPathType(c.Val)
}
func (c *constantQuery) Properties() queryProp {
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}
type groupQuery struct {
posit int
@ -692,6 +945,14 @@ func (g *groupQuery) Clone() query {
return &groupQuery{Input: g.Input.Clone()}
}
func (g *groupQuery) ValueType() resultType {
return g.Input.ValueType()
}
func (g *groupQuery) Properties() queryProp {
return queryProps.Position
}
func (g *groupQuery) position() int {
return g.posit
}
@ -726,11 +987,19 @@ func (l *logicalQuery) Clone() query {
return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do}
}
func (l *logicalQuery) ValueType() resultType {
return xpathResultType.Boolean
}
func (l *logicalQuery) Properties() queryProp {
return queryProps.Merge
}
// numericQuery is an XPath numeric operator expression.
type numericQuery struct {
Left, Right query
Do func(interface{}, interface{}) interface{}
Do func(iterator, interface{}, interface{}) interface{}
}
func (n *numericQuery) Select(t iterator) NodeNavigator {
@ -740,13 +1009,21 @@ func (n *numericQuery) Select(t iterator) NodeNavigator {
func (n *numericQuery) Evaluate(t iterator) interface{} {
m := n.Left.Evaluate(t)
k := n.Right.Evaluate(t)
return n.Do(m, k)
return n.Do(t, m, k)
}
func (n *numericQuery) Clone() query {
return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do}
}
func (n *numericQuery) ValueType() resultType {
return xpathResultType.Number
}
func (n *numericQuery) Properties() queryProp {
return queryProps.Merge
}
type booleanQuery struct {
IsOr bool
Left, Right query
@ -837,6 +1114,14 @@ func (b *booleanQuery) Clone() query {
return &booleanQuery{IsOr: b.IsOr, Left: b.Left.Clone(), Right: b.Right.Clone()}
}
func (b *booleanQuery) ValueType() resultType {
return xpathResultType.Boolean
}
func (b *booleanQuery) Properties() queryProp {
return queryProps.Merge
}
type unionQuery struct {
Left, Right query
iterator func() NodeNavigator
@ -894,6 +1179,14 @@ func (u *unionQuery) Clone() query {
return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()}
}
func (u *unionQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (u *unionQuery) Properties() queryProp {
return queryProps.Merge
}
type lastQuery struct {
buffer []NodeNavigator
counted bool
@ -923,6 +1216,147 @@ func (q *lastQuery) Clone() query {
return &lastQuery{Input: q.Input.Clone()}
}
func (q *lastQuery) ValueType() resultType {
return xpathResultType.Number
}
func (q *lastQuery) Properties() queryProp {
return queryProps.Merge
}
type descendantOverDescendantQuery struct {
name string
level int
posit int
currentNode NodeNavigator
Input query
MatchSelf bool
Predicate func(NodeNavigator) bool
}
func (d *descendantOverDescendantQuery) moveToFirstChild() bool {
if d.currentNode.MoveToChild() {
d.level++
return true
}
return false
}
func (d *descendantOverDescendantQuery) moveUpUntilNext() bool {
for !d.currentNode.MoveToNext() {
d.level--
if d.level == 0 {
return false
}
d.currentNode.MoveToParent()
}
return true
}
func (d *descendantOverDescendantQuery) Select(t iterator) NodeNavigator {
for {
if d.level == 0 {
node := d.Input.Select(t)
if node == nil {
return nil
}
d.currentNode = node.Copy()
d.posit = 0
if d.MatchSelf && d.Predicate(d.currentNode) {
d.posit = 1
return d.currentNode
}
d.moveToFirstChild()
} else if !d.moveUpUntilNext() {
continue
}
for ok := true; ok; ok = d.moveToFirstChild() {
if d.Predicate(d.currentNode) {
d.posit++
return d.currentNode
}
}
}
}
func (d *descendantOverDescendantQuery) Evaluate(t iterator) interface{} {
d.Input.Evaluate(t)
return d
}
func (d *descendantOverDescendantQuery) Clone() query {
return &descendantOverDescendantQuery{Input: d.Input.Clone(), Predicate: d.Predicate, MatchSelf: d.MatchSelf}
}
func (d *descendantOverDescendantQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (d *descendantOverDescendantQuery) Properties() queryProp {
return queryProps.Merge
}
func (d *descendantOverDescendantQuery) position() int {
return d.posit
}
type mergeQuery struct {
Input query
Child query
iterator func() NodeNavigator
}
func (m *mergeQuery) Select(t iterator) NodeNavigator {
for {
if m.iterator == nil {
root := m.Input.Select(t)
if root == nil {
return nil
}
m.Child.Evaluate(t)
root = root.Copy()
t.Current().MoveTo(root)
var list []NodeNavigator
for node := m.Child.Select(t); node != nil; node = m.Child.Select(t) {
list = append(list, node.Copy())
}
i := 0
m.iterator = func() NodeNavigator {
if i >= len(list) {
return nil
}
result := list[i]
i++
return result
}
}
if node := m.iterator(); node != nil {
return node
}
m.iterator = nil
}
}
func (m *mergeQuery) Evaluate(t iterator) interface{} {
m.Input.Evaluate(t)
return m
}
func (m *mergeQuery) Clone() query {
return &mergeQuery{Input: m.Input.Clone(), Child: m.Child.Clone()}
}
func (m *mergeQuery) ValueType() resultType {
return xpathResultType.NodeSet
}
func (m *mergeQuery) Properties() queryProp {
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
}
func getHashCode(n NodeNavigator) uint64 {
var sb bytes.Buffer
switch n.NodeType() {
@ -958,7 +1392,7 @@ func getHashCode(n NodeNavigator) uint64 {
}
}
h := fnv.New64a()
h.Write([]byte(sb.String()))
h.Write(sb.Bytes())
return h.Sum64()
}
@ -981,3 +1415,20 @@ func getNodeDepth(q query) int {
}
return 0
}
func getXPathType(i interface{}) resultType {
v := reflect.ValueOf(i)
switch v.Kind() {
case reflect.Float64:
return xpathResultType.Number
case reflect.String:
return xpathResultType.String
case reflect.Bool:
return xpathResultType.Boolean
default:
if _, ok := i.(query); ok {
return xpathResultType.NodeSet
}
}
panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
}

View File

@ -84,13 +84,13 @@ func (t *NodeIterator) Current() NodeNavigator {
// 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
if n == nil {
return false
}
return false
if !t.node.MoveTo(n) {
t.node = n.Copy()
}
return true
}
// Select selects a node set using the specified XPath expression.

4
vendor/golang.org/x/net/LICENSE generated vendored
View File

@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

View File

@ -104,7 +104,7 @@ tokenization, and tokenization and tree construction stages of the WHATWG HTML
parsing specification respectively. While the tokenizer parses and normalizes
individual HTML tokens, only the parser constructs the DOM tree from the
tokenized HTML, as described in the tree construction stage of the
specification, dynamically modifying or extending the docuemnt's DOM tree.
specification, dynamically modifying or extending the document's DOM tree.
If your use case requires semantically well-formed HTML documents, as defined by
the WHATWG specification, the parser should be used rather than the tokenizer.

View File

@ -910,9 +910,6 @@ func (z *Tokenizer) readTagAttrKey() {
return
}
switch c {
case ' ', '\n', '\r', '\t', '\f', '/':
z.pendingAttr[0].end = z.raw.end - 1
return
case '=':
if z.pendingAttr[0].start+1 == z.raw.end {
// WHATWG 13.2.5.32, if we see an equals sign before the attribute name
@ -920,7 +917,9 @@ func (z *Tokenizer) readTagAttrKey() {
continue
}
fallthrough
case '>':
case ' ', '\n', '\r', '\t', '\f', '/', '>':
// WHATWG 13.2.5.33 Attribute name state
// We need to reconsume the char in the after attribute name state to support the / character
z.raw.end--
z.pendingAttr[0].end = z.raw.end
return
@ -939,6 +938,11 @@ func (z *Tokenizer) readTagAttrVal() {
if z.err != nil {
return
}
if c == '/' {
// WHATWG 13.2.5.34 After attribute name state
// U+002F SOLIDUS (/) - Switch to the self-closing start tag state.
return
}
if c != '=' {
z.raw.end--
return

4
vendor/golang.org/x/text/LICENSE generated vendored
View File

@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

8
vendor/modules.txt vendored
View File

@ -1,7 +1,7 @@
# github.com/antchfx/xmlquery v1.3.18
# github.com/antchfx/xmlquery v1.4.1
## explicit; go 1.14
github.com/antchfx/xmlquery
# github.com/antchfx/xpath v1.2.5
# github.com/antchfx/xpath v1.3.1
## explicit; go 1.14
github.com/antchfx/xpath
# github.com/davecgh/go-spew v1.1.1
@ -16,12 +16,12 @@ github.com/influxdata/influxdb1-client/pkg/escape
github.com/influxdata/influxdb1-client/v2
# github.com/stretchr/testify v1.7.0
## explicit; go 1.13
# golang.org/x/net v0.20.0
# golang.org/x/net v0.28.0
## explicit; go 1.18
golang.org/x/net/html
golang.org/x/net/html/atom
golang.org/x/net/html/charset
# golang.org/x/text v0.14.0
# golang.org/x/text v0.17.0
## explicit; go 1.18
golang.org/x/text/encoding
golang.org/x/text/encoding/charmap