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

This commit is contained in:
Paul 2023-03-17 13:16:58 +01:00
parent d13318e927
commit e1533ab274
19 changed files with 472 additions and 96 deletions

10
go.mod
View File

@ -1,13 +1,13 @@
module git.paulbsd.com/paulbsd/fuelprices
go 1.19
go 1.20
require (
github.com/antchfx/xmlquery v1.3.12
github.com/antchfx/xpath v1.2.1 // indirect
github.com/antchfx/xmlquery v1.3.15
github.com/antchfx/xpath v1.2.4 // indirect
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
golang.org/x/net v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/text v0.8.0 // indirect
gopkg.in/ini.v1 v1.67.0
)

32
go.sum
View File

@ -1,7 +1,12 @@
github.com/antchfx/xmlquery v1.3.12 h1:6TMGpdjpO/P8VhjnaYPXuqT3qyJ/VsqoyNTmJzNBTQ4=
github.com/antchfx/xmlquery v1.3.12/go.mod h1:3w2RvQvTz+DaT5fSgsELkSJcdNgkmg6vuXDEuhdwsPQ=
github.com/antchfx/xmlquery v1.3.15 h1:aJConNMi1sMha5G8YJoAIF5P+H+qG1L73bSItWHo8Tw=
github.com/antchfx/xmlquery v1.3.15/go.mod h1:zMDv5tIGjOxY/JCNNinnle7V/EwthZ5IT8eeCGJKRWA=
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=
github.com/antchfx/xpath v1.2.4 h1:dW1HB/JxKvGtJ9WyVGJ0sIoEcqftV3SqIstujI+B9XY=
github.com/antchfx/xpath v1.2.4/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=
@ -15,16 +20,43 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
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=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=

View File

@ -235,6 +235,9 @@ title.FirstChild = title_text
channel.FirstChild = title
fmt.Println(doc.OutputXML(true))
// <?xml version="1.0"?><rss><channel><title>W3Schools Home Page</title></channel></rss>
fmt.Println(doc.OutputXMLWithOptions(WithOutputSelf()))
// <?xml version="1.0"?><rss><channel><title>W3Schools Home Page</title></channel></rss>
```
Questions

View File

@ -1,7 +1,6 @@
package xmlquery
import (
"bytes"
"encoding/xml"
"fmt"
"html"
@ -50,24 +49,55 @@ type Node struct {
level int // node level in the tree
}
type outputConfiguration struct {
printSelf bool
preserveSpaces bool
emptyElementTagSupport bool
skipComments bool
}
type OutputOption func(*outputConfiguration)
// WithOutputSelf configures the Node to print the root node itself
func WithOutputSelf() OutputOption {
return func(oc *outputConfiguration) {
oc.printSelf = true
}
}
// WithEmptyTagSupport empty tags should be written as <empty/> and
// not as <empty></empty>
func WithEmptyTagSupport() OutputOption {
return func(oc *outputConfiguration) {
oc.emptyElementTagSupport = true
}
}
// WithoutComments will skip comments in output
func WithoutComments() OutputOption {
return func(oc *outputConfiguration) {
oc.skipComments = true
}
}
// InnerText returns the text between the start and end tags of the object.
func (n *Node) InnerText() string {
var output func(*bytes.Buffer, *Node)
output = func(buf *bytes.Buffer, n *Node) {
var output func(*strings.Builder, *Node)
output = func(b *strings.Builder, n *Node) {
switch n.Type {
case TextNode, CharDataNode:
buf.WriteString(n.Data)
b.WriteString(n.Data)
case CommentNode:
default:
for child := n.FirstChild; child != nil; child = child.NextSibling {
output(buf, child)
output(b, child)
}
}
}
var buf bytes.Buffer
output(&buf, n)
return buf.String()
var b strings.Builder
output(&b, n)
return b.String()
}
func (n *Node) sanitizedData(preserveSpaces bool) string {
@ -86,72 +116,106 @@ func calculatePreserveSpaces(n *Node, pastValue bool) bool {
return pastValue
}
func outputXML(buf *bytes.Buffer, n *Node, preserveSpaces bool) {
func outputXML(b *strings.Builder, n *Node, preserveSpaces bool, config *outputConfiguration) {
preserveSpaces = calculatePreserveSpaces(n, preserveSpaces)
switch n.Type {
case TextNode:
buf.WriteString(html.EscapeString(n.sanitizedData(preserveSpaces)))
b.WriteString(html.EscapeString(n.sanitizedData(preserveSpaces)))
return
case CharDataNode:
buf.WriteString("<![CDATA[")
buf.WriteString(n.Data)
buf.WriteString("]]>")
b.WriteString("<![CDATA[")
b.WriteString(n.Data)
b.WriteString("]]>")
return
case CommentNode:
buf.WriteString("<!--")
buf.WriteString(n.Data)
buf.WriteString("-->")
if !config.skipComments {
b.WriteString("<!--")
b.WriteString(n.Data)
b.WriteString("-->")
}
return
case DeclarationNode:
buf.WriteString("<?" + n.Data)
b.WriteString("<?" + n.Data)
default:
if n.Prefix == "" {
buf.WriteString("<" + n.Data)
b.WriteString("<" + n.Data)
} else {
buf.WriteString("<" + n.Prefix + ":" + n.Data)
b.WriteString("<" + n.Prefix + ":" + n.Data)
}
}
for _, attr := range n.Attr {
if attr.Name.Space != "" {
buf.WriteString(fmt.Sprintf(` %s:%s=`, attr.Name.Space, attr.Name.Local))
b.WriteString(fmt.Sprintf(` %s:%s=`, attr.Name.Space, attr.Name.Local))
} else {
buf.WriteString(fmt.Sprintf(` %s=`, attr.Name.Local))
b.WriteString(fmt.Sprintf(` %s=`, attr.Name.Local))
}
buf.WriteByte('"')
buf.WriteString(html.EscapeString(attr.Value))
buf.WriteByte('"')
b.WriteByte('"')
b.WriteString(html.EscapeString(attr.Value))
b.WriteByte('"')
}
if n.Type == DeclarationNode {
buf.WriteString("?>")
b.WriteString("?>")
} else {
buf.WriteString(">")
if n.FirstChild != nil || !config.emptyElementTagSupport {
b.WriteString(">")
} else {
b.WriteString("/>")
return
}
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
outputXML(buf, child, preserveSpaces)
outputXML(b, child, preserveSpaces, config)
}
if n.Type != DeclarationNode {
if n.Prefix == "" {
buf.WriteString(fmt.Sprintf("</%s>", n.Data))
b.WriteString(fmt.Sprintf("</%s>", n.Data))
} else {
buf.WriteString(fmt.Sprintf("</%s:%s>", n.Prefix, n.Data))
b.WriteString(fmt.Sprintf("</%s:%s>", n.Prefix, n.Data))
}
}
}
// OutputXML returns the text that including tags name.
func (n *Node) OutputXML(self bool) string {
config := &outputConfiguration{
printSelf: true,
emptyElementTagSupport: false,
}
preserveSpaces := calculatePreserveSpaces(n, false)
var buf bytes.Buffer
var b strings.Builder
if self && n.Type != DocumentNode {
outputXML(&buf, n, preserveSpaces)
outputXML(&b, n, preserveSpaces, config)
} else {
for n := n.FirstChild; n != nil; n = n.NextSibling {
outputXML(&buf, n, preserveSpaces)
outputXML(&b, n, preserveSpaces, config)
}
}
return buf.String()
return b.String()
}
// OutputXMLWithOptions returns the text that including tags name.
func (n *Node) OutputXMLWithOptions(opts ...OutputOption) string {
config := &outputConfiguration{}
// Set the options
for _, opt := range opts {
opt(config)
}
preserveSpaces := calculatePreserveSpaces(n, false)
var b strings.Builder
if config.printSelf && n.Type != DocumentNode {
outputXML(&b, n, preserveSpaces, config)
} else {
for n := n.FirstChild; n != nil; n = n.NextSibling {
outputXML(&b, n, preserveSpaces, config)
}
}
return b.String()
}
// AddAttr adds a new attribute specified by 'key' and 'val' to a node 'n'.
@ -172,6 +236,55 @@ func AddAttr(n *Node, key, val string) {
n.Attr = append(n.Attr, attr)
}
// SetAttr allows an attribute value with the specified name to be changed.
// If the attribute did not previously exist, it will be created.
func (n *Node) SetAttr(key, value string) {
if i := strings.Index(key, ":"); i > 0 {
space := key[:i]
local := key[i+1:]
for idx := 0; idx < len(n.Attr); idx++ {
if n.Attr[idx].Name.Space == space && n.Attr[idx].Name.Local == local {
n.Attr[idx].Value = value
return
}
}
AddAttr(n, key, value)
} else {
for idx := 0; idx < len(n.Attr); idx++ {
if n.Attr[idx].Name.Local == key {
n.Attr[idx].Value = value
return
}
}
AddAttr(n, key, value)
}
}
// RemoveAttr removes the attribute with the specified name.
func (n *Node) RemoveAttr(key string) {
removeIdx := -1
if i := strings.Index(key, ":"); i > 0 {
space := key[:i]
local := key[i+1:]
for idx := 0; idx < len(n.Attr); idx++ {
if n.Attr[idx].Name.Space == space && n.Attr[idx].Name.Local == local {
removeIdx = idx
}
}
} else {
for idx := 0; idx < len(n.Attr); idx++ {
if n.Attr[idx].Name.Local == key {
removeIdx = idx
}
}
}
if removeIdx != -1 {
n.Attr = append(n.Attr[:removeIdx], n.Attr[removeIdx+1:]...)
}
}
// AddChild adds a new node 'n' to a node 'parent' as its last child.
func AddChild(parent, n *Node) {
n.Parent = parent

View File

@ -3,7 +3,6 @@ package xmlquery
import (
"bufio"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
@ -92,23 +91,35 @@ func (p *parser) parse() (*Node, error) {
case xml.StartElement:
if p.level == 0 {
// mising XML declaration
node := &Node{Type: DeclarationNode, Data: "xml", level: 1}
attributes := make([]Attr, 1)
attributes[0].Name = xml.Name{Local: "version"}
attributes[0].Value = "1.0"
node := &Node{
Type: DeclarationNode,
Data: "xml",
Attr: attributes,
level: 1,
}
AddChild(p.prev, node)
p.level = 1
p.prev = node
}
// https://www.w3.org/TR/xml-names/#scoping-defaulting
var defaultNamespaceURL string
for _, att := range tok.Attr {
if att.Name.Local == "xmlns" {
p.space2prefix[att.Value] = ""
p.space2prefix[att.Value] = "" // reset empty if exist the default namespace
defaultNamespaceURL = att.Value
} else if att.Name.Space == "xmlns" {
p.space2prefix[att.Value] = att.Name.Local
if _, ok := p.space2prefix[att.Value]; !ok {
p.space2prefix[att.Value] = att.Name.Local
}
}
}
if tok.Name.Space != "" {
if _, found := p.space2prefix[tok.Name.Space]; !found {
return nil, errors.New("xmlquery: invalid XML document, namespace is missing")
if space := tok.Name.Space; space != "" {
if _, found := p.space2prefix[space]; !found && p.decoder.Strict {
return nil, fmt.Errorf("xmlquery: invalid XML document, namespace %s is missing", space)
}
}
@ -128,7 +139,6 @@ func (p *parser) parse() (*Node, error) {
node := &Node{
Type: ElementNode,
Data: tok.Name.Local,
Prefix: p.space2prefix[tok.Name.Space],
NamespaceURI: tok.Name.Space,
Attr: attributes,
level: p.level,
@ -144,6 +154,14 @@ func (p *parser) parse() (*Node, error) {
}
AddSibling(p.prev.Parent, node)
}
if node.NamespaceURI != "" {
node.Prefix = p.space2prefix[node.NamespaceURI]
if defaultNamespaceURL != "" && node.NamespaceURI == defaultNamespaceURL {
node.Prefix = ""
} else if n := node.Parent; n != nil && node.NamespaceURI == n.NamespaceURI {
node.Prefix = n.Prefix
}
}
// If we're in the streaming mode, we need to remember the node if it is the target node
// so that when we finish processing the node's EndElement, we know how/what to return to
// caller. Also we need to remove the target node from the tree upon next Read() call so

View File

@ -44,6 +44,12 @@ func axisPredicate(root *axisNode) func(NodeNavigator) bool {
predicate := func(n NodeNavigator) bool {
if typ == n.NodeType() || typ == allNode {
if nametest {
type namespaceURL interface {
NamespaceURL() string
}
if ns, ok := n.(namespaceURL); ok && root.hasNamespaceURI {
return root.LocalName == n.LocalName() && root.namespaceURI == ns.NamespaceURL()
}
if root.LocalName == n.LocalName() && root.Prefix == n.Prefix() {
return true
}
@ -88,7 +94,10 @@ func (b *builder) processAxisNode(root *axisNode) (query, error) {
}
return v
}
qyOutput = &descendantQuery{Input: qyGrandInput, Predicate: filter, Self: true}
// 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}
return qyOutput, nil
}
}
@ -347,7 +356,15 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
},
}
case "last":
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{Input: b.firstInput, Func: lastFunc}
}
case "position":
qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc}
case "boolean", "number", "string":
@ -511,6 +528,7 @@ func (b *builder) processNode(root node) (q query, err error) {
b.firstInput = q
case nodeFilter:
q, err = b.processFilterNode(root.(*filterNode))
b.firstInput = q
case nodeFunction:
q, err = b.processFunctionNode(root.(*functionNode))
case nodeOperator:
@ -521,12 +539,13 @@ func (b *builder) processNode(root node) (q query, err error) {
return
}
q = &groupQuery{Input: q}
b.firstInput = q
}
return
}
// build builds a specified XPath expressions expr.
func build(expr string) (q query, err error) {
func build(expr string, namespaces map[string]string) (q query, err error) {
defer func() {
if e := recover(); e != nil {
switch x := e.(type) {
@ -539,7 +558,7 @@ func build(expr string) (q query, err error) {
}
}
}()
root := parse(expr)
root := parse(expr, namespaces)
b := &builder{}
return b.processNode(root)
}

View File

@ -69,8 +69,9 @@ const (
)
type parser struct {
r *scanner
d int
r *scanner
d int
namespaces map[string]string
}
// newOperatorNode returns new operator node OperatorNode.
@ -84,8 +85,8 @@ func newOperandNode(v interface{}) node {
}
// newAxisNode returns new axis node AxisNode.
func newAxisNode(axeTyp, localName, prefix, prop string, n node) node {
return &axisNode{
func newAxisNode(axeTyp, localName, prefix, prop string, n node, opts ...func(p *axisNode)) node {
a := axisNode{
nodeType: nodeAxis,
LocalName: localName,
Prefix: prefix,
@ -93,6 +94,10 @@ func newAxisNode(axeTyp, localName, prefix, prop string, n node) node {
Prop: prop,
Input: n,
}
for _, o := range opts {
o(&a)
}
return &a
}
// newVariableNode returns new variable node VariableNode.
@ -469,7 +474,16 @@ func (p *parser) parseNodeTest(n node, axeTyp string) (opnd node) {
if p.r.name == "*" {
name = ""
}
opnd = newAxisNode(axeTyp, name, prefix, "", n)
opnd = newAxisNode(axeTyp, name, prefix, "", n, func(a *axisNode) {
if prefix != "" && p.namespaces != nil {
if ns, ok := p.namespaces[prefix]; ok {
a.hasNamespaceURI = true
a.namespaceURI = ns
} else {
panic(fmt.Sprintf("prefix %s not defined.", prefix))
}
}
})
}
case itemStar:
opnd = newAxisNode(axeTyp, "", "", "", n)
@ -531,11 +545,11 @@ func (p *parser) parseMethod(n node) node {
}
// Parse parsing the XPath express string expr and returns a tree node.
func parse(expr string) node {
func parse(expr string, namespaces map[string]string) node {
r := &scanner{text: expr}
r.nextChar()
r.nextItem()
p := &parser{r: r}
p := &parser{r: r, namespaces: namespaces}
return p.parseExpression(nil)
}
@ -563,11 +577,13 @@ func (o *operatorNode) String() string {
// axisNode holds a location step.
type axisNode struct {
nodeType
Input node
Prop string // node-test name.[comment|text|processing-instruction|node]
AxeType string // name of the axes.[attribute|ancestor|child|....]
LocalName string // local part name of node.
Prefix string // prefix name of node.
Input node
Prop string // node-test name.[comment|text|processing-instruction|node]
AxeType string // name of the axes.[attribute|ancestor|child|....]
LocalName string // local part name of node.
Prefix string // prefix name of node.
namespaceURI string // namespace URI of node
hasNamespaceURI bool // if namespace URI is set (can be "")
}
func (a *axisNode) String() string {

View File

@ -56,7 +56,7 @@ func (c *contextQuery) Evaluate(iterator) interface{} {
}
func (c *contextQuery) Clone() query {
return &contextQuery{count: 0, Root: c.Root}
return &contextQuery{Root: c.Root}
}
// ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
@ -558,8 +558,8 @@ func (f *filterQuery) do(t iterator) bool {
pt := getNodePosition(f.Input)
return int(val.Float()) == pt
default:
if q, ok := f.Predicate.(query); ok {
return q.Select(t) != nil
if f.Predicate != nil {
return f.Predicate.Select(t) != nil
}
}
return false
@ -577,7 +577,7 @@ func (f *filterQuery) Select(t iterator) NodeNavigator {
node := f.Input.Select(t)
if node == nil {
return node
return nil
}
node = node.Copy()
@ -676,14 +676,12 @@ type groupQuery struct {
}
func (g *groupQuery) Select(t iterator) NodeNavigator {
for {
node := g.Input.Select(t)
if node == nil {
return nil
}
g.posit++
return node.Copy()
node := g.Input.Select(t)
if node == nil {
return nil
}
g.posit++
return node
}
func (g *groupQuery) Evaluate(t iterator) interface{} {
@ -691,7 +689,7 @@ func (g *groupQuery) Evaluate(t iterator) interface{} {
}
func (g *groupQuery) Clone() query {
return &groupQuery{Input: g.Input}
return &groupQuery{Input: g.Input.Clone()}
}
func (g *groupQuery) position() int {
@ -896,6 +894,35 @@ func (u *unionQuery) Clone() query {
return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()}
}
type lastQuery struct {
buffer []NodeNavigator
counted bool
Input query
}
func (q *lastQuery) Select(t iterator) NodeNavigator {
return nil
}
func (q *lastQuery) Evaluate(t iterator) interface{} {
if !q.counted {
for {
node := q.Input.Select(t)
if node == nil {
break
}
q.buffer = append(q.buffer, node.Copy())
}
q.counted = true
}
return float64(len(q.buffer))
}
func (q *lastQuery) Clone() query {
return &lastQuery{Input: q.Input.Clone()}
}
func getHashCode(n NodeNavigator) uint64 {
var sb bytes.Buffer
switch n.NodeType() {

View File

@ -141,7 +141,7 @@ func Compile(expr string) (*Expr, error) {
if expr == "" {
return nil, errors.New("expr expression is nil")
}
qy, err := build(expr)
qy, err := build(expr, nil)
if err != nil {
return nil, err
}
@ -159,3 +159,18 @@ func MustCompile(expr string) *Expr {
}
return exp
}
// CompileWithNS compiles an XPath expression string, using given namespaces map.
func CompileWithNS(expr string, namespaces map[string]string) (*Expr, error) {
if expr == "" {
return nil, errors.New("expr expression is nil")
}
qy, err := build(expr, namespaces)
if err != nil {
return nil, err
}
if qy == nil {
return nil, fmt.Errorf(fmt.Sprintf("undeclared variable in XPath expression: %s", expr))
}
return &Expr{s: expr, q: qy}, nil
}

15
vendor/golang.org/x/net/html/doc.go generated vendored
View File

@ -92,6 +92,21 @@ example, to process each anchor node in depth-first order:
The relevant specifications include:
https://html.spec.whatwg.org/multipage/syntax.html and
https://html.spec.whatwg.org/multipage/syntax.html#tokenization
# Security Considerations
Care should be taken when parsing and interpreting HTML, whether full documents
or fragments, within the framework of the HTML specification, especially with
regard to untrusted inputs.
This package provides both a tokenizer and a parser. Only the parser constructs
a DOM according to the HTML specification, resolving malformed and misplaced
tags where appropriate. The tokenizer simply tokenizes the HTML presented to it,
and as such does not resolve issues that may exist in the processed HTML,
producing a literal interpretation of the input.
If your use case requires semantically well-formed HTML, as defined by the
WHATWG specifiction, the parser should be used rather than the tokenizer.
*/
package html // import "golang.org/x/net/html"

View File

@ -193,6 +193,87 @@ func lower(b []byte) []byte {
return b
}
// escapeComment is like func escape but escapes its input bytes less often.
// Per https://github.com/golang/go/issues/58246 some HTML comments are (1)
// meaningful and (2) contain angle brackets that we'd like to avoid escaping
// unless we have to.
//
// "We have to" includes the '&' byte, since that introduces other escapes.
//
// It also includes those bytes (not including EOF) that would otherwise end
// the comment. Per the summary table at the bottom of comment_test.go, this is
// the '>' byte that, per above, we'd like to avoid escaping unless we have to.
//
// Studying the summary table (and T actions in its '>' column) closely, we
// only need to escape in states 43, 44, 49, 51 and 52. State 43 is at the
// start of the comment data. State 52 is after a '!'. The other three states
// are after a '-'.
//
// Our algorithm is thus to escape every '&' and to escape '>' if and only if:
// - The '>' is after a '!' or '-' (in the unescaped data) or
// - The '>' is at the start of the comment data (after the opening "<!--").
func escapeComment(w writer, s string) error {
// When modifying this function, consider manually increasing the
// maxSuffixLen constant in func TestComments, from 6 to e.g. 9 or more.
// That increase should only be temporary, not committed, as it
// exponentially affects the test running time.
if len(s) == 0 {
return nil
}
// Loop:
// - Grow j such that s[i:j] does not need escaping.
// - If s[j] does need escaping, output s[i:j] and an escaped s[j],
// resetting i and j to point past that s[j] byte.
i := 0
for j := 0; j < len(s); j++ {
escaped := ""
switch s[j] {
case '&':
escaped = "&amp;"
case '>':
if j > 0 {
if prev := s[j-1]; (prev != '!') && (prev != '-') {
continue
}
}
escaped = "&gt;"
default:
continue
}
if i < j {
if _, err := w.WriteString(s[i:j]); err != nil {
return err
}
}
if _, err := w.WriteString(escaped); err != nil {
return err
}
i = j + 1
}
if i < len(s) {
if _, err := w.WriteString(s[i:]); err != nil {
return err
}
}
return nil
}
// escapeCommentString is to EscapeString as escapeComment is to escape.
func escapeCommentString(s string) string {
if strings.IndexAny(s, "&>") == -1 {
return s
}
var buf bytes.Buffer
escapeComment(&buf, s)
return buf.String()
}
const escapedChars = "&'<>\"\r"
func escape(w writer, s string) error {

View File

@ -184,7 +184,7 @@ func (p *parser) clearStackToContext(s scope) {
}
}
// parseGenericRawTextElements implements the generic raw text element parsing
// parseGenericRawTextElement implements the generic raw text element parsing
// algorithm defined in 12.2.6.2.
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-elements-that-contain-only-text
// TODO: Since both RAWTEXT and RCDATA states are treated as tokenizer's part

View File

@ -85,7 +85,7 @@ func render1(w writer, n *Node) error {
if _, err := w.WriteString("<!--"); err != nil {
return err
}
if err := escape(w, n.Data); err != nil {
if err := escapeComment(w, n.Data); err != nil {
return err
}
if _, err := w.WriteString("-->"); err != nil {

View File

@ -110,7 +110,7 @@ func (t Token) String() string {
case SelfClosingTagToken:
return "<" + t.tagString() + "/>"
case CommentToken:
return "<!--" + EscapeString(t.Data) + "-->"
return "<!--" + escapeCommentString(t.Data) + "-->"
case DoctypeToken:
return "<!DOCTYPE " + EscapeString(t.Data) + ">"
}
@ -598,6 +598,11 @@ scriptDataDoubleEscapeEnd:
// readComment reads the next comment token starting with "<!--". The opening
// "<!--" has already been consumed.
func (z *Tokenizer) readComment() {
// When modifying this function, consider manually increasing the
// maxSuffixLen constant in func TestComments, from 6 to e.g. 9 or more.
// That increase should only be temporary, not committed, as it
// exponentially affects the test running time.
z.data.start = z.raw.end
defer func() {
if z.data.end < z.data.start {
@ -605,14 +610,13 @@ func (z *Tokenizer) readComment() {
z.data.end = z.data.start
}
}()
for dashCount := 2; ; {
var dashCount int
beginning := true
for {
c := z.readByte()
if z.err != nil {
// Ignore up to two dashes at EOF.
if dashCount > 2 {
dashCount = 2
}
z.data.end = z.raw.end - dashCount
z.data.end = z.calculateAbruptCommentDataEnd()
return
}
switch c {
@ -620,7 +624,7 @@ func (z *Tokenizer) readComment() {
dashCount++
continue
case '>':
if dashCount >= 2 {
if dashCount >= 2 || beginning {
z.data.end = z.raw.end - len("-->")
return
}
@ -628,19 +632,52 @@ func (z *Tokenizer) readComment() {
if dashCount >= 2 {
c = z.readByte()
if z.err != nil {
z.data.end = z.raw.end
z.data.end = z.calculateAbruptCommentDataEnd()
return
}
if c == '>' {
} else if c == '>' {
z.data.end = z.raw.end - len("--!>")
return
} else if c == '-' {
dashCount = 1
beginning = false
continue
}
}
}
dashCount = 0
beginning = false
}
}
func (z *Tokenizer) calculateAbruptCommentDataEnd() int {
raw := z.Raw()
const prefixLen = len("<!--")
if len(raw) >= prefixLen {
raw = raw[prefixLen:]
if hasSuffix(raw, "--!") {
return z.raw.end - 3
} else if hasSuffix(raw, "--") {
return z.raw.end - 2
} else if hasSuffix(raw, "-") {
return z.raw.end - 1
}
}
return z.raw.end
}
func hasSuffix(b []byte, suffix string) bool {
if len(b) < len(suffix) {
return false
}
b = b[len(b)-len(suffix):]
for i := range b {
if b[i] != suffix[i] {
return false
}
}
return true
}
// readUntilCloseAngle reads until the next ">".
func (z *Tokenizer) readUntilCloseAngle() {
z.data.start = z.raw.end

View File

@ -64,7 +64,7 @@ func (e FuncEncoding) NewEncoder() *encoding.Encoder {
// byte.
type RepertoireError byte
// Error implements the error interrface.
// Error implements the error interface.
func (r RepertoireError) Error() string {
return "encoding: rune not supported by encoding."
}

View File

@ -118,7 +118,7 @@ func (t Tag) Parent() Tag {
return Tag{language: lang, locale: lang}
}
// returns token t and the rest of the string.
// nextToken returns token t and the rest of the string.
func nextToken(s string) (t, tail string) {
p := strings.Index(s[1:], "-")
if p == -1 {

View File

@ -409,7 +409,7 @@ func (t Tag) SetTypeForKey(key, value string) (Tag, error) {
return t, nil
}
// findKeyAndType returns the start and end position for the type corresponding
// findTypeForKey returns the start and end position for the type corresponding
// to key or the point at which to insert the key-value pair if the type
// wasn't found. The hasExt return value reports whether an -u extension was present.
// Note: the extensions are typically very small and are likely to contain

View File

@ -344,7 +344,7 @@ func (t Tag) Parent() Tag {
return Tag(compact.Tag(t).Parent())
}
// returns token t and the rest of the string.
// nextToken returns token t and the rest of the string.
func nextToken(s string) (t, tail string) {
p := strings.Index(s[1:], "-")
if p == -1 {

8
vendor/modules.txt vendored
View File

@ -1,7 +1,7 @@
# github.com/antchfx/xmlquery v1.3.12
# github.com/antchfx/xmlquery v1.3.15
## explicit; go 1.14
github.com/antchfx/xmlquery
# github.com/antchfx/xpath v1.2.1
# github.com/antchfx/xpath v1.2.4
## 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.1.0
# golang.org/x/net v0.8.0
## explicit; go 1.17
golang.org/x/net/html
golang.org/x/net/html/atom
golang.org/x/net/html/charset
# golang.org/x/text v0.4.0
# golang.org/x/text v0.8.0
## explicit; go 1.17
golang.org/x/text/encoding
golang.org/x/text/encoding/charmap