├── README.md
├── cmd
└── goxpath
│ ├── test
│ ├── subdir
│ │ ├── 2.xml
│ │ └── 3.xml
│ └── 1.xml
│ ├── goxpath.go
│ └── goxpath_test.go
├── parser
├── pathexpr
│ └── pathexpr.go
├── ast.go
└── parser.go
├── tree
├── interfaces.go
├── xmltree
│ ├── xmlbuilder
│ │ └── xmlbuilder.go
│ ├── xmlnode
│ │ └── xmlnode.go
│ ├── xmlele
│ │ └── xmlele.go
│ └── xmltree.go
├── xmlstruct
│ ├── xmlroot.go
│ ├── xmlstruct.go
│ ├── xmlnode.go
│ ├── xmlstruct_test.go
│ └── xmlele.go
├── xfn.go
├── xtypes.go
└── tree.go
├── internal
├── xsort
│ └── xsort.go
└── execxp
│ ├── execxp.go
│ ├── intfns
│ ├── numfns.go
│ ├── intfns.go
│ ├── boolfns.go
│ ├── nodesetfns.go
│ └── stringfns.go
│ ├── operators.go
│ ├── findutil
│ └── findUtil.go
│ └── paths.go
├── .travis.yml
├── coverage.sh
├── LICENSE
├── value_test.go
├── xconst
└── xconst.go
├── marshal.go
├── comp_test.go
├── goxpath.go
├── lexer
├── paths.go
├── xmlchars.go
└── lexer.go
├── err_test.go
├── misc_test.go
├── fns_test.go
└── path_test.go
/README.md:
--------------------------------------------------------------------------------
1 | # Deprecated. Use [xsel](https://github.com/ChrisTrenkamp/xsel) instead.
2 |
--------------------------------------------------------------------------------
/cmd/goxpath/test/subdir/2.xml:
--------------------------------------------------------------------------------
1 | bar
2 |
--------------------------------------------------------------------------------
/cmd/goxpath/test/subdir/3.xml:
--------------------------------------------------------------------------------
1 | bar2
2 |
--------------------------------------------------------------------------------
/cmd/goxpath/test/1.xml:
--------------------------------------------------------------------------------
1 | testpathtest2
2 |
--------------------------------------------------------------------------------
/parser/pathexpr/pathexpr.go:
--------------------------------------------------------------------------------
1 | package pathexpr
2 |
3 | import "encoding/xml"
4 |
5 | //PathExpr represents XPath step's. xmltree.XMLTree uses it to find nodes.
6 | type PathExpr struct {
7 | Name xml.Name
8 | Axis string
9 | NodeType string
10 | NS map[string]string
11 | }
12 |
--------------------------------------------------------------------------------
/tree/interfaces.go:
--------------------------------------------------------------------------------
1 | package tree
2 |
3 | import "fmt"
4 |
5 | //Result is used for all data types.
6 | type Result interface {
7 | fmt.Stringer
8 | }
9 |
10 | //IsBool is used for the XPath boolean function. It turns the data type to a bool.
11 | type IsBool interface {
12 | Bool() Bool
13 | }
14 |
15 | //IsNum is used for the XPath number function. It turns the data type to a number.
16 | type IsNum interface {
17 | Num() Num
18 | }
19 |
--------------------------------------------------------------------------------
/internal/xsort/xsort.go:
--------------------------------------------------------------------------------
1 | package xsort
2 |
3 | import (
4 | "sort"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/tree"
7 | )
8 |
9 | type nodeSort []tree.Node
10 |
11 | func (ns nodeSort) Len() int { return len(ns) }
12 | func (ns nodeSort) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
13 | func (ns nodeSort) Less(i, j int) bool {
14 | return ns[i].Pos() < ns[j].Pos()
15 | }
16 |
17 | //SortNodes sorts the array by the node document order
18 | func SortNodes(res []tree.Node) {
19 | sort.Sort(nodeSort(res))
20 | }
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | go:
4 | - 1.6
5 |
6 | before_install:
7 | - go get -u github.com/ChrisTrenkamp/goxpath
8 | - go get -u github.com/ChrisTrenkamp/goxpath/cmd/goxpath
9 | - go get -u github.com/wadey/gocovmerge
10 |
11 | script:
12 | - go list -f '{{if gt (len .TestGoFiles) 0}}"go test -covermode count -coverprofile {{.Name}}.coverprofile -coverpkg ./... {{.ImportPath}}"{{end}}' ./... | xargs -I {} bash -c {}
13 | - gocovmerge `ls *.coverprofile` > coverage.txt
14 |
15 | after_success:
16 | - bash <(curl -s https://codecov.io/bash)
17 |
--------------------------------------------------------------------------------
/tree/xmltree/xmlbuilder/xmlbuilder.go:
--------------------------------------------------------------------------------
1 | package xmlbuilder
2 |
3 | import (
4 | "encoding/xml"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/tree"
7 | )
8 |
9 | //BuilderOpts supplies all the information needed to create an XML node.
10 | type BuilderOpts struct {
11 | Dec *xml.Decoder
12 | Tok xml.Token
13 | NodeType tree.NodeType
14 | NS map[xml.Name]string
15 | Attrs []*xml.Attr
16 | NodePos int
17 | AttrStartPos int
18 | }
19 |
20 | //XMLBuilder is an interface for creating XML structures.
21 | type XMLBuilder interface {
22 | tree.Node
23 | CreateNode(*BuilderOpts) XMLBuilder
24 | EndElem() XMLBuilder
25 | }
26 |
--------------------------------------------------------------------------------
/coverage.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
3 | go get github.com/ChrisTrenkamp/goxpath/cmd/goxpath
4 | if [ $? != 0 ]; then
5 | exit 1
6 | fi
7 | go test >/dev/null 2>&1
8 | if [ $? != 0 ]; then
9 | go test
10 | exit 1
11 | fi
12 | gometalinter --deadline=1m ./...
13 | go list -f '{{if gt (len .TestGoFiles) 0}}"go test -covermode count -coverprofile {{.Name}}.coverprofile -coverpkg ./... {{.ImportPath}}"{{end}} >/dev/null' ./... | xargs -I {} bash -c {} 2>/dev/null
14 | gocovmerge `ls *.coverprofile` > coverage.txt
15 | go tool cover -html=coverage.txt -o coverage.html
16 | firefox coverage.html
17 | rm coverage.html coverage.txt *.coverprofile
18 |
--------------------------------------------------------------------------------
/internal/execxp/execxp.go:
--------------------------------------------------------------------------------
1 | package execxp
2 |
3 | import (
4 | "encoding/xml"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/parser"
7 | "github.com/ChrisTrenkamp/goxpath/tree"
8 | )
9 |
10 | //Exec executes the XPath expression, xp, against the tree, t, with the
11 | //namespace mappings, ns.
12 | func Exec(n *parser.Node, t tree.Node, ns map[string]string, fns map[xml.Name]tree.Wrap, v map[string]tree.Result) (tree.Result, error) {
13 | f := xpFilt{
14 | t: t,
15 | ns: ns,
16 | ctx: tree.NodeSet{t},
17 | fns: fns,
18 | variables: v,
19 | }
20 |
21 | return exec(&f, n)
22 | }
23 |
24 | func exec(f *xpFilt, n *parser.Node) (tree.Result, error) {
25 | err := xfExec(f, n)
26 | return f.ctx, err
27 | }
28 |
--------------------------------------------------------------------------------
/tree/xmlstruct/xmlroot.go:
--------------------------------------------------------------------------------
1 | package xmlstruct
2 |
3 | import (
4 | "encoding/xml"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/tree"
7 | )
8 |
9 | type XMLRoot struct {
10 | Ele *XMLEle
11 | }
12 |
13 | func (x *XMLRoot) ResValue() string {
14 | return x.Ele.ResValue()
15 | }
16 |
17 | func (x *XMLRoot) Pos() int {
18 | return 0
19 | }
20 |
21 | func (x *XMLRoot) GetToken() xml.Token {
22 | return xml.StartElement{}
23 | }
24 |
25 | func (x *XMLRoot) GetParent() tree.Elem {
26 | return x
27 | }
28 |
29 | func (x *XMLRoot) GetNodeType() tree.NodeType {
30 | return tree.NtRoot
31 | }
32 |
33 | func (x *XMLRoot) GetChildren() []tree.Node {
34 | return []tree.Node{x.Ele}
35 | }
36 |
37 | func (x *XMLRoot) GetAttrs() []tree.Node {
38 | return nil
39 | }
40 |
--------------------------------------------------------------------------------
/tree/xmlstruct/xmlstruct.go:
--------------------------------------------------------------------------------
1 | package xmlstruct
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | )
7 |
8 | func ParseStruct(i interface{}) (*XMLRoot, error) {
9 | ret := &XMLRoot{}
10 | val := reflect.ValueOf(i)
11 |
12 | if val.Kind() == reflect.Ptr {
13 | val = val.Elem()
14 | }
15 |
16 | if val.Kind() != reflect.Struct {
17 | return ret, fmt.Errorf("Interface is not a struct")
18 | }
19 |
20 | if getXMLName(val).Local == "" {
21 | return nil, fmt.Errorf("Invalid XML struct")
22 | }
23 |
24 | ret.Ele = &XMLEle{Val: val, pos: 1, prnt: ret}
25 |
26 | return ret, nil
27 | }
28 |
29 | func MustParseStruct(i interface{}) *XMLRoot {
30 | ret, err := ParseStruct(i)
31 |
32 | if err != nil {
33 | panic(err)
34 | }
35 |
36 | return ret
37 | }
38 |
--------------------------------------------------------------------------------
/tree/xmlstruct/xmlnode.go:
--------------------------------------------------------------------------------
1 | package xmlstruct
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 | "reflect"
7 |
8 | "github.com/ChrisTrenkamp/goxpath/tree"
9 | )
10 |
11 | type XMLNode struct {
12 | Val reflect.Value
13 | pos int
14 | prnt tree.Elem
15 | nodeType tree.NodeType
16 | prntTag string
17 | }
18 |
19 | func (x *XMLNode) ResValue() string {
20 | if x.Val.Kind() == reflect.Ptr {
21 | return fmt.Sprintf("%v", x.Val.Elem().Interface())
22 | }
23 | return fmt.Sprintf("%v", x.Val.Interface())
24 | }
25 |
26 | func (x *XMLNode) Pos() int {
27 | return x.pos
28 | }
29 |
30 | func (x *XMLNode) GetToken() xml.Token {
31 | switch x.nodeType {
32 | case tree.NtAttr:
33 | return xml.Attr{Name: getTagInfo(x.prntTag).name, Value: x.ResValue()}
34 | case tree.NtChd:
35 | return xml.CharData(x.ResValue())
36 | }
37 | //case tree.NtComm:
38 | return xml.Comment(x.ResValue())
39 | }
40 |
41 | func (x *XMLNode) GetParent() tree.Elem {
42 | return x.prnt
43 | }
44 |
45 | func (x *XMLNode) GetNodeType() tree.NodeType {
46 | return x.nodeType
47 | }
48 |
--------------------------------------------------------------------------------
/tree/xmlstruct/xmlstruct_test.go:
--------------------------------------------------------------------------------
1 | package xmlstruct
2 |
3 | import (
4 | "encoding/xml"
5 | "testing"
6 |
7 | "github.com/ChrisTrenkamp/goxpath"
8 | )
9 |
10 | func TestSingleFields(t *testing.T) {
11 | str := struct {
12 | XMLName xml.Name `xml:"struct"`
13 | Elem string `xml:"elem"`
14 | Attr string `xml:"attr,attr"`
15 | Attr2 string `xml:",attr"`
16 | Comm string `xml:",comment"`
17 | CD string `xml:",chardata"`
18 | Test interface{}
19 | }{
20 | Elem: "foo",
21 | Attr: "bar",
22 | Attr2: "baz",
23 | Comm: "steak",
24 | CD: "eggs",
25 | Test: struct {
26 | Elem2 string `xml:"elem2"`
27 | Attr3 string `xml:",attr"`
28 | }{
29 | Elem2: "elem2",
30 | Attr3: "attr3",
31 | },
32 | }
33 |
34 | x := MustParseStruct(&str)
35 | x1, err := xml.Marshal(str)
36 | str1 := string(x1)
37 | if err != nil {
38 | t.Error(err)
39 | }
40 | str2, err := goxpath.MarshalStr(x)
41 | if err != nil {
42 | t.Error(err)
43 | }
44 | if str1 != str2 {
45 | t.Error("Strings not equal")
46 | t.Error(str1)
47 | t.Error(str2)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/tree/xmltree/xmlnode/xmlnode.go:
--------------------------------------------------------------------------------
1 | package xmlnode
2 |
3 | import (
4 | "encoding/xml"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/tree"
7 | )
8 |
9 | //XMLNode will represent an attribute, character data, comment, or processing instruction node
10 | type XMLNode struct {
11 | xml.Token
12 | tree.NodePos
13 | tree.NodeType
14 | Parent tree.Elem
15 | }
16 |
17 | //GetToken returns the xml.Token representation of the node
18 | func (a XMLNode) GetToken() xml.Token {
19 | if a.NodeType == tree.NtAttr {
20 | ret := a.Token.(*xml.Attr)
21 | return *ret
22 | }
23 | return a.Token
24 | }
25 |
26 | //GetParent returns the parent node
27 | func (a XMLNode) GetParent() tree.Elem {
28 | return a.Parent
29 | }
30 |
31 | //ResValue returns the string value of the attribute
32 | func (a XMLNode) ResValue() string {
33 | switch a.NodeType {
34 | case tree.NtAttr:
35 | return a.Token.(*xml.Attr).Value
36 | case tree.NtChd:
37 | return string(a.Token.(xml.CharData))
38 | case tree.NtComm:
39 | return string(a.Token.(xml.Comment))
40 | }
41 | //case tree.NtPi:
42 | return string(a.Token.(xml.ProcInst).Inst)
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 ChrisTrenkamp
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/tree/xfn.go:
--------------------------------------------------------------------------------
1 | package tree
2 |
3 | import (
4 | "fmt"
5 | )
6 |
7 | //Ctx represents the current context position, size, node, and the current filtered result
8 | type Ctx struct {
9 | NodeSet
10 | Pos int
11 | Size int
12 | }
13 |
14 | //Fn is a XPath function, written in Go
15 | type Fn func(c Ctx, args ...Result) (Result, error)
16 |
17 | //LastArgOpt sets whether the last argument in a function is optional, variadic, or neither
18 | type LastArgOpt int
19 |
20 | //LastArgOpt options
21 | const (
22 | None LastArgOpt = iota
23 | Optional
24 | Variadic
25 | )
26 |
27 | //Wrap interfaces XPath function calls with Go
28 | type Wrap struct {
29 | Fn Fn
30 | //NArgs represents the number of arguments to the XPath function. -1 represents a single optional argument
31 | NArgs int
32 | LastArgOpt LastArgOpt
33 | }
34 |
35 | //Call checks the arguments and calls Fn if they are valid
36 | func (w Wrap) Call(c Ctx, args ...Result) (Result, error) {
37 | switch w.LastArgOpt {
38 | case Optional:
39 | if len(args) == w.NArgs || len(args) == w.NArgs-1 {
40 | return w.Fn(c, args...)
41 | }
42 | case Variadic:
43 | if len(args) >= w.NArgs-1 {
44 | return w.Fn(c, args...)
45 | }
46 | default:
47 | if len(args) == w.NArgs {
48 | return w.Fn(c, args...)
49 | }
50 | }
51 | return nil, fmt.Errorf("Invalid number of arguments")
52 | }
53 |
--------------------------------------------------------------------------------
/internal/execxp/intfns/numfns.go:
--------------------------------------------------------------------------------
1 | package intfns
2 |
3 | import (
4 | "fmt"
5 | "math"
6 |
7 | "github.com/ChrisTrenkamp/goxpath/tree"
8 | )
9 |
10 | func number(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
11 | if b, ok := args[0].(tree.IsNum); ok {
12 | return b.Num(), nil
13 | }
14 |
15 | return nil, fmt.Errorf("Cannot convert object to a number")
16 | }
17 |
18 | func sum(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
19 | n, ok := args[0].(tree.NodeSet)
20 | if !ok {
21 | return nil, fmt.Errorf("Cannot convert object to a node-set")
22 | }
23 |
24 | ret := 0.0
25 | for _, i := range n {
26 | ret += float64(tree.GetNodeNum(i))
27 | }
28 |
29 | return tree.Num(ret), nil
30 | }
31 |
32 | func floor(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
33 | n, ok := args[0].(tree.IsNum)
34 | if !ok {
35 | return nil, fmt.Errorf("Cannot convert object to a number")
36 | }
37 |
38 | return tree.Num(math.Floor(float64(n.Num()))), nil
39 | }
40 |
41 | func ceiling(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
42 | n, ok := args[0].(tree.IsNum)
43 | if !ok {
44 | return nil, fmt.Errorf("Cannot convert object to a number")
45 | }
46 |
47 | return tree.Num(math.Ceil(float64(n.Num()))), nil
48 | }
49 |
50 | func round(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
51 | isn, ok := args[0].(tree.IsNum)
52 | if !ok {
53 | return nil, fmt.Errorf("Cannot convert object to a number")
54 | }
55 |
56 | n := isn.Num()
57 |
58 | if math.IsNaN(float64(n)) || math.IsInf(float64(n), 0) {
59 | return n, nil
60 | }
61 |
62 | if n < -0.5 {
63 | n = tree.Num(int(n - 0.5))
64 | } else if n > 0.5 {
65 | n = tree.Num(int(n + 0.5))
66 | } else {
67 | n = 0
68 | }
69 |
70 | return n, nil
71 | }
72 |
--------------------------------------------------------------------------------
/value_test.go:
--------------------------------------------------------------------------------
1 | package goxpath
2 |
3 | import (
4 | "bytes"
5 | "runtime/debug"
6 | "testing"
7 |
8 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree"
9 | )
10 |
11 | func execVal(xp, x string, exp string, ns map[string]string, t *testing.T) {
12 | defer func() {
13 | if r := recover(); r != nil {
14 | t.Error("Panicked: from XPath expr: '" + xp)
15 | t.Error(r)
16 | t.Error(string(debug.Stack()))
17 | }
18 | }()
19 | res := MustParse(xp).MustExec(xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns })
20 |
21 | if res.String() != exp {
22 | t.Error("Incorrect result:'" + res.String() + "' from XPath expr: '" + xp + "'. Expecting: '" + exp + "'")
23 | return
24 | }
25 | }
26 |
27 | func TestNodeVal(t *testing.T) {
28 | p := `/test`
29 | x := `testpathtest2`
30 | exp := "testpathtest2"
31 | execVal(p, x, exp, nil, t)
32 | }
33 |
34 | func TestAttrVal(t *testing.T) {
35 | p := `/p1/@test`
36 | x := ``
37 | exp := "foo"
38 | execVal(p, x, exp, nil, t)
39 | }
40 |
41 | func TestCommentVal(t *testing.T) {
42 | p := `//comment()`
43 | x := ``
44 | exp := ` comment `
45 | execVal(p, x, exp, nil, t)
46 | }
47 |
48 | func TestProcInstVal(t *testing.T) {
49 | p := `//processing-instruction()`
50 | x := ``
51 | exp := `test`
52 | execVal(p, x, exp, nil, t)
53 | }
54 |
55 | func TestNodeNamespaceVal(t *testing.T) {
56 | p := `/test:p1/namespace::test`
57 | x := ``
58 | exp := `http://test`
59 | execVal(p, x, exp, nil, t)
60 | }
61 |
--------------------------------------------------------------------------------
/internal/execxp/intfns/intfns.go:
--------------------------------------------------------------------------------
1 | package intfns
2 |
3 | import (
4 | "encoding/xml"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/tree"
7 | )
8 |
9 | //BuiltIn contains the list of built-in XPath functions
10 | var BuiltIn = map[xml.Name]tree.Wrap{
11 | //String functions
12 | {Local: "string"}: {Fn: _string, NArgs: 1, LastArgOpt: tree.Optional},
13 | {Local: "concat"}: {Fn: concat, NArgs: 3, LastArgOpt: tree.Variadic},
14 | {Local: "starts-with"}: {Fn: startsWith, NArgs: 2},
15 | {Local: "contains"}: {Fn: contains, NArgs: 2},
16 | {Local: "substring-before"}: {Fn: substringBefore, NArgs: 2},
17 | {Local: "substring-after"}: {Fn: substringAfter, NArgs: 2},
18 | {Local: "substring"}: {Fn: substring, NArgs: 3, LastArgOpt: tree.Optional},
19 | {Local: "string-length"}: {Fn: stringLength, NArgs: 1, LastArgOpt: tree.Optional},
20 | {Local: "normalize-space"}: {Fn: normalizeSpace, NArgs: 1, LastArgOpt: tree.Optional},
21 | {Local: "translate"}: {Fn: translate, NArgs: 3},
22 | //Node set functions
23 | {Local: "last"}: {Fn: last},
24 | {Local: "position"}: {Fn: position},
25 | {Local: "count"}: {Fn: count, NArgs: 1},
26 | {Local: "local-name"}: {Fn: localName, NArgs: 1, LastArgOpt: tree.Optional},
27 | {Local: "namespace-uri"}: {Fn: namespaceURI, NArgs: 1, LastArgOpt: tree.Optional},
28 | {Local: "name"}: {Fn: name, NArgs: 1, LastArgOpt: tree.Optional},
29 | //boolean functions
30 | {Local: "boolean"}: {Fn: boolean, NArgs: 1},
31 | {Local: "not"}: {Fn: not, NArgs: 1},
32 | {Local: "true"}: {Fn: _true},
33 | {Local: "false"}: {Fn: _false},
34 | {Local: "lang"}: {Fn: lang, NArgs: 1},
35 | //number functions
36 | {Local: "number"}: {Fn: number, NArgs: 1, LastArgOpt: tree.Optional},
37 | {Local: "sum"}: {Fn: sum, NArgs: 1},
38 | {Local: "floor"}: {Fn: floor, NArgs: 1},
39 | {Local: "ceiling"}: {Fn: ceiling, NArgs: 1},
40 | {Local: "round"}: {Fn: round, NArgs: 1},
41 | }
42 |
--------------------------------------------------------------------------------
/internal/execxp/intfns/boolfns.go:
--------------------------------------------------------------------------------
1 | package intfns
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/tree"
7 | "golang.org/x/text/language"
8 | )
9 |
10 | func boolean(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
11 | if b, ok := args[0].(tree.IsBool); ok {
12 | return b.Bool(), nil
13 | }
14 |
15 | return nil, fmt.Errorf("Cannot convert object to a boolean")
16 | }
17 |
18 | func not(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
19 | b, ok := args[0].(tree.IsBool)
20 | if !ok {
21 | return nil, fmt.Errorf("Cannot convert object to a boolean")
22 | }
23 | return !b.Bool(), nil
24 | }
25 |
26 | func _true(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
27 | return tree.Bool(true), nil
28 | }
29 |
30 | func _false(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
31 | return tree.Bool(false), nil
32 | }
33 |
34 | func lang(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
35 | lStr := args[0].String()
36 |
37 | var n tree.Elem
38 |
39 | for _, i := range c.NodeSet {
40 | if i.GetNodeType() == tree.NtElem {
41 | n = i.(tree.Elem)
42 | } else {
43 | n = i.GetParent()
44 | }
45 |
46 | for n.GetNodeType() != tree.NtRoot {
47 | if attr, ok := tree.GetAttribute(n, "lang", tree.XMLSpace); ok {
48 | return checkLang(lStr, attr.Value), nil
49 | }
50 | n = n.GetParent()
51 | }
52 | }
53 |
54 | return tree.Bool(false), nil
55 | }
56 |
57 | func checkLang(srcStr, targStr string) tree.Bool {
58 | srcLang := language.Make(srcStr)
59 | srcRegion, srcRegionConf := srcLang.Region()
60 |
61 | targLang := language.Make(targStr)
62 | targRegion, targRegionConf := targLang.Region()
63 |
64 | if srcRegionConf == language.Exact && targRegionConf != language.Exact {
65 | return tree.Bool(false)
66 | }
67 |
68 | if srcRegion != targRegion && srcRegionConf == language.Exact && targRegionConf == language.Exact {
69 | return tree.Bool(false)
70 | }
71 |
72 | _, _, conf := language.NewMatcher([]language.Tag{srcLang}).Match(targLang)
73 | return tree.Bool(conf >= language.High)
74 | }
75 |
--------------------------------------------------------------------------------
/xconst/xconst.go:
--------------------------------------------------------------------------------
1 | package xconst
2 |
3 | const (
4 | //AxisAncestor represents the "ancestor" axis
5 | AxisAncestor = "ancestor"
6 | //AxisAncestorOrSelf represents the "ancestor-or-self" axis
7 | AxisAncestorOrSelf = "ancestor-or-self"
8 | //AxisAttribute represents the "attribute" axis
9 | AxisAttribute = "attribute"
10 | //AxisChild represents the "child" axis
11 | AxisChild = "child"
12 | //AxisDescendent represents the "descendant" axis
13 | AxisDescendent = "descendant"
14 | //AxisDescendentOrSelf represents the "descendant-or-self" axis
15 | AxisDescendentOrSelf = "descendant-or-self"
16 | //AxisFollowing represents the "following" axis
17 | AxisFollowing = "following"
18 | //AxisFollowingSibling represents the "following-sibling" axis
19 | AxisFollowingSibling = "following-sibling"
20 | //AxisNamespace represents the "namespace" axis
21 | AxisNamespace = "namespace"
22 | //AxisParent represents the "parent" axis
23 | AxisParent = "parent"
24 | //AxisPreceding represents the "preceding" axis
25 | AxisPreceding = "preceding"
26 | //AxisPrecedingSibling represents the "preceding-sibling" axis
27 | AxisPrecedingSibling = "preceding-sibling"
28 | //AxisSelf represents the "self" axis
29 | AxisSelf = "self"
30 | )
31 |
32 | //AxisNames is all the possible Axis identifiers wrapped in an array for convenience
33 | var AxisNames = []string{
34 | AxisAncestor,
35 | AxisAncestorOrSelf,
36 | AxisAttribute,
37 | AxisChild,
38 | AxisDescendent,
39 | AxisDescendentOrSelf,
40 | AxisFollowing,
41 | AxisFollowingSibling,
42 | AxisNamespace,
43 | AxisParent,
44 | AxisPreceding,
45 | AxisPrecedingSibling,
46 | AxisSelf,
47 | }
48 |
49 | const (
50 | //NodeTypeComment represents the "comment" node test
51 | NodeTypeComment = "comment"
52 | //NodeTypeText represents the "text" node test
53 | NodeTypeText = "text"
54 | //NodeTypeProcInst represents the "processing-instruction" node test
55 | NodeTypeProcInst = "processing-instruction"
56 | //NodeTypeNode represents the "node" node test
57 | NodeTypeNode = "node"
58 | )
59 |
60 | //NodeTypes is all the possible node tests wrapped in an array for convenience
61 | var NodeTypes = []string{
62 | NodeTypeComment,
63 | NodeTypeText,
64 | NodeTypeProcInst,
65 | NodeTypeNode,
66 | }
67 |
--------------------------------------------------------------------------------
/marshal.go:
--------------------------------------------------------------------------------
1 | package goxpath
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "io"
7 |
8 | "github.com/ChrisTrenkamp/goxpath/tree"
9 | )
10 |
11 | //Marshal prints the result tree, r, in XML form to w.
12 | func Marshal(n tree.Node, w io.Writer) error {
13 | return marshal(n, w)
14 | }
15 |
16 | //MarshalStr is like Marhal, but returns a string.
17 | func MarshalStr(n tree.Node) (string, error) {
18 | ret := bytes.NewBufferString("")
19 | err := marshal(n, ret)
20 |
21 | return ret.String(), err
22 | }
23 |
24 | func marshal(n tree.Node, w io.Writer) error {
25 | e := xml.NewEncoder(w)
26 | err := encTok(n, e)
27 | if err != nil {
28 | return err
29 | }
30 |
31 | return e.Flush()
32 | }
33 |
34 | func encTok(n tree.Node, e *xml.Encoder) error {
35 | switch n.GetNodeType() {
36 | case tree.NtAttr:
37 | return encAttr(n.GetToken().(xml.Attr), e)
38 | case tree.NtElem:
39 | return encEle(n.(tree.Elem), e)
40 | case tree.NtNs:
41 | return encNS(n.GetToken().(xml.Attr), e)
42 | case tree.NtRoot:
43 | for _, i := range n.(tree.Elem).GetChildren() {
44 | err := encTok(i, e)
45 | if err != nil {
46 | return err
47 | }
48 | }
49 | return nil
50 | }
51 |
52 | //case tree.NtChd, tree.NtComm, tree.NtPi:
53 | return e.EncodeToken(n.GetToken())
54 | }
55 |
56 | func encAttr(a xml.Attr, e *xml.Encoder) error {
57 | str := a.Name.Local + `="` + a.Value + `"`
58 |
59 | if a.Name.Space != "" {
60 | str += ` xmlns="` + a.Name.Space + `"`
61 | }
62 |
63 | pi := xml.ProcInst{
64 | Target: "attribute",
65 | Inst: ([]byte)(str),
66 | }
67 |
68 | return e.EncodeToken(pi)
69 | }
70 |
71 | func encNS(ns xml.Attr, e *xml.Encoder) error {
72 | pi := xml.ProcInst{
73 | Target: "namespace",
74 | Inst: ([]byte)(ns.Value),
75 | }
76 | return e.EncodeToken(pi)
77 | }
78 |
79 | func encEle(n tree.Elem, e *xml.Encoder) error {
80 | ele := xml.StartElement{
81 | Name: n.GetToken().(xml.StartElement).Name,
82 | }
83 |
84 | attrs := n.GetAttrs()
85 | ele.Attr = make([]xml.Attr, len(attrs))
86 | for i := range attrs {
87 | ele.Attr[i] = attrs[i].GetToken().(xml.Attr)
88 | }
89 |
90 | err := e.EncodeToken(ele)
91 | if err != nil {
92 | return err
93 | }
94 |
95 | if x, ok := n.(tree.Elem); ok {
96 | for _, i := range x.GetChildren() {
97 | err := encTok(i, e)
98 | if err != nil {
99 | return err
100 | }
101 | }
102 | }
103 |
104 | return e.EncodeToken(ele.End())
105 | }
106 |
--------------------------------------------------------------------------------
/tree/xtypes.go:
--------------------------------------------------------------------------------
1 | package tree
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "strconv"
7 | "strings"
8 | )
9 |
10 | //Boolean strings
11 | const (
12 | True = "true"
13 | False = "false"
14 | )
15 |
16 | //Bool is a boolean XPath type
17 | type Bool bool
18 |
19 | //ResValue satisfies the Res interface for Bool
20 | func (b Bool) String() string {
21 | if b {
22 | return True
23 | }
24 |
25 | return False
26 | }
27 |
28 | //Bool satisfies the HasBool interface for Bool's
29 | func (b Bool) Bool() Bool {
30 | return b
31 | }
32 |
33 | //Num satisfies the HasNum interface for Bool's
34 | func (b Bool) Num() Num {
35 | if b {
36 | return Num(1)
37 | }
38 |
39 | return Num(0)
40 | }
41 |
42 | //Num is a number XPath type
43 | type Num float64
44 |
45 | //ResValue satisfies the Res interface for Num
46 | func (n Num) String() string {
47 | if math.IsInf(float64(n), 0) {
48 | if math.IsInf(float64(n), 1) {
49 | return "Infinity"
50 | }
51 | return "-Infinity"
52 | }
53 | return fmt.Sprintf("%g", float64(n))
54 | }
55 |
56 | //Bool satisfies the HasBool interface for Num's
57 | func (n Num) Bool() Bool {
58 | return n != 0
59 | }
60 |
61 | //Num satisfies the HasNum interface for Num's
62 | func (n Num) Num() Num {
63 | return n
64 | }
65 |
66 | //String is string XPath type
67 | type String string
68 |
69 | //ResValue satisfies the Res interface for String
70 | func (s String) String() string {
71 | return string(s)
72 | }
73 |
74 | //Bool satisfies the HasBool interface for String's
75 | func (s String) Bool() Bool {
76 | return Bool(len(s) > 0)
77 | }
78 |
79 | //Num satisfies the HasNum interface for String's
80 | func (s String) Num() Num {
81 | num, err := strconv.ParseFloat(strings.TrimSpace(string(s)), 64)
82 | if err != nil {
83 | return Num(math.NaN())
84 | }
85 | return Num(num)
86 | }
87 |
88 | //NodeSet is a node-set XPath type
89 | type NodeSet []Node
90 |
91 | //GetNodeNum converts the node to a string-value and to a number
92 | func GetNodeNum(n Node) Num {
93 | return String(n.ResValue()).Num()
94 | }
95 |
96 | //String satisfies the Res interface for NodeSet
97 | func (n NodeSet) String() string {
98 | if len(n) == 0 {
99 | return ""
100 | }
101 |
102 | return n[0].ResValue()
103 | }
104 |
105 | //Bool satisfies the HasBool interface for node-set's
106 | func (n NodeSet) Bool() Bool {
107 | return Bool(len(n) > 0)
108 | }
109 |
110 | //Num satisfies the HasNum interface for NodeSet's
111 | func (n NodeSet) Num() Num {
112 | return String(n.String()).Num()
113 | }
114 |
--------------------------------------------------------------------------------
/parser/ast.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import "github.com/ChrisTrenkamp/goxpath/lexer"
4 |
5 | //NodeType enumerations
6 | const (
7 | Empty lexer.XItemType = ""
8 | )
9 |
10 | //Node builds an AST tree for operating on XPath expressions
11 | type Node struct {
12 | Val lexer.XItem
13 | Left *Node
14 | Right *Node
15 | Parent *Node
16 | next *Node
17 | }
18 |
19 | var beginPathType = map[lexer.XItemType]bool{
20 | lexer.XItemAbsLocPath: true,
21 | lexer.XItemAbbrAbsLocPath: true,
22 | lexer.XItemAbbrRelLocPath: true,
23 | lexer.XItemRelLocPath: true,
24 | lexer.XItemFunction: true,
25 | }
26 |
27 | func (n *Node) add(i lexer.XItem) {
28 | if n.Val.Typ == Empty {
29 | n.Val = i
30 | } else if n.Left == nil && n.Right == nil {
31 | n.Left = &Node{Val: n.Val, Parent: n}
32 | n.Val = i
33 | } else if beginPathType[n.Val.Typ] {
34 | next := &Node{Val: n.Val, Left: n.Left, Right: n.Right, Parent: n}
35 | n.Left, n.Right = next, nil
36 | n.Val = i
37 | } else if n.Right == nil {
38 | n.Right = &Node{Val: i, Parent: n}
39 | } else {
40 | next := &Node{Val: n.Val, Left: n.Left, Right: n.Right, Parent: n}
41 | n.Left, n.Right = next, nil
42 | n.Val = i
43 | }
44 | n.next = n
45 | }
46 |
47 | func (n *Node) push(i lexer.XItem) {
48 | if n.Left == nil {
49 | n.Left = &Node{Val: i, Parent: n}
50 | n.next = n.Left
51 | } else if n.Right == nil {
52 | n.Right = &Node{Val: i, Parent: n}
53 | n.next = n.Right
54 | } else {
55 | next := &Node{Val: i, Left: n.Right, Parent: n}
56 | n.Right = next
57 | n.next = n.Right
58 | }
59 | }
60 |
61 | func (n *Node) pushNotEmpty(i lexer.XItem) {
62 | if n.Val.Typ == Empty {
63 | n.add(i)
64 | } else {
65 | n.push(i)
66 | }
67 | }
68 |
69 | /*
70 | func (n *Node) prettyPrint(depth, width int) {
71 | nodes := []*Node{}
72 | n.getLine(depth, &nodes)
73 | fmt.Printf("%*s", (width-depth)*2, "")
74 | toggle := true
75 | if len(nodes) > 1 {
76 | for _, i := range nodes {
77 | if i != nil {
78 | if toggle {
79 | fmt.Print("/ ")
80 | } else {
81 | fmt.Print("\\ ")
82 | }
83 | }
84 | toggle = !toggle
85 | }
86 | fmt.Println()
87 | fmt.Printf("%*s", (width-depth)*2, "")
88 | }
89 | for _, i := range nodes {
90 | if i != nil {
91 | fmt.Print(i.Val.Val, " ")
92 | }
93 | }
94 | fmt.Println()
95 | }
96 |
97 | func (n *Node) getLine(depth int, ret *[]*Node) {
98 | if depth <= 0 && n != nil {
99 | *ret = append(*ret, n)
100 | return
101 | }
102 | if n.Left != nil {
103 | n.Left.getLine(depth-1, ret)
104 | } else if depth-1 <= 0 {
105 | *ret = append(*ret, nil)
106 | }
107 | if n.Right != nil {
108 | n.Right.getLine(depth-1, ret)
109 | } else if depth-1 <= 0 {
110 | *ret = append(*ret, nil)
111 | }
112 | }
113 | */
114 |
--------------------------------------------------------------------------------
/comp_test.go:
--------------------------------------------------------------------------------
1 | package goxpath
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 | )
7 |
8 | func TestAddition(t *testing.T) {
9 | p := `1 + 1`
10 | x := ``
11 | exp := "2"
12 | execVal(p, x, exp, nil, t)
13 | }
14 |
15 | func TestParenths(t *testing.T) {
16 | p := `(1 + 2) * 3`
17 | x := ``
18 | exp := "9"
19 | execVal(p, x, exp, nil, t)
20 | }
21 |
22 | func TestParenths2(t *testing.T) {
23 | p := `(1 + 2 * 3)`
24 | x := ``
25 | exp := "7"
26 | execVal(p, x, exp, nil, t)
27 | }
28 |
29 | func TestEquals(t *testing.T) {
30 | p := `/test/test2 = 3`
31 | x := `3`
32 | exp := "true"
33 | execVal(p, x, exp, nil, t)
34 | }
35 |
36 | func TestEqualsStr(t *testing.T) {
37 | p := `/test/test2 = 'foobar'`
38 | x := `foobar`
39 | exp := "true"
40 | execVal(p, x, exp, nil, t)
41 | }
42 |
43 | func TestEqualsStr2(t *testing.T) {
44 | p := `/root[@test="foo"]`
45 | x := `test`
46 | exp := "test"
47 | execVal(p, x, exp, nil, t)
48 | }
49 |
50 | func TestNumberOps(t *testing.T) {
51 | x := `2352`
52 | testFloatMap := make(map[string]float64)
53 | testFloatMap[`/t/t1 * 3`] = 2 * 3
54 | testFloatMap[`5 div /t/t1`] = 5.0 / 2.0
55 | testFloatMap[`/t/t2 + /t/t3`] = 3 + 5
56 | testFloatMap[`/t/t2 - /t/t3`] = 3 - 5
57 | testFloatMap[`/t/t3 mod /t/t1`] = 5 % 2
58 | testFloatMap[`number('5')`] = 5
59 | testFloatMap[`sum(/t/*)`] = 2 + 3 + 5 + 2
60 | testFloatMap[`floor(/t/t3 div /t/t1)`] = 2
61 | testFloatMap[`ceiling(t/t3 div /t/t1)`] = 3
62 | testFloatMap[`round(-1.5)`] = -2
63 | testFloatMap[`round(1.5)`] = 2
64 | testFloatMap[`round(0)`] = 0
65 | for k, v := range testFloatMap {
66 | execVal(k, x, fmt.Sprintf("%g", float64(v)), nil, t)
67 | }
68 |
69 | execVal(`/t/t3 div 0`, x, "Infinity", nil, t)
70 | execVal(`-1 div 0`, x, "-Infinity", nil, t)
71 | execVal(`0 div 0`, x, "NaN", nil, t)
72 |
73 | testBoolMap := make(map[string]string)
74 | testBoolMap[`/t/t1 = 2`] = "true"
75 | testBoolMap[`2 = /t/t1`] = "true"
76 | testBoolMap[`/t/t1 != 2`] = "false"
77 | testBoolMap[`4 = /t/t2`] = "false"
78 | testBoolMap[`/t/t1 != /t/t2`] = "true"
79 | testBoolMap[`2 < /t/t4`] = "false"
80 | testBoolMap[`/t/t1 <= 2`] = "true"
81 | testBoolMap[`/t/t1 > /t/t4`] = "false"
82 | testBoolMap[`2 >= /t/t4`] = "true"
83 | testBoolMap[`/t/t1 >= /t/t4`] = "true"
84 | testBoolMap[`/t/t1 != /t/t2 and /t/t1 < /t/t4`] = "false"
85 | testBoolMap[`/t/t1 != /t/t2 or /t/t1 < /t/t4`] = "true"
86 | testBoolMap[`(/t/t1 != /t/t2 or /t/t1 < /t/t4) = true()`] = "true"
87 | testBoolMap[`(/t/t1 != /t/t2 and /t/t1 < /t/t4) != true()`] = "true"
88 | for k, v := range testBoolMap {
89 | execVal(k, x, v, nil, t)
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/tree/xmltree/xmlele/xmlele.go:
--------------------------------------------------------------------------------
1 | package xmlele
2 |
3 | import (
4 | "encoding/xml"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/tree"
7 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlbuilder"
8 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlnode"
9 | )
10 |
11 | //XMLEle is an implementation of XPRes for XML elements
12 | type XMLEle struct {
13 | xml.StartElement
14 | tree.NSBuilder
15 | Attrs []tree.Node
16 | Children []tree.Node
17 | Parent tree.Elem
18 | tree.NodePos
19 | tree.NodeType
20 | }
21 |
22 | //Root is the default root node builder for xmltree.ParseXML
23 | func Root() xmlbuilder.XMLBuilder {
24 | return &XMLEle{NodeType: tree.NtRoot}
25 | }
26 |
27 | //CreateNode is an implementation of xmlbuilder.XMLBuilder. It appends the node
28 | //specified in opts and returns the child if it is an element. Otherwise, it returns x.
29 | func (x *XMLEle) CreateNode(opts *xmlbuilder.BuilderOpts) xmlbuilder.XMLBuilder {
30 | if opts.NodeType == tree.NtElem {
31 | ele := &XMLEle{
32 | StartElement: opts.Tok.(xml.StartElement),
33 | NSBuilder: tree.NSBuilder{NS: opts.NS},
34 | Attrs: make([]tree.Node, len(opts.Attrs)),
35 | Parent: x,
36 | NodePos: tree.NodePos(opts.NodePos),
37 | NodeType: opts.NodeType,
38 | }
39 | for i := range opts.Attrs {
40 | ele.Attrs[i] = xmlnode.XMLNode{
41 | Token: opts.Attrs[i],
42 | NodePos: tree.NodePos(opts.AttrStartPos + i),
43 | NodeType: tree.NtAttr,
44 | Parent: ele,
45 | }
46 | }
47 | x.Children = append(x.Children, ele)
48 | return ele
49 | }
50 |
51 | node := xmlnode.XMLNode{
52 | Token: opts.Tok,
53 | NodePos: tree.NodePos(opts.NodePos),
54 | NodeType: opts.NodeType,
55 | Parent: x,
56 | }
57 | x.Children = append(x.Children, node)
58 | return x
59 | }
60 |
61 | //EndElem is an implementation of xmlbuilder.XMLBuilder. It returns x's parent.
62 | func (x *XMLEle) EndElem() xmlbuilder.XMLBuilder {
63 | return x.Parent.(*XMLEle)
64 | }
65 |
66 | //GetToken returns the xml.Token representation of the node
67 | func (x *XMLEle) GetToken() xml.Token {
68 | return x.StartElement
69 | }
70 |
71 | //GetParent returns the parent node, or itself if it's the root
72 | func (x *XMLEle) GetParent() tree.Elem {
73 | return x.Parent
74 | }
75 |
76 | //GetChildren returns all child nodes of the element
77 | func (x *XMLEle) GetChildren() []tree.Node {
78 | ret := make([]tree.Node, len(x.Children))
79 |
80 | for i := range x.Children {
81 | ret[i] = x.Children[i]
82 | }
83 |
84 | return ret
85 | }
86 |
87 | //GetAttrs returns all attributes of the element
88 | func (x *XMLEle) GetAttrs() []tree.Node {
89 | ret := make([]tree.Node, len(x.Attrs))
90 | for i := range x.Attrs {
91 | ret[i] = x.Attrs[i]
92 | }
93 | return ret
94 | }
95 |
96 | //ResValue returns the string value of the element and children
97 | func (x *XMLEle) ResValue() string {
98 | ret := ""
99 | for i := range x.Children {
100 | switch x.Children[i].GetNodeType() {
101 | case tree.NtChd, tree.NtElem, tree.NtRoot:
102 | ret += x.Children[i].ResValue()
103 | }
104 | }
105 | return ret
106 | }
107 |
--------------------------------------------------------------------------------
/internal/execxp/intfns/nodesetfns.go:
--------------------------------------------------------------------------------
1 | package intfns
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 |
7 | "github.com/ChrisTrenkamp/goxpath/tree"
8 | )
9 |
10 | func last(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
11 | return tree.Num(c.Size), nil
12 | }
13 |
14 | func position(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
15 | return tree.Num(c.Pos), nil
16 | }
17 |
18 | func count(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
19 | n, ok := args[0].(tree.NodeSet)
20 | if !ok {
21 | return nil, fmt.Errorf("Cannot convert object to a node-set")
22 | }
23 |
24 | return tree.Num(len(n)), nil
25 | }
26 |
27 | func localName(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
28 | var n tree.NodeSet
29 | ok := true
30 | if len(args) == 1 {
31 | n, ok = args[0].(tree.NodeSet)
32 | } else {
33 | n = c.NodeSet
34 | }
35 | if !ok {
36 | return nil, fmt.Errorf("Cannot convert object to a node-set")
37 | }
38 |
39 | ret := ""
40 | if len(n) == 0 {
41 | return tree.String(ret), nil
42 | }
43 | node := n[0]
44 |
45 | tok := node.GetToken()
46 |
47 | switch node.GetNodeType() {
48 | case tree.NtElem:
49 | ret = tok.(xml.StartElement).Name.Local
50 | case tree.NtAttr:
51 | ret = tok.(xml.Attr).Name.Local
52 | case tree.NtPi:
53 | ret = tok.(xml.ProcInst).Target
54 | }
55 |
56 | return tree.String(ret), nil
57 | }
58 |
59 | func namespaceURI(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
60 | var n tree.NodeSet
61 | ok := true
62 | if len(args) == 1 {
63 | n, ok = args[0].(tree.NodeSet)
64 | } else {
65 | n = c.NodeSet
66 | }
67 | if !ok {
68 | return nil, fmt.Errorf("Cannot convert object to a node-set")
69 | }
70 |
71 | ret := ""
72 | if len(n) == 0 {
73 | return tree.String(ret), nil
74 | }
75 | node := n[0]
76 |
77 | tok := node.GetToken()
78 |
79 | switch node.GetNodeType() {
80 | case tree.NtElem:
81 | ret = tok.(xml.StartElement).Name.Space
82 | case tree.NtAttr:
83 | ret = tok.(xml.Attr).Name.Space
84 | }
85 |
86 | return tree.String(ret), nil
87 | }
88 |
89 | func name(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
90 | var n tree.NodeSet
91 | ok := true
92 | if len(args) == 1 {
93 | n, ok = args[0].(tree.NodeSet)
94 | } else {
95 | n = c.NodeSet
96 | }
97 | if !ok {
98 | return nil, fmt.Errorf("Cannot convert object to a node-set")
99 | }
100 |
101 | ret := ""
102 | if len(n) == 0 {
103 | return tree.String(ret), nil
104 | }
105 | node := n[0]
106 |
107 | switch node.GetNodeType() {
108 | case tree.NtElem:
109 | t := node.GetToken().(xml.StartElement)
110 | space := ""
111 |
112 | if t.Name.Space != "" {
113 | space = fmt.Sprintf("{%s}", t.Name.Space)
114 | }
115 |
116 | ret = fmt.Sprintf("%s%s", space, t.Name.Local)
117 | case tree.NtAttr:
118 | t := node.GetToken().(xml.Attr)
119 | space := ""
120 |
121 | if t.Name.Space != "" {
122 | space = fmt.Sprintf("{%s}", t.Name.Space)
123 | }
124 |
125 | ret = fmt.Sprintf("%s%s", space, t.Name.Local)
126 | case tree.NtPi:
127 | ret = fmt.Sprintf("%s", node.GetToken().(xml.ProcInst).Target)
128 | }
129 |
130 | return tree.String(ret), nil
131 | }
132 |
--------------------------------------------------------------------------------
/goxpath.go:
--------------------------------------------------------------------------------
1 | package goxpath
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 |
7 | "github.com/ChrisTrenkamp/goxpath/internal/execxp"
8 | "github.com/ChrisTrenkamp/goxpath/parser"
9 | "github.com/ChrisTrenkamp/goxpath/tree"
10 | )
11 |
12 | //Opts defines namespace mappings and custom functions for XPath expressions.
13 | type Opts struct {
14 | NS map[string]string
15 | Funcs map[xml.Name]tree.Wrap
16 | Vars map[string]tree.Result
17 | }
18 |
19 | //FuncOpts is a function wrapper for Opts.
20 | type FuncOpts func(*Opts)
21 |
22 | //XPathExec is the XPath executor, compiled from an XPath string
23 | type XPathExec struct {
24 | n *parser.Node
25 | }
26 |
27 | //Parse parses the XPath expression, xp, returning an XPath executor.
28 | func Parse(xp string) (XPathExec, error) {
29 | n, err := parser.Parse(xp)
30 | return XPathExec{n: n}, err
31 | }
32 |
33 | //MustParse is like Parse, but panics instead of returning an error.
34 | func MustParse(xp string) XPathExec {
35 | ret, err := Parse(xp)
36 | if err != nil {
37 | panic(err)
38 | }
39 | return ret
40 | }
41 |
42 | //Exec executes the XPath expression, xp, against the tree, t, with the
43 | //namespace mappings, ns, and returns the result as a stringer.
44 | func (xp XPathExec) Exec(t tree.Node, opts ...FuncOpts) (tree.Result, error) {
45 | o := &Opts{
46 | NS: make(map[string]string),
47 | Funcs: make(map[xml.Name]tree.Wrap),
48 | Vars: make(map[string]tree.Result),
49 | }
50 | for _, i := range opts {
51 | i(o)
52 | }
53 | return execxp.Exec(xp.n, t, o.NS, o.Funcs, o.Vars)
54 | }
55 |
56 | //ExecBool is like Exec, except it will attempt to convert the result to its boolean value.
57 | func (xp XPathExec) ExecBool(t tree.Node, opts ...FuncOpts) (bool, error) {
58 | res, err := xp.Exec(t, opts...)
59 | if err != nil {
60 | return false, err
61 | }
62 |
63 | b, ok := res.(tree.IsBool)
64 | if !ok {
65 | return false, fmt.Errorf("Cannot convert result to a boolean")
66 | }
67 |
68 | return bool(b.Bool()), nil
69 | }
70 |
71 | //ExecNum is like Exec, except it will attempt to convert the result to its number value.
72 | func (xp XPathExec) ExecNum(t tree.Node, opts ...FuncOpts) (float64, error) {
73 | res, err := xp.Exec(t, opts...)
74 | if err != nil {
75 | return 0, err
76 | }
77 |
78 | n, ok := res.(tree.IsNum)
79 | if !ok {
80 | return 0, fmt.Errorf("Cannot convert result to a number")
81 | }
82 |
83 | return float64(n.Num()), nil
84 | }
85 |
86 | //ExecNode is like Exec, except it will attempt to return the result as a node-set.
87 | func (xp XPathExec) ExecNode(t tree.Node, opts ...FuncOpts) (tree.NodeSet, error) {
88 | res, err := xp.Exec(t, opts...)
89 | if err != nil {
90 | return nil, err
91 | }
92 |
93 | n, ok := res.(tree.NodeSet)
94 | if !ok {
95 | return nil, fmt.Errorf("Cannot convert result to a node-set")
96 | }
97 |
98 | return n, nil
99 | }
100 |
101 | //MustExec is like Exec, but panics instead of returning an error.
102 | func (xp XPathExec) MustExec(t tree.Node, opts ...FuncOpts) tree.Result {
103 | res, err := xp.Exec(t, opts...)
104 | if err != nil {
105 | panic(err)
106 | }
107 | return res
108 | }
109 |
110 | //ParseExec parses the XPath string, xpstr, and runs Exec.
111 | func ParseExec(xpstr string, t tree.Node, opts ...FuncOpts) (tree.Result, error) {
112 | xp, err := Parse(xpstr)
113 | if err != nil {
114 | return nil, err
115 | }
116 | return xp.Exec(t, opts...)
117 | }
118 |
--------------------------------------------------------------------------------
/internal/execxp/intfns/stringfns.go:
--------------------------------------------------------------------------------
1 | package intfns
2 |
3 | import (
4 | "math"
5 | "regexp"
6 | "strings"
7 |
8 | "github.com/ChrisTrenkamp/goxpath/tree"
9 | )
10 |
11 | func _string(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
12 | if len(args) == 1 {
13 | return tree.String(args[0].String()), nil
14 | }
15 |
16 | return tree.String(c.NodeSet.String()), nil
17 | }
18 |
19 | func concat(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
20 | ret := ""
21 |
22 | for _, i := range args {
23 | ret += i.String()
24 | }
25 |
26 | return tree.String(ret), nil
27 | }
28 |
29 | func startsWith(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
30 | return tree.Bool(strings.Index(args[0].String(), args[1].String()) == 0), nil
31 | }
32 |
33 | func contains(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
34 | return tree.Bool(strings.Contains(args[0].String(), args[1].String())), nil
35 | }
36 |
37 | func substringBefore(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
38 | ind := strings.Index(args[0].String(), args[1].String())
39 | if ind == -1 {
40 | return tree.String(""), nil
41 | }
42 |
43 | return tree.String(args[0].String()[:ind]), nil
44 | }
45 |
46 | func substringAfter(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
47 | ind := strings.Index(args[0].String(), args[1].String())
48 | if ind == -1 {
49 | return tree.String(""), nil
50 | }
51 |
52 | return tree.String(args[0].String()[ind+len(args[1].String()):]), nil
53 | }
54 |
55 | func substring(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
56 | str := args[0].String()
57 |
58 | bNum, bErr := round(c, args[1])
59 | if bErr != nil {
60 | return nil, bErr
61 | }
62 |
63 | b := bNum.(tree.Num).Num()
64 |
65 | if float64(b-1) >= float64(len(str)) || math.IsNaN(float64(b)) {
66 | return tree.String(""), nil
67 | }
68 |
69 | if len(args) == 2 {
70 | if b <= 1 {
71 | b = 1
72 | }
73 |
74 | return tree.String(str[int(b)-1:]), nil
75 | }
76 |
77 | eNum, eErr := round(c, args[2])
78 | if eErr != nil {
79 | return nil, eErr
80 | }
81 |
82 | e := eNum.(tree.Num).Num()
83 |
84 | if e <= 0 || math.IsNaN(float64(e)) || (math.IsInf(float64(b), 0) && math.IsInf(float64(e), 0)) {
85 | return tree.String(""), nil
86 | }
87 |
88 | if b <= 1 {
89 | e = b + e - 1
90 | b = 1
91 | }
92 |
93 | if float64(b+e-1) >= float64(len(str)) {
94 | e = tree.Num(len(str)) - b + 1
95 | }
96 |
97 | return tree.String(str[int(b)-1 : int(b+e)-1]), nil
98 | }
99 |
100 | func stringLength(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
101 | var str string
102 | if len(args) == 1 {
103 | str = args[0].String()
104 | } else {
105 | str = c.NodeSet.String()
106 | }
107 |
108 | return tree.Num(len(str)), nil
109 | }
110 |
111 | var spaceTrim = regexp.MustCompile(`\s+`)
112 |
113 | func normalizeSpace(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
114 | var str string
115 | if len(args) == 1 {
116 | str = args[0].String()
117 | } else {
118 | str = c.NodeSet.String()
119 | }
120 |
121 | str = strings.TrimSpace(str)
122 |
123 | return tree.String(spaceTrim.ReplaceAllString(str, " ")), nil
124 | }
125 |
126 | func translate(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
127 | ret := args[0].String()
128 | src := args[1].String()
129 | repl := args[2].String()
130 |
131 | for i := range src {
132 | r := ""
133 | if i < len(repl) {
134 | r = string(repl[i])
135 | }
136 |
137 | ret = strings.Replace(ret, string(src[i]), r, -1)
138 | }
139 |
140 | return tree.String(ret), nil
141 | }
142 |
--------------------------------------------------------------------------------
/tree/xmltree/xmltree.go:
--------------------------------------------------------------------------------
1 | package xmltree
2 |
3 | import (
4 | "encoding/xml"
5 | "io"
6 |
7 | "golang.org/x/net/html/charset"
8 |
9 | "github.com/ChrisTrenkamp/goxpath/tree"
10 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlbuilder"
11 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlele"
12 | )
13 |
14 | //ParseOptions is a set of methods and function pointers that alter
15 | //the way the XML decoder works and the Node types that are created.
16 | //Options that are not set will default to what is set in internal/defoverride.go
17 | type ParseOptions struct {
18 | Strict bool
19 | XMLRoot func() xmlbuilder.XMLBuilder
20 | }
21 |
22 | //DirectiveParser is an optional interface extended from XMLBuilder that handles
23 | //XML directives.
24 | type DirectiveParser interface {
25 | xmlbuilder.XMLBuilder
26 | Directive(xml.Directive, *xml.Decoder)
27 | }
28 |
29 | //ParseSettings is a function for setting the ParseOptions you want when
30 | //parsing an XML tree.
31 | type ParseSettings func(s *ParseOptions)
32 |
33 | //MustParseXML is like ParseXML, but panics instead of returning an error.
34 | func MustParseXML(r io.Reader, op ...ParseSettings) tree.Node {
35 | ret, err := ParseXML(r, op...)
36 |
37 | if err != nil {
38 | panic(err)
39 | }
40 |
41 | return ret
42 | }
43 |
44 | //ParseXML creates an XMLTree structure from an io.Reader.
45 | func ParseXML(r io.Reader, op ...ParseSettings) (tree.Node, error) {
46 | ov := ParseOptions{
47 | Strict: true,
48 | XMLRoot: xmlele.Root,
49 | }
50 | for _, i := range op {
51 | i(&ov)
52 | }
53 |
54 | dec := xml.NewDecoder(r)
55 | dec.CharsetReader = charset.NewReaderLabel
56 | dec.Strict = ov.Strict
57 |
58 | ordrPos := 1
59 | xmlTree := ov.XMLRoot()
60 |
61 | t, err := dec.Token()
62 |
63 | if err != nil {
64 | return nil, err
65 | }
66 |
67 | if head, ok := t.(xml.ProcInst); ok && head.Target == "xml" {
68 | t, err = dec.Token()
69 | }
70 |
71 | opts := xmlbuilder.BuilderOpts{
72 | Dec: dec,
73 | }
74 |
75 | for err == nil {
76 | switch xt := t.(type) {
77 | case xml.StartElement:
78 | setEle(&opts, xmlTree, xt, &ordrPos)
79 | xmlTree = xmlTree.CreateNode(&opts)
80 | case xml.CharData:
81 | setNode(&opts, xmlTree, xt, tree.NtChd, &ordrPos)
82 | xmlTree = xmlTree.CreateNode(&opts)
83 | case xml.Comment:
84 | setNode(&opts, xmlTree, xt, tree.NtComm, &ordrPos)
85 | xmlTree = xmlTree.CreateNode(&opts)
86 | case xml.ProcInst:
87 | setNode(&opts, xmlTree, xt, tree.NtPi, &ordrPos)
88 | xmlTree = xmlTree.CreateNode(&opts)
89 | case xml.EndElement:
90 | xmlTree = xmlTree.EndElem()
91 | case xml.Directive:
92 | if dp, ok := xmlTree.(DirectiveParser); ok {
93 | dp.Directive(xt.Copy(), dec)
94 | }
95 | }
96 |
97 | t, err = dec.Token()
98 | }
99 |
100 | if err == io.EOF {
101 | err = nil
102 | }
103 |
104 | return xmlTree, err
105 | }
106 |
107 | func setEle(opts *xmlbuilder.BuilderOpts, xmlTree xmlbuilder.XMLBuilder, ele xml.StartElement, ordrPos *int) {
108 | opts.NodePos = *ordrPos
109 | opts.Tok = ele
110 | opts.Attrs = opts.Attrs[0:0:cap(opts.Attrs)]
111 | opts.NS = make(map[xml.Name]string)
112 | opts.NodeType = tree.NtElem
113 |
114 | for i := range ele.Attr {
115 | attr := ele.Attr[i].Name
116 | val := ele.Attr[i].Value
117 |
118 | if (attr.Local == "xmlns" && attr.Space == "") || attr.Space == "xmlns" {
119 | opts.NS[attr] = val
120 | } else {
121 | opts.Attrs = append(opts.Attrs, &ele.Attr[i])
122 | }
123 | }
124 |
125 | if nstree, ok := xmlTree.(tree.NSElem); ok {
126 | ns := make(map[xml.Name]string)
127 |
128 | for _, i := range tree.BuildNS(nstree) {
129 | ns[i.Name] = i.Value
130 | }
131 |
132 | for k, v := range opts.NS {
133 | ns[k] = v
134 | }
135 |
136 | if ns[xml.Name{Local: "xmlns"}] == "" {
137 | delete(ns, xml.Name{Local: "xmlns"})
138 | }
139 |
140 | for k, v := range ns {
141 | opts.NS[k] = v
142 | }
143 |
144 | if xmlTree.GetNodeType() == tree.NtRoot {
145 | opts.NS[xml.Name{Space: "xmlns", Local: "xml"}] = tree.XMLSpace
146 | }
147 | }
148 |
149 | opts.AttrStartPos = len(opts.NS) + len(opts.Attrs) + *ordrPos
150 | *ordrPos = opts.AttrStartPos + 1
151 | }
152 |
153 | func setNode(opts *xmlbuilder.BuilderOpts, xmlTree xmlbuilder.XMLBuilder, tok xml.Token, nt tree.NodeType, ordrPos *int) {
154 | opts.Tok = xml.CopyToken(tok)
155 | opts.NodeType = nt
156 | opts.NodePos = *ordrPos
157 | *ordrPos++
158 | }
159 |
--------------------------------------------------------------------------------
/tree/xmlstruct/xmlele.go:
--------------------------------------------------------------------------------
1 | package xmlstruct
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 | "reflect"
7 | "strings"
8 |
9 | "github.com/ChrisTrenkamp/goxpath/tree"
10 | )
11 |
12 | type XMLEle struct {
13 | Val reflect.Value
14 | pos int
15 | prnt tree.Elem
16 | prntTag string
17 | }
18 |
19 | func (x *XMLEle) ResValue() string {
20 | if x.Val.Kind() != reflect.Struct {
21 | return fmt.Sprintf("%v", x.Val.Interface())
22 | }
23 |
24 | ret := ""
25 | for _, i := range x.GetChildren() {
26 | switch i.GetNodeType() {
27 | case tree.NtChd, tree.NtElem, tree.NtRoot:
28 | ret += i.ResValue()
29 | }
30 | }
31 | return ret
32 | }
33 |
34 | func (x *XMLEle) Pos() int {
35 | return x.pos
36 | }
37 |
38 | func (x *XMLEle) GetToken() xml.Token {
39 | ret := xml.StartElement{}
40 |
41 | if x.prntTag != "" {
42 | ret.Name = getTagInfo(x.prntTag).name
43 | } else {
44 | ret.Name = getXMLName(x.Val)
45 | }
46 |
47 | return ret
48 | }
49 |
50 | func (x *XMLEle) GetParent() tree.Elem {
51 | return x.prnt
52 | }
53 |
54 | func (x *XMLEle) GetNodeType() tree.NodeType {
55 | return tree.NtElem
56 | }
57 |
58 | func (x *XMLEle) GetChildren() []tree.Node {
59 | n, _ := getChildren(x, x.Val, x.pos, false)
60 | return n
61 | }
62 |
63 | func (x *XMLEle) GetAttrs() []tree.Node {
64 | n, _ := getChildren(x, x.Val, x.pos, true)
65 | return n
66 | }
67 |
68 | type tagInfo struct {
69 | name xml.Name
70 | attr bool
71 | cdata bool
72 | comment bool
73 | }
74 |
75 | func getTagInfo(tag string) (ret tagInfo) {
76 | spl := strings.Split(tag, ",")
77 | name := strings.Split(spl[0], " ")
78 | if len(spl) >= 2 {
79 | for i := 1; i < len(spl); i++ {
80 | if spl[i] == "chardata" || spl[i] == "cdata" {
81 | ret.cdata = true
82 | } else if spl[i] == "attr" {
83 | ret.attr = true
84 | } else if spl[i] == "comment" {
85 | ret.comment = true
86 | }
87 | }
88 | }
89 |
90 | if len(name) == 2 {
91 | ret.name.Space = name[1]
92 | }
93 |
94 | ret.name.Local = name[0]
95 | return
96 | }
97 |
98 | func getXMLName(val reflect.Value) xml.Name {
99 | n := val.FieldByName("XMLName")
100 | zero := reflect.Value{}
101 |
102 | if zero != n {
103 | field, _ := val.Type().FieldByName("XMLName")
104 | tagInfo := getTagInfo(field.Tag.Get("xml"))
105 | if tagInfo.name.Local != "" {
106 | return tagInfo.name
107 | }
108 |
109 | if name, ok := n.Interface().(xml.Name); ok {
110 | return name
111 | }
112 | }
113 |
114 | return xml.Name{Local: val.Type().Name()}
115 | }
116 |
117 | func getChildren(x *XMLEle, val reflect.Value, pos int, getAttrs bool) ([]tree.Node, int) {
118 | if val.Kind() == reflect.Ptr {
119 | val = val.Elem()
120 | }
121 |
122 | if val.Kind() == reflect.Interface {
123 | val = reflect.ValueOf(val.Interface())
124 | }
125 |
126 | if val.Kind() != reflect.Struct {
127 | if getAttrs {
128 | return []tree.Node{}, x.pos + 1
129 | }
130 |
131 | return []tree.Node{&XMLNode{
132 | Val: x.Val,
133 | pos: x.pos + 1,
134 | prnt: x,
135 | nodeType: tree.NtChd,
136 | prntTag: "",
137 | }}, x.pos + 2
138 | }
139 |
140 | ret := make([]tree.Node, 0, val.NumField())
141 |
142 | for i := 0; i < val.NumField(); i++ {
143 | field := val.Field(i)
144 | name := val.Type().Field(i).Name
145 |
146 | if val.Type().Field(i).Anonymous {
147 | nodes, newPos := getChildren(x, field, pos, getAttrs)
148 | ret = append(ret, nodes...)
149 | pos = newPos
150 | continue
151 | }
152 |
153 | tag := val.Type().Field(i).Tag.Get("xml")
154 |
155 | if tag == "-" || name == "XMLName" {
156 | continue
157 | }
158 |
159 | tagInfo := getTagInfo(tag)
160 |
161 | pos++
162 |
163 | if tagInfo.attr || tagInfo.cdata || tagInfo.comment {
164 | if !getAttrs && tagInfo.attr || getAttrs && !tagInfo.attr {
165 | continue
166 | }
167 |
168 | child := &XMLNode{
169 | Val: field,
170 | pos: pos,
171 | prnt: x,
172 | prntTag: tag,
173 | }
174 |
175 | if tagInfo.attr {
176 | child.nodeType = tree.NtAttr
177 | } else if tagInfo.cdata {
178 | child.nodeType = tree.NtChd
179 | } else {
180 | child.nodeType = tree.NtComm
181 | }
182 |
183 | if tagInfo.name.Local == "" {
184 | child.prntTag = name
185 | }
186 |
187 | ret = append(ret, child)
188 |
189 | continue
190 | }
191 |
192 | if getAttrs {
193 | continue
194 | }
195 |
196 | child := &XMLEle{Val: field, pos: pos}
197 |
198 | if tag == "" {
199 | tag = name
200 | }
201 |
202 | child.prntTag = tag
203 | ret = append(ret, child)
204 | }
205 |
206 | return ret, pos + 1
207 | }
208 |
--------------------------------------------------------------------------------
/parser/parser.go:
--------------------------------------------------------------------------------
1 | package parser
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/lexer"
7 | )
8 |
9 | type stateType int
10 |
11 | const (
12 | defState stateType = iota
13 | xpathState
14 | funcState
15 | paramState
16 | predState
17 | parenState
18 | )
19 |
20 | type parseStack struct {
21 | stack []*Node
22 | stateTypes []stateType
23 | cur *Node
24 | }
25 |
26 | func (p *parseStack) push(t stateType) {
27 | p.stack = append(p.stack, p.cur)
28 | p.stateTypes = append(p.stateTypes, t)
29 | }
30 |
31 | func (p *parseStack) pop() {
32 | stackPos := len(p.stack) - 1
33 |
34 | p.cur = p.stack[stackPos]
35 | p.stack = p.stack[:stackPos]
36 | p.stateTypes = p.stateTypes[:stackPos]
37 | }
38 |
39 | func (p *parseStack) curState() stateType {
40 | if len(p.stateTypes) == 0 {
41 | return defState
42 | }
43 | return p.stateTypes[len(p.stateTypes)-1]
44 | }
45 |
46 | type lexFn func(*parseStack, lexer.XItem)
47 |
48 | var parseMap = map[lexer.XItemType]lexFn{
49 | lexer.XItemAbsLocPath: xiXPath,
50 | lexer.XItemAbbrAbsLocPath: xiXPath,
51 | lexer.XItemAbbrRelLocPath: xiXPath,
52 | lexer.XItemRelLocPath: xiXPath,
53 | lexer.XItemEndPath: xiEndPath,
54 | lexer.XItemAxis: xiXPath,
55 | lexer.XItemAbbrAxis: xiXPath,
56 | lexer.XItemNCName: xiXPath,
57 | lexer.XItemQName: xiXPath,
58 | lexer.XItemNodeType: xiXPath,
59 | lexer.XItemProcLit: xiXPath,
60 | lexer.XItemFunction: xiFunc,
61 | lexer.XItemArgument: xiFuncArg,
62 | lexer.XItemEndFunction: xiEndFunc,
63 | lexer.XItemPredicate: xiPred,
64 | lexer.XItemEndPredicate: xiEndPred,
65 | lexer.XItemStrLit: xiValue,
66 | lexer.XItemNumLit: xiValue,
67 | lexer.XItemOperator: xiOp,
68 | lexer.XItemVariable: xiValue,
69 | }
70 |
71 | var opPrecedence = map[string]int{
72 | "|": 1,
73 | "*": 2,
74 | "div": 2,
75 | "mod": 2,
76 | "+": 3,
77 | "-": 3,
78 | "=": 4,
79 | "!=": 4,
80 | "<": 4,
81 | "<=": 4,
82 | ">": 4,
83 | ">=": 4,
84 | "and": 5,
85 | "or": 6,
86 | }
87 |
88 | //Parse creates an AST tree for XPath expressions.
89 | func Parse(xp string) (*Node, error) {
90 | var err error
91 | c := lexer.Lex(xp)
92 | n := &Node{}
93 | p := &parseStack{cur: n}
94 |
95 | for next := range c {
96 | if next.Typ != lexer.XItemError {
97 | parseMap[next.Typ](p, next)
98 | } else if err == nil {
99 | err = fmt.Errorf(next.Val)
100 | }
101 | }
102 |
103 | return n, err
104 | }
105 |
106 | func xiXPath(p *parseStack, i lexer.XItem) {
107 | if p.curState() == xpathState {
108 | p.cur.push(i)
109 | p.cur = p.cur.next
110 | return
111 | }
112 |
113 | if p.cur.Val.Typ == lexer.XItemFunction {
114 | p.cur.Right = &Node{Val: i, Parent: p.cur}
115 | p.cur.next = p.cur.Right
116 | p.push(xpathState)
117 | p.cur = p.cur.next
118 | return
119 | }
120 |
121 | p.cur.pushNotEmpty(i)
122 | p.push(xpathState)
123 | p.cur = p.cur.next
124 | }
125 |
126 | func xiEndPath(p *parseStack, i lexer.XItem) {
127 | p.pop()
128 | }
129 |
130 | func xiFunc(p *parseStack, i lexer.XItem) {
131 | if p.cur.Val.Typ == Empty {
132 | p.cur.pushNotEmpty(i)
133 | p.push(funcState)
134 | p.cur = p.cur.next
135 | return
136 | }
137 | p.cur.push(i)
138 | p.cur = p.cur.next
139 | p.push(funcState)
140 | }
141 |
142 | func xiFuncArg(p *parseStack, i lexer.XItem) {
143 | if p.curState() != funcState {
144 | p.pop()
145 | }
146 |
147 | p.cur.push(i)
148 | p.cur = p.cur.next
149 | p.push(paramState)
150 | p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
151 | p.cur = p.cur.next
152 | }
153 |
154 | func xiEndFunc(p *parseStack, i lexer.XItem) {
155 | if p.curState() == paramState {
156 | p.pop()
157 | }
158 | p.pop()
159 | }
160 |
161 | func xiPred(p *parseStack, i lexer.XItem) {
162 | p.cur.push(i)
163 | p.cur = p.cur.next
164 | p.push(predState)
165 | p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
166 | p.cur = p.cur.next
167 | }
168 |
169 | func xiEndPred(p *parseStack, i lexer.XItem) {
170 | p.pop()
171 | }
172 |
173 | func xiValue(p *parseStack, i lexer.XItem) {
174 | p.cur.add(i)
175 | }
176 |
177 | func xiOp(p *parseStack, i lexer.XItem) {
178 | if i.Val == "(" {
179 | p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
180 | p.push(parenState)
181 | p.cur = p.cur.next
182 | return
183 | }
184 |
185 | if i.Val == ")" {
186 | p.pop()
187 | return
188 | }
189 |
190 | if p.cur.Val.Typ == lexer.XItemOperator {
191 | if opPrecedence[p.cur.Val.Val] <= opPrecedence[i.Val] {
192 | p.cur.add(i)
193 | } else {
194 | p.cur.push(i)
195 | }
196 | } else {
197 | p.cur.add(i)
198 | }
199 | p.cur = p.cur.next
200 | }
201 |
--------------------------------------------------------------------------------
/cmd/goxpath/goxpath.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "flag"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "os"
10 | "path/filepath"
11 | "strings"
12 |
13 | "github.com/ChrisTrenkamp/goxpath"
14 | "github.com/ChrisTrenkamp/goxpath/tree"
15 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree"
16 | )
17 |
18 | type namespace map[string]string
19 |
20 | func (n *namespace) String() string {
21 | return fmt.Sprint(*n)
22 | }
23 |
24 | func (n *namespace) Set(value string) error {
25 | nsMap := strings.Split(value, "=")
26 | if len(nsMap) != 2 {
27 | nsErr = fmt.Errorf("Invalid namespace mapping: %s\n", value)
28 | }
29 | (*n)[nsMap[0]] = nsMap[1]
30 | return nil
31 | }
32 |
33 | type variables map[string]string
34 |
35 | func (v *variables) String() string {
36 | return fmt.Sprint(*v)
37 | }
38 |
39 | func (v *variables) Set(value string) error {
40 | varMap := strings.Split(value, "=")
41 | if len(varMap) != 2 {
42 | nsErr = fmt.Errorf("Invalid variable mapping: %s\n", value)
43 | }
44 | (*v)[varMap[0]] = varMap[1]
45 | return nil
46 | }
47 |
48 | var rec bool
49 | var value bool
50 | var ns = make(namespace)
51 | var vars = make(variables)
52 | var nsErr error
53 | var unstrict bool
54 | var noFileName bool
55 | var args = []string{}
56 | var stdin io.Reader = os.Stdin
57 | var stdout io.ReadWriter = os.Stdout
58 | var stderr io.ReadWriter = os.Stderr
59 |
60 | var retCode = 0
61 |
62 | func main() {
63 | exec()
64 | os.Exit(retCode)
65 | }
66 |
67 | func exec() {
68 | flag.BoolVar(&rec, "r", false, "Recursive")
69 | flag.BoolVar(&value, "v", false, "Output the string value of the XPath result")
70 | flag.Var(&ns, "ns", "Namespace mappings. e.g. -ns myns=http://example.com")
71 | flag.Var(&vars, "var", "Variables mappings. e.g. -var myvar=myvalue")
72 | flag.BoolVar(&unstrict, "u", false, "Turns off strict XML validation")
73 | flag.BoolVar(&noFileName, "h", false, "Suppress filename prefixes.")
74 | flag.Parse()
75 | args = flag.Args()
76 |
77 | if nsErr != nil {
78 | fmt.Fprintf(stderr, nsErr.Error())
79 | retCode = 1
80 | return
81 | }
82 |
83 | if len(args) < 1 {
84 | fmt.Fprintf(stderr, "Specify an XPath expression with one or more files, or pipe the XML from stdin. Run 'goxpath --help' for more information.\n")
85 | retCode = 1
86 | return
87 | }
88 |
89 | xp, err := goxpath.Parse(args[0])
90 |
91 | if err != nil {
92 | fmt.Fprintf(stderr, "%s\n", err.Error())
93 | retCode = 1
94 | return
95 | }
96 |
97 | if len(args) == 1 {
98 | ret, err := runXPath(xp, stdin, ns, value)
99 | if err != nil {
100 | fmt.Fprintf(stderr, "%s\n", err.Error())
101 | retCode = 1
102 | }
103 |
104 | printResult(ret, "")
105 | }
106 |
107 | for i := 1; i < len(args); i++ {
108 | procPath(args[i], xp, ns, value)
109 | }
110 | }
111 |
112 | func procPath(path string, x goxpath.XPathExec, ns namespace, value bool) {
113 | fi, err := os.Stat(path)
114 | if err != nil {
115 | fmt.Fprintf(stderr, "Could not open file: %s\n", path)
116 | retCode = 1
117 | return
118 | }
119 |
120 | if fi.IsDir() {
121 | procDir(path, x, ns, value)
122 | return
123 | }
124 |
125 | data, _ := ioutil.ReadFile(path)
126 | ret, err := runXPath(x, bytes.NewBuffer(data), ns, value)
127 | if err != nil {
128 | fmt.Fprintf(stderr, "%s: %s\n", path, err.Error())
129 | retCode = 1
130 | }
131 |
132 | printResult(ret, path)
133 | }
134 |
135 | func printResult(ret []string, path string) {
136 | for _, j := range ret {
137 | if (len(flag.Args()) > 2 || rec) && !noFileName {
138 | fmt.Fprintf(stdout, "%s:", path)
139 | }
140 |
141 | fmt.Fprintf(stdout, "%s\n", j)
142 | }
143 | }
144 |
145 | func procDir(path string, x goxpath.XPathExec, ns namespace, value bool) {
146 | if !rec {
147 | fmt.Fprintf(stderr, "%s: Is a directory\n", path)
148 | retCode = 1
149 | return
150 | }
151 |
152 | list, _ := ioutil.ReadDir(path)
153 |
154 | for _, i := range list {
155 | procPath(filepath.Join(path, i.Name()), x, ns, value)
156 | }
157 | }
158 |
159 | func runXPath(x goxpath.XPathExec, r io.Reader, ns namespace, value bool) ([]string, error) {
160 | t, err := xmltree.ParseXML(r, func(o *xmltree.ParseOptions) {
161 | o.Strict = !unstrict
162 | })
163 |
164 | if err != nil {
165 | return nil, err
166 | }
167 |
168 | res, err := x.Exec(t, func(o *goxpath.Opts) {
169 | o.NS = ns
170 | for k, v := range vars {
171 | o.Vars[k] = tree.String(v)
172 | }
173 | })
174 |
175 | if err != nil {
176 | return nil, err
177 | }
178 |
179 | var ret []string
180 |
181 | if nodes, ok := res.(tree.NodeSet); ok && !value {
182 | ret = make([]string, len(nodes))
183 | for i, v := range nodes {
184 | ret[i], _ = goxpath.MarshalStr(v)
185 | ret[i] = strings.Replace(ret[i], "\n", "
", -1)
186 | }
187 | } else {
188 | str := res.String()
189 | if str != "" {
190 | ret = strings.Split(str, "\n")
191 | }
192 | }
193 |
194 | return ret, nil
195 | }
196 |
--------------------------------------------------------------------------------
/internal/execxp/operators.go:
--------------------------------------------------------------------------------
1 | package execxp
2 |
3 | import (
4 | "fmt"
5 | "math"
6 |
7 | "github.com/ChrisTrenkamp/goxpath/tree"
8 | )
9 |
10 | func bothNodeOperator(left tree.NodeSet, right tree.NodeSet, f *xpFilt, op string) error {
11 | var err error
12 | for _, l := range left {
13 | for _, r := range right {
14 | lStr := l.ResValue()
15 | rStr := r.ResValue()
16 |
17 | if eqOps[op] {
18 | err = equalsOperator(tree.String(lStr), tree.String(rStr), f, op)
19 | if err == nil && f.ctx.String() == tree.True {
20 | return nil
21 | }
22 | } else {
23 | err = numberOperator(tree.String(lStr), tree.String(rStr), f, op)
24 | if err == nil && f.ctx.String() == tree.True {
25 | return nil
26 | }
27 | }
28 | }
29 | }
30 |
31 | f.ctx = tree.Bool(false)
32 |
33 | return nil
34 | }
35 |
36 | func leftNodeOperator(left tree.NodeSet, right tree.Result, f *xpFilt, op string) error {
37 | var err error
38 | for _, l := range left {
39 | lStr := l.ResValue()
40 |
41 | if eqOps[op] {
42 | err = equalsOperator(tree.String(lStr), right, f, op)
43 | if err == nil && f.ctx.String() == tree.True {
44 | return nil
45 | }
46 | } else {
47 | err = numberOperator(tree.String(lStr), right, f, op)
48 | if err == nil && f.ctx.String() == tree.True {
49 | return nil
50 | }
51 | }
52 | }
53 |
54 | f.ctx = tree.Bool(false)
55 |
56 | return nil
57 | }
58 |
59 | func rightNodeOperator(left tree.Result, right tree.NodeSet, f *xpFilt, op string) error {
60 | var err error
61 | for _, r := range right {
62 | rStr := r.ResValue()
63 |
64 | if eqOps[op] {
65 | err = equalsOperator(left, tree.String(rStr), f, op)
66 | if err == nil && f.ctx.String() == "true" {
67 | return nil
68 | }
69 | } else {
70 | err = numberOperator(left, tree.String(rStr), f, op)
71 | if err == nil && f.ctx.String() == "true" {
72 | return nil
73 | }
74 | }
75 | }
76 |
77 | f.ctx = tree.Bool(false)
78 |
79 | return nil
80 | }
81 |
82 | func equalsOperator(left, right tree.Result, f *xpFilt, op string) error {
83 | _, lOK := left.(tree.Bool)
84 | _, rOK := right.(tree.Bool)
85 |
86 | if lOK || rOK {
87 | lTest, lt := left.(tree.IsBool)
88 | rTest, rt := right.(tree.IsBool)
89 | if !lt || !rt {
90 | return fmt.Errorf("Cannot convert argument to boolean")
91 | }
92 |
93 | if op == "=" {
94 | f.ctx = tree.Bool(lTest.Bool() == rTest.Bool())
95 | } else {
96 | f.ctx = tree.Bool(lTest.Bool() != rTest.Bool())
97 | }
98 |
99 | return nil
100 | }
101 |
102 | _, lOK = left.(tree.Num)
103 | _, rOK = right.(tree.Num)
104 | if lOK || rOK {
105 | return numberOperator(left, right, f, op)
106 | }
107 |
108 | lStr := left.String()
109 | rStr := right.String()
110 |
111 | if op == "=" {
112 | f.ctx = tree.Bool(lStr == rStr)
113 | } else {
114 | f.ctx = tree.Bool(lStr != rStr)
115 | }
116 |
117 | return nil
118 | }
119 |
120 | func numberOperator(left, right tree.Result, f *xpFilt, op string) error {
121 | lt, lOK := left.(tree.IsNum)
122 | rt, rOK := right.(tree.IsNum)
123 | if !lOK || !rOK {
124 | return fmt.Errorf("Cannot convert data type to number")
125 | }
126 |
127 | ln, rn := lt.Num(), rt.Num()
128 |
129 | switch op {
130 | case "*":
131 | f.ctx = ln * rn
132 | case "div":
133 | if rn != 0 {
134 | f.ctx = ln / rn
135 | } else {
136 | if ln == 0 {
137 | f.ctx = tree.Num(math.NaN())
138 | } else {
139 | if math.Signbit(float64(ln)) == math.Signbit(float64(rn)) {
140 | f.ctx = tree.Num(math.Inf(1))
141 | } else {
142 | f.ctx = tree.Num(math.Inf(-1))
143 | }
144 | }
145 | }
146 | case "mod":
147 | f.ctx = tree.Num(int(ln) % int(rn))
148 | case "+":
149 | f.ctx = ln + rn
150 | case "-":
151 | f.ctx = ln - rn
152 | case "=":
153 | f.ctx = tree.Bool(ln == rn)
154 | case "!=":
155 | f.ctx = tree.Bool(ln != rn)
156 | case "<":
157 | f.ctx = tree.Bool(ln < rn)
158 | case "<=":
159 | f.ctx = tree.Bool(ln <= rn)
160 | case ">":
161 | f.ctx = tree.Bool(ln > rn)
162 | case ">=":
163 | f.ctx = tree.Bool(ln >= rn)
164 | }
165 |
166 | return nil
167 | }
168 |
169 | func andOrOperator(left, right tree.Result, f *xpFilt, op string) error {
170 | lt, lOK := left.(tree.IsBool)
171 | rt, rOK := right.(tree.IsBool)
172 |
173 | if !lOK || !rOK {
174 | return fmt.Errorf("Cannot convert argument to boolean")
175 | }
176 |
177 | l, r := lt.Bool(), rt.Bool()
178 |
179 | if op == "and" {
180 | f.ctx = l && r
181 | } else {
182 | f.ctx = l || r
183 | }
184 |
185 | return nil
186 | }
187 |
188 | func unionOperator(left, right tree.Result, f *xpFilt, op string) error {
189 | lNode, lOK := left.(tree.NodeSet)
190 | rNode, rOK := right.(tree.NodeSet)
191 |
192 | if !lOK || !rOK {
193 | return fmt.Errorf("Cannot convert data type to node-set")
194 | }
195 |
196 | uniq := make(map[int]tree.Node)
197 | for _, i := range lNode {
198 | uniq[i.Pos()] = i
199 | }
200 | for _, i := range rNode {
201 | uniq[i.Pos()] = i
202 | }
203 |
204 | res := make(tree.NodeSet, 0, len(uniq))
205 | for _, v := range uniq {
206 | res = append(res, v)
207 | }
208 |
209 | f.ctx = res
210 |
211 | return nil
212 | }
213 |
--------------------------------------------------------------------------------
/cmd/goxpath/goxpath_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "flag"
7 | "os"
8 | "strings"
9 | "testing"
10 | )
11 |
12 | func setup(in string, args ...string) (*bytes.Buffer, *bytes.Buffer) {
13 | retCode = 0
14 | os.Args = append([]string{"test"}, args...)
15 | flag.CommandLine = flag.NewFlagSet("test", flag.ExitOnError)
16 | out := &bytes.Buffer{}
17 | err := &bytes.Buffer{}
18 | stdout = out
19 | stderr = err
20 | stdin = strings.NewReader(in)
21 | exec()
22 | return out, err
23 | }
24 |
25 | func TestStdinVal(t *testing.T) {
26 | out, _ := setup(xml.Header+"test", "-v", "/root/tag")
27 | if out.String() != "test\n" {
28 | t.Error("Expecting 'test' for the result. Recieved: ", out.String())
29 | }
30 | if retCode != 0 {
31 | t.Error("Incorrect return value")
32 | }
33 | }
34 |
35 | func TestStdinNonVal(t *testing.T) {
36 | out, _ := setup(xml.Header+"test", "/root/tag")
37 | if out.String() != "test\n" {
38 | t.Error("Expecting 'test' for the result. Recieved: ", out.String())
39 | }
40 | if retCode != 0 {
41 | t.Error("Incorrect return value")
42 | }
43 | }
44 |
45 | func TestFile(t *testing.T) {
46 | out, _ := setup("", "-ns", "foo=http://foo.bar", "/foo:test/foo:path", "test/1.xml")
47 | if out.String() != `path`+"\n" {
48 | t.Error(`Expecting 'path' for the result. Recieved: `, out.String())
49 | }
50 | if retCode != 0 {
51 | t.Error("Incorrect return value")
52 | }
53 | }
54 |
55 | func TestDir(t *testing.T) {
56 | out, _ := setup("", "-r", "/foo", "test/subdir")
57 | val := strings.Replace(out.String(), "test\\subdir\\", "test/subdir/", -1)
58 | if val != `test/subdir/2.xml:bar`+"\n"+`test/subdir/3.xml:bar2`+"\n" {
59 | t.Error(`Incorrect result. Recieved: `, val)
60 | }
61 | if retCode != 0 {
62 | t.Error("Incorrect return value")
63 | }
64 | }
65 |
66 | func TestDirNonRec(t *testing.T) {
67 | _, err := setup("", "/foo", "test/subdir")
68 | val := strings.Replace(err.String(), "test\\subdir\\", "test/subdir/", -1)
69 | if val != `test/subdir: Is a directory`+"\n" {
70 | t.Error(`Incorrect result. Recieved: `, val)
71 | }
72 | if retCode != 1 {
73 | t.Error("Incorrect return value")
74 | }
75 | }
76 |
77 | func TestNoXPath(t *testing.T) {
78 | _, err := setup("")
79 | if err.String() != "Specify an XPath expression with one or more files, or pipe the XML from stdin. Run 'goxpath --help' for more information.\n" {
80 | t.Error("No XPath error")
81 | }
82 | if retCode != 1 {
83 | t.Error("Incorrect return value")
84 | }
85 | }
86 |
87 | func TestInvalidXPathExpr(t *testing.T) {
88 | _, err := setup("", "/foo()", "test/1.xml")
89 | if err.String() != "Invalid node-type foo\n" {
90 | t.Error("Invalid XPath error")
91 | }
92 | if retCode != 1 {
93 | t.Error("Incorrect return value")
94 | }
95 | }
96 |
97 | func TestInvalidFilePath(t *testing.T) {
98 | _, err := setup("", "/foo", "foo.xml")
99 | if err.String() != "Could not open file: foo.xml\n" {
100 | t.Error("Invalid error")
101 | }
102 | if retCode != 1 {
103 | t.Error("Incorrect return value")
104 | }
105 | }
106 |
107 | func TestXPathExecErr(t *testing.T) {
108 | _, err := setup("", "foobar()", "test/1.xml")
109 | if err.String() != "test/1.xml: Unknown function: foobar\n" {
110 | t.Error("Invalid error", err.String())
111 | }
112 | if retCode != 1 {
113 | t.Error("Incorrect return value")
114 | }
115 | }
116 |
117 | func TestXPathExecErrStdin(t *testing.T) {
118 | _, err := setup(xml.Header+"test", "foobar()")
119 | if err.String() != "Unknown function: foobar\n" {
120 | t.Error("Invalid error", err.String())
121 | }
122 | if retCode != 1 {
123 | t.Error("Incorrect return value")
124 | }
125 | }
126 |
127 | func TestInvalidXML(t *testing.T) {
128 | _, err := setup("", "/root")
129 | if err.String() != "XML syntax error on line 1: unexpected EOF\n" {
130 | t.Error("Invalid error", err.String())
131 | }
132 | if retCode != 1 {
133 | t.Error("Incorrect return value")
134 | }
135 | }
136 |
137 | func TestVarRef(t *testing.T) {
138 | out, _ := setup(xml.Header+"test", "-var=foo=test", "/root/tag = $foo")
139 | if out.String() != "true\n" {
140 | t.Error("Expecting 'true' for the result. Recieved: ", out.String())
141 | }
142 | if retCode != 0 {
143 | t.Error("Incorrect return value")
144 | }
145 | }
146 |
147 | func TestInvalidNSMap(t *testing.T) {
148 | _, err := setup(xml.Header+"", "-ns=foo=http://foo=bar", "/root")
149 | if err.String() != "Invalid namespace mapping: foo=http://foo=bar\n" {
150 | t.Error("Invalid error", err.String())
151 | }
152 | if retCode != 1 {
153 | t.Error("Incorrect return value")
154 | }
155 | }
156 |
157 | func TestInvalidVarMap(t *testing.T) {
158 | _, err := setup(xml.Header+"", "-var=test=blag=foo", "/root")
159 | if err.String() != "Invalid variable mapping: test=blag=foo\n" {
160 | t.Error("Invalid error", err.String())
161 | }
162 | if retCode != 1 {
163 | t.Error("Incorrect return value")
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/lexer/paths.go:
--------------------------------------------------------------------------------
1 | package lexer
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/xconst"
7 | )
8 |
9 | func absLocPathState(l *Lexer) stateFn {
10 | l.emit(XItemAbsLocPath)
11 | return stepState
12 | }
13 |
14 | func abbrAbsLocPathState(l *Lexer) stateFn {
15 | l.emit(XItemAbbrAbsLocPath)
16 | return stepState
17 | }
18 |
19 | func relLocPathState(l *Lexer) stateFn {
20 | l.emit(XItemRelLocPath)
21 | return stepState
22 | }
23 |
24 | func abbrRelLocPathState(l *Lexer) stateFn {
25 | l.emit(XItemAbbrRelLocPath)
26 | return stepState
27 | }
28 |
29 | func stepState(l *Lexer) stateFn {
30 | l.skipWS(true)
31 | r := l.next()
32 |
33 | for isElemChar(r) {
34 | r = l.next()
35 | }
36 |
37 | l.backup()
38 | tok := l.input[l.start:l.pos]
39 |
40 | state, err := parseSeparators(l, tok)
41 | if err != nil {
42 | return l.errorf(err.Error())
43 | }
44 |
45 | return getNextPathState(l, state)
46 | }
47 |
48 | func parseSeparators(l *Lexer, tok string) (XItemType, error) {
49 | l.skipWS(false)
50 | state := XItemType(XItemQName)
51 | r := l.peek()
52 |
53 | if string(r) == ":" && string(l.peekAt(2)) == ":" {
54 | var err error
55 | if state, err = getAxis(l, tok); err != nil {
56 | return state, fmt.Errorf(err.Error())
57 | }
58 | } else if string(r) == ":" {
59 | state = XItemNCName
60 | l.emitVal(state, tok)
61 | l.skip(1)
62 | l.skipWS(true)
63 | } else if string(r) == "@" {
64 | state = XItemAbbrAxis
65 | l.emitVal(state, tok)
66 | l.skip(1)
67 | l.skipWS(true)
68 | } else if string(r) == "(" {
69 | var err error
70 | if state, err = getNT(l, tok); err != nil {
71 | return state, fmt.Errorf(err.Error())
72 | }
73 | } else if len(tok) > 0 {
74 | l.emitVal(state, tok)
75 | }
76 |
77 | return state, nil
78 | }
79 |
80 | func getAxis(l *Lexer, tok string) (XItemType, error) {
81 | var state XItemType
82 | for i := range xconst.AxisNames {
83 | if tok == xconst.AxisNames[i] {
84 | state = XItemAxis
85 | }
86 | }
87 | if state != XItemAxis {
88 | return state, fmt.Errorf("Invalid Axis specifier, %s", tok)
89 | }
90 | l.emitVal(state, tok)
91 | l.skip(2)
92 | l.skipWS(true)
93 | return state, nil
94 | }
95 |
96 | func getNT(l *Lexer, tok string) (XItemType, error) {
97 | isNT := false
98 | for _, i := range xconst.NodeTypes {
99 | if tok == i {
100 | isNT = true
101 | break
102 | }
103 | }
104 |
105 | if isNT {
106 | return procNT(l, tok)
107 | }
108 |
109 | return XItemError, fmt.Errorf("Invalid node-type " + tok)
110 | }
111 |
112 | func procNT(l *Lexer, tok string) (XItemType, error) {
113 | state := XItemType(XItemNodeType)
114 | l.emitVal(state, tok)
115 | l.skip(1)
116 | l.skipWS(true)
117 | n := l.peek()
118 | if tok == xconst.NodeTypeProcInst && (string(n) == `"` || string(n) == `'`) {
119 | if err := getStrLit(l, XItemProcLit); err != nil {
120 | return state, fmt.Errorf(err.Error())
121 | }
122 | l.skipWS(true)
123 | n = l.next()
124 | }
125 |
126 | if string(n) != ")" {
127 | return state, fmt.Errorf("Missing ) at end of NodeType declaration.")
128 | }
129 |
130 | l.skip(1)
131 | return state, nil
132 | }
133 |
134 | func procFunc(l *Lexer, tok string) error {
135 | state := XItemType(XItemFunction)
136 | l.emitVal(state, tok)
137 | l.skip(1)
138 | l.skipWS(true)
139 | if string(l.peek()) != ")" {
140 | l.emit(XItemArgument)
141 | for {
142 | for state := startState; state != nil; {
143 | state = state(l)
144 | }
145 | l.skipWS(true)
146 |
147 | if string(l.peek()) == "," {
148 | l.emit(XItemArgument)
149 | l.skip(1)
150 | } else if string(l.peek()) == ")" {
151 | l.emit(XItemEndFunction)
152 | l.skip(1)
153 | break
154 | } else if l.peek() == eof {
155 | return fmt.Errorf("Missing ) at end of function declaration.")
156 | }
157 | }
158 | } else {
159 | l.emit(XItemEndFunction)
160 | l.skip(1)
161 | }
162 |
163 | return nil
164 | }
165 |
166 | func getNextPathState(l *Lexer, state XItemType) stateFn {
167 | isMultiPart := state == XItemAxis || state == XItemAbbrAxis || state == XItemNCName
168 |
169 | l.skipWS(true)
170 |
171 | for string(l.peek()) == "[" {
172 | if err := getPred(l); err != nil {
173 | return l.errorf(err.Error())
174 | }
175 | }
176 |
177 | if string(l.peek()) == "/" && !isMultiPart {
178 | l.skip(1)
179 | if string(l.peek()) == "/" {
180 | l.skip(1)
181 | return abbrRelLocPathState
182 | }
183 | l.skipWS(true)
184 | return relLocPathState
185 | } else if isMultiPart && isElemChar(l.peek()) {
186 | return stepState
187 | }
188 |
189 | if isMultiPart {
190 | return l.errorf("Step is not complete")
191 | }
192 |
193 | l.emit(XItemEndPath)
194 | return findOperatorState
195 | }
196 |
197 | func getPred(l *Lexer) error {
198 | l.emit(XItemPredicate)
199 | l.skip(1)
200 | l.skipWS(true)
201 |
202 | if string(l.peek()) == "]" {
203 | return fmt.Errorf("Missing content in predicate.")
204 | }
205 |
206 | for state := startState; state != nil; {
207 | state = state(l)
208 | }
209 |
210 | l.skipWS(true)
211 | if string(l.peek()) != "]" {
212 | return fmt.Errorf("Missing ] at end of predicate.")
213 | }
214 | l.skip(1)
215 | l.emit(XItemEndPredicate)
216 | l.skipWS(true)
217 |
218 | return nil
219 | }
220 |
--------------------------------------------------------------------------------
/tree/tree.go:
--------------------------------------------------------------------------------
1 | package tree
2 |
3 | import (
4 | "encoding/xml"
5 | "sort"
6 | )
7 |
8 | //XMLSpace is the W3C XML namespace
9 | const XMLSpace = "http://www.w3.org/XML/1998/namespace"
10 |
11 | //NodePos is a helper for representing the node's document order
12 | type NodePos int
13 |
14 | //Pos returns the node's document order position
15 | func (n NodePos) Pos() int {
16 | return int(n)
17 | }
18 |
19 | //NodeType is a safer way to determine a node's type than type assertions.
20 | type NodeType int
21 |
22 | //GetNodeType returns the node's type.
23 | func (t NodeType) GetNodeType() NodeType {
24 | return t
25 | }
26 |
27 | //These are all the possible node types
28 | const (
29 | NtAttr NodeType = iota
30 | NtChd
31 | NtComm
32 | NtElem
33 | NtNs
34 | NtRoot
35 | NtPi
36 | )
37 |
38 | //Node is a XPath result that is a node except elements
39 | type Node interface {
40 | //ResValue prints the node's string value
41 | ResValue() string
42 | //Pos returns the node's position in the document order
43 | Pos() int
44 | //GetToken returns the xml.Token representation of the node
45 | GetToken() xml.Token
46 | //GetParent returns the parent node, which will always be an XML element
47 | GetParent() Elem
48 | //GetNodeType returns the node's type
49 | GetNodeType() NodeType
50 | }
51 |
52 | //Elem is a XPath result that is an element node
53 | type Elem interface {
54 | Node
55 | //GetChildren returns the elements children.
56 | GetChildren() []Node
57 | //GetAttrs returns the attributes of the element
58 | GetAttrs() []Node
59 | }
60 |
61 | //NSElem is a node that keeps track of namespaces.
62 | type NSElem interface {
63 | Elem
64 | GetNS() map[xml.Name]string
65 | }
66 |
67 | //NSBuilder is a helper-struct for satisfying the NSElem interface
68 | type NSBuilder struct {
69 | NS map[xml.Name]string
70 | }
71 |
72 | //GetNS returns the namespaces found on the current element. It should not be
73 | //confused with BuildNS, which actually resolves the namespace nodes.
74 | func (ns NSBuilder) GetNS() map[xml.Name]string {
75 | return ns.NS
76 | }
77 |
78 | type nsValueSort []NS
79 |
80 | func (ns nsValueSort) Len() int { return len(ns) }
81 | func (ns nsValueSort) Swap(i, j int) {
82 | ns[i], ns[j] = ns[j], ns[i]
83 | }
84 | func (ns nsValueSort) Less(i, j int) bool {
85 | return ns[i].Value < ns[j].Value
86 | }
87 |
88 | //BuildNS resolves all the namespace nodes of the element and returns them
89 | func BuildNS(t Elem) (ret []NS) {
90 | vals := make(map[xml.Name]string)
91 |
92 | if nselem, ok := t.(NSElem); ok {
93 | buildNS(nselem, vals)
94 |
95 | ret = make([]NS, 0, len(vals))
96 | i := 1
97 |
98 | for k, v := range vals {
99 | if !(k.Local == "xmlns" && k.Space == "" && v == "") {
100 | ret = append(ret, NS{
101 | Attr: xml.Attr{Name: k, Value: v},
102 | Parent: t,
103 | NodeType: NtNs,
104 | })
105 | i++
106 | }
107 | }
108 |
109 | sort.Sort(nsValueSort(ret))
110 | for i := range ret {
111 | ret[i].NodePos = NodePos(t.Pos() + i + 1)
112 | }
113 | }
114 |
115 | return ret
116 | }
117 |
118 | func buildNS(x NSElem, ret map[xml.Name]string) {
119 | if x.GetNodeType() == NtRoot {
120 | return
121 | }
122 |
123 | if nselem, ok := x.GetParent().(NSElem); ok {
124 | buildNS(nselem, ret)
125 | }
126 |
127 | for k, v := range x.GetNS() {
128 | ret[k] = v
129 | }
130 | }
131 |
132 | //NS is a namespace node.
133 | type NS struct {
134 | xml.Attr
135 | Parent Elem
136 | NodePos
137 | NodeType
138 | }
139 |
140 | //GetToken returns the xml.Token representation of the namespace.
141 | func (ns NS) GetToken() xml.Token {
142 | return ns.Attr
143 | }
144 |
145 | //GetParent returns the parent node of the namespace.
146 | func (ns NS) GetParent() Elem {
147 | return ns.Parent
148 | }
149 |
150 | //ResValue returns the string value of the namespace
151 | func (ns NS) ResValue() string {
152 | return ns.Attr.Value
153 | }
154 |
155 | //GetAttribute is a convenience function for getting the specified attribute from an element.
156 | //false is returned if the attribute is not found.
157 | func GetAttribute(n Elem, local, space string) (xml.Attr, bool) {
158 | attrs := n.GetAttrs()
159 | for _, i := range attrs {
160 | attr := i.GetToken().(xml.Attr)
161 | if local == attr.Name.Local && space == attr.Name.Space {
162 | return attr, true
163 | }
164 | }
165 | return xml.Attr{}, false
166 | }
167 |
168 | //GetAttributeVal is like GetAttribute, except it returns the attribute's value.
169 | func GetAttributeVal(n Elem, local, space string) (string, bool) {
170 | attr, ok := GetAttribute(n, local, space)
171 | return attr.Value, ok
172 | }
173 |
174 | //GetAttrValOrEmpty is like GetAttributeVal, except it returns an empty string if
175 | //the attribute is not found instead of false.
176 | func GetAttrValOrEmpty(n Elem, local, space string) string {
177 | val, ok := GetAttributeVal(n, local, space)
178 | if !ok {
179 | return ""
180 | }
181 | return val
182 | }
183 |
184 | //FindNodeByPos finds a node from the given position. Returns nil if the node
185 | //is not found.
186 | func FindNodeByPos(n Node, pos int) Node {
187 | if n.Pos() == pos {
188 | return n
189 | }
190 |
191 | if elem, ok := n.(Elem); ok {
192 | chldrn := elem.GetChildren()
193 | for i := 1; i < len(chldrn); i++ {
194 | if chldrn[i-1].Pos() <= pos && chldrn[i].Pos() > pos {
195 | return FindNodeByPos(chldrn[i-1], pos)
196 | }
197 | }
198 |
199 | if len(chldrn) > 0 {
200 | if chldrn[len(chldrn)-1].Pos() <= pos {
201 | return FindNodeByPos(chldrn[len(chldrn)-1], pos)
202 | }
203 | }
204 |
205 | attrs := elem.GetAttrs()
206 | for _, i := range attrs {
207 | if i.Pos() == pos {
208 | return i
209 | }
210 | }
211 |
212 | ns := BuildNS(elem)
213 | for _, i := range ns {
214 | if i.Pos() == pos {
215 | return i
216 | }
217 | }
218 | }
219 |
220 | return nil
221 | }
222 |
--------------------------------------------------------------------------------
/internal/execxp/findutil/findUtil.go:
--------------------------------------------------------------------------------
1 | package findutil
2 |
3 | import (
4 | "encoding/xml"
5 |
6 | "github.com/ChrisTrenkamp/goxpath/parser/pathexpr"
7 | "github.com/ChrisTrenkamp/goxpath/tree"
8 | "github.com/ChrisTrenkamp/goxpath/xconst"
9 | )
10 |
11 | const (
12 | wildcard = "*"
13 | )
14 |
15 | type findFunc func(tree.Node, *pathexpr.PathExpr, *[]tree.Node)
16 |
17 | var findMap = map[string]findFunc{
18 | xconst.AxisAncestor: findAncestor,
19 | xconst.AxisAncestorOrSelf: findAncestorOrSelf,
20 | xconst.AxisAttribute: findAttribute,
21 | xconst.AxisChild: findChild,
22 | xconst.AxisDescendent: findDescendent,
23 | xconst.AxisDescendentOrSelf: findDescendentOrSelf,
24 | xconst.AxisFollowing: findFollowing,
25 | xconst.AxisFollowingSibling: findFollowingSibling,
26 | xconst.AxisNamespace: findNamespace,
27 | xconst.AxisParent: findParent,
28 | xconst.AxisPreceding: findPreceding,
29 | xconst.AxisPrecedingSibling: findPrecedingSibling,
30 | xconst.AxisSelf: findSelf,
31 | }
32 |
33 | //Find finds nodes based on the pathexpr.PathExpr
34 | func Find(x tree.Node, p pathexpr.PathExpr) []tree.Node {
35 | ret := []tree.Node{}
36 |
37 | if p.Axis == "" {
38 | findChild(x, &p, &ret)
39 | return ret
40 | }
41 |
42 | f := findMap[p.Axis]
43 | f(x, &p, &ret)
44 |
45 | return ret
46 | }
47 |
48 | func findAncestor(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
49 | if x.GetNodeType() == tree.NtRoot {
50 | return
51 | }
52 |
53 | addNode(x.GetParent(), p, ret)
54 | findAncestor(x.GetParent(), p, ret)
55 | }
56 |
57 | func findAncestorOrSelf(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
58 | findSelf(x, p, ret)
59 | findAncestor(x, p, ret)
60 | }
61 |
62 | func findAttribute(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
63 | if ele, ok := x.(tree.Elem); ok {
64 | for _, i := range ele.GetAttrs() {
65 | addNode(i, p, ret)
66 | }
67 | }
68 | }
69 |
70 | func findChild(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
71 | if ele, ok := x.(tree.Elem); ok {
72 | ch := ele.GetChildren()
73 | for i := range ch {
74 | addNode(ch[i], p, ret)
75 | }
76 | }
77 | }
78 |
79 | func findDescendent(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
80 | if ele, ok := x.(tree.Elem); ok {
81 | ch := ele.GetChildren()
82 | for i := range ch {
83 | addNode(ch[i], p, ret)
84 | findDescendent(ch[i], p, ret)
85 | }
86 | }
87 | }
88 |
89 | func findDescendentOrSelf(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
90 | findSelf(x, p, ret)
91 | findDescendent(x, p, ret)
92 | }
93 |
94 | func findFollowing(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
95 | if x.GetNodeType() == tree.NtRoot {
96 | return
97 | }
98 | par := x.GetParent()
99 | ch := par.GetChildren()
100 | i := 0
101 | for x != ch[i] {
102 | i++
103 | }
104 | i++
105 | for i < len(ch) {
106 | findDescendentOrSelf(ch[i], p, ret)
107 | i++
108 | }
109 | findFollowing(par, p, ret)
110 | }
111 |
112 | func findFollowingSibling(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
113 | if x.GetNodeType() == tree.NtRoot {
114 | return
115 | }
116 | par := x.GetParent()
117 | ch := par.GetChildren()
118 | i := 0
119 | for x != ch[i] {
120 | i++
121 | }
122 | i++
123 | for i < len(ch) {
124 | findSelf(ch[i], p, ret)
125 | i++
126 | }
127 | }
128 |
129 | func findNamespace(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
130 | if ele, ok := x.(tree.NSElem); ok {
131 | ns := tree.BuildNS(ele)
132 | for _, i := range ns {
133 | addNode(i, p, ret)
134 | }
135 | }
136 | }
137 |
138 | func findParent(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
139 | if x.GetNodeType() != tree.NtRoot {
140 | addNode(x.GetParent(), p, ret)
141 | }
142 | }
143 |
144 | func findPreceding(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
145 | if x.GetNodeType() == tree.NtRoot {
146 | return
147 | }
148 | par := x.GetParent()
149 | ch := par.GetChildren()
150 | i := len(ch) - 1
151 | for x != ch[i] {
152 | i--
153 | }
154 | i--
155 | for i >= 0 {
156 | findDescendentOrSelf(ch[i], p, ret)
157 | i--
158 | }
159 | findPreceding(par, p, ret)
160 | }
161 |
162 | func findPrecedingSibling(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
163 | if x.GetNodeType() == tree.NtRoot {
164 | return
165 | }
166 | par := x.GetParent()
167 | ch := par.GetChildren()
168 | i := len(ch) - 1
169 | for x != ch[i] {
170 | i--
171 | }
172 | i--
173 | for i >= 0 {
174 | findSelf(ch[i], p, ret)
175 | i--
176 | }
177 | }
178 |
179 | func findSelf(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
180 | addNode(x, p, ret)
181 | }
182 |
183 | func addNode(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
184 | add := false
185 | tok := x.GetToken()
186 |
187 | switch x.GetNodeType() {
188 | case tree.NtAttr:
189 | add = evalAttr(p, tok.(xml.Attr))
190 | case tree.NtChd:
191 | add = evalChd(p)
192 | case tree.NtComm:
193 | add = evalComm(p)
194 | case tree.NtElem, tree.NtRoot:
195 | add = evalEle(p, tok.(xml.StartElement))
196 | case tree.NtNs:
197 | add = evalNS(p, tok.(xml.Attr))
198 | case tree.NtPi:
199 | add = evalPI(p)
200 | }
201 |
202 | if add {
203 | *ret = append(*ret, x)
204 | }
205 | }
206 |
207 | func evalAttr(p *pathexpr.PathExpr, a xml.Attr) bool {
208 | if p.NodeType == "" {
209 | if p.Name.Space != wildcard {
210 | if a.Name.Space != p.NS[p.Name.Space] {
211 | return false
212 | }
213 | }
214 |
215 | if p.Name.Local == wildcard && p.Axis == xconst.AxisAttribute {
216 | return true
217 | }
218 |
219 | if p.Name.Local == a.Name.Local {
220 | return true
221 | }
222 | } else {
223 | if p.NodeType == xconst.NodeTypeNode {
224 | return true
225 | }
226 | }
227 |
228 | return false
229 | }
230 |
231 | func evalChd(p *pathexpr.PathExpr) bool {
232 | if p.NodeType == xconst.NodeTypeText || p.NodeType == xconst.NodeTypeNode {
233 | return true
234 | }
235 |
236 | return false
237 | }
238 |
239 | func evalComm(p *pathexpr.PathExpr) bool {
240 | if p.NodeType == xconst.NodeTypeComment || p.NodeType == xconst.NodeTypeNode {
241 | return true
242 | }
243 |
244 | return false
245 | }
246 |
247 | func evalEle(p *pathexpr.PathExpr, ele xml.StartElement) bool {
248 | if p.NodeType == "" {
249 | return checkNameAndSpace(p, ele)
250 | }
251 |
252 | if p.NodeType == xconst.NodeTypeNode {
253 | return true
254 | }
255 |
256 | return false
257 | }
258 |
259 | func checkNameAndSpace(p *pathexpr.PathExpr, ele xml.StartElement) bool {
260 | if p.Name.Local == wildcard && p.Name.Space == "" {
261 | return true
262 | }
263 |
264 | if p.Name.Space != wildcard && ele.Name.Space != p.NS[p.Name.Space] {
265 | return false
266 | }
267 |
268 | if p.Name.Local == wildcard && p.Axis != xconst.AxisAttribute && p.Axis != xconst.AxisNamespace {
269 | return true
270 | }
271 |
272 | if p.Name.Local == ele.Name.Local {
273 | return true
274 | }
275 |
276 | return false
277 | }
278 |
279 | func evalNS(p *pathexpr.PathExpr, ns xml.Attr) bool {
280 | if p.NodeType == "" {
281 | if p.Name.Space != "" && p.Name.Space != wildcard {
282 | return false
283 | }
284 |
285 | if p.Name.Local == wildcard && p.Axis == xconst.AxisNamespace {
286 | return true
287 | }
288 |
289 | if p.Name.Local == ns.Name.Local {
290 | return true
291 | }
292 | } else {
293 | if p.NodeType == xconst.NodeTypeNode {
294 | return true
295 | }
296 | }
297 |
298 | return false
299 | }
300 |
301 | func evalPI(p *pathexpr.PathExpr) bool {
302 | if p.NodeType == xconst.NodeTypeProcInst || p.NodeType == xconst.NodeTypeNode {
303 | return true
304 | }
305 |
306 | return false
307 | }
308 |
--------------------------------------------------------------------------------
/err_test.go:
--------------------------------------------------------------------------------
1 | package goxpath
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "runtime/debug"
7 | "testing"
8 |
9 | "github.com/ChrisTrenkamp/goxpath/tree"
10 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree"
11 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlele"
12 | )
13 |
14 | type dummyType string
15 |
16 | func (d dummyType) String() string {
17 | return string(d)
18 | }
19 | func dummyFunc(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
20 | return dummyType(""), nil
21 | }
22 |
23 | var custFns = map[xml.Name]tree.Wrap{
24 | {Local: "dummy"}: {Fn: dummyFunc},
25 | {Space: "http://foo.com", Local: "spaceDummy"}: {Fn: dummyFunc},
26 | }
27 |
28 | func execErr(xp, x string, errStr string, ns map[string]string, t *testing.T) {
29 | defer func() {
30 | if r := recover(); r != nil {
31 | t.Error("Panicked: from XPath expr: '" + xp)
32 | t.Error(r)
33 | t.Error(string(debug.Stack()))
34 | }
35 | }()
36 | _, err := ParseExec(xp, xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns; o.Funcs = custFns })
37 |
38 | if err.Error() != errStr {
39 | t.Error("Incorrect result:'" + err.Error() + "' from XPath expr: '" + xp + "'. Expecting: '" + errStr + "'")
40 | return
41 | }
42 | }
43 |
44 | func TestBadAxis(t *testing.T) {
45 | x := ``
46 | execErr(`/test/chil::p2`, x, "Invalid Axis specifier, chil", nil, t)
47 | }
48 |
49 | func TestIncompleteStep(t *testing.T) {
50 | x := ``
51 | execErr(`/child::+2`, x, "Step is not complete", nil, t)
52 | execErr(`/foo:`, x, "Step is not complete", nil, t)
53 | }
54 |
55 | func TestBadNodeType(t *testing.T) {
56 | x := ``
57 | execErr(`/test/foo()`, x, "Invalid node-type foo", nil, t)
58 | }
59 |
60 | func TestXPathErr(t *testing.T) {
61 | x := ``
62 | execErr(`/test/chil::p2`, x, "Invalid Axis specifier, chil", nil, t)
63 | }
64 |
65 | func TestNodeSetConvErr(t *testing.T) {
66 | x := ``
67 | for _, i := range []string{"sum", "count", "local-name", "namespace-uri", "name"} {
68 | execErr("/p1["+i+"(1)]", x, "Cannot convert object to a node-set", nil, t)
69 | }
70 | }
71 |
72 | func TestNodeSetConvUnionErr(t *testing.T) {
73 | x := ``
74 | execErr(`/p1 | 'invalid'`, x, "Cannot convert data type to node-set", nil, t)
75 | }
76 |
77 | func TestUnknownFunction(t *testing.T) {
78 | x := ``
79 | execErr(`invFunc()`, x, "Unknown function: invFunc", nil, t)
80 | }
81 |
82 | func TestUnterminatedString(t *testing.T) {
83 | x := ``
84 | execErr(`"asdf`, x, "Unexpected end of string literal.", nil, t)
85 | }
86 |
87 | func TestUnterminatedParenths(t *testing.T) {
88 | x := ``
89 | execErr(`(1 + 2 * 3`, x, "Missing end )", nil, t)
90 | }
91 |
92 | func TestUnterminatedNTQuotes(t *testing.T) {
93 | x := ``
94 | execErr(`//processing-instruction('foo)`, x, "Unexpected end of string literal.", nil, t)
95 | }
96 |
97 | func TestUnterminatedNTParenths(t *testing.T) {
98 | x := ``
99 | execErr(`//processing-instruction('foo'`, x, "Missing ) at end of NodeType declaration.", nil, t)
100 | }
101 |
102 | func TestUnterminatedFnParenths(t *testing.T) {
103 | x := ``
104 | execErr(`true(`, x, "Missing ) at end of function declaration.", nil, t)
105 | }
106 |
107 | func TestEmptyPred(t *testing.T) {
108 | x := `text`
109 | execErr(`/p1[ ]`, x, "Missing content in predicate.", nil, t)
110 | }
111 |
112 | func TestUnterminatedPred(t *testing.T) {
113 | x := `text`
114 | execErr(`/p1[. = 'text'`, x, "Missing ] at end of predicate.", nil, t)
115 | }
116 |
117 | func TestNotEnoughArgs(t *testing.T) {
118 | x := `text`
119 | execErr(`concat('test')`, x, "Invalid number of arguments", nil, t)
120 | }
121 |
122 | func TestMarshalErr(t *testing.T) {
123 | x := ``
124 | n := xmltree.MustParseXML(bytes.NewBufferString(x))
125 | f := tree.FindNodeByPos(n, 3).(*xmlele.XMLEle)
126 | f.Name.Local = ""
127 | buf := &bytes.Buffer{}
128 | err := Marshal(n, buf)
129 | if err == nil {
130 | t.Error("No error")
131 | }
132 | }
133 |
134 | func TestParsePanic(t *testing.T) {
135 | errs := 0
136 | defer func() {
137 | if errs != 1 {
138 | t.Error("Err not 1")
139 | }
140 | }()
141 | defer func() {
142 | if r := recover(); r != nil {
143 | errs++
144 | }
145 | }()
146 | MustParse(`/foo()`)
147 | }
148 |
149 | func TestExecPanic(t *testing.T) {
150 | errs := 0
151 | defer func() {
152 | if errs != 1 {
153 | t.Error("Err not 1")
154 | }
155 | }()
156 | defer func() {
157 | if r := recover(); r != nil {
158 | errs++
159 | }
160 | }()
161 | MustParse("foo()").MustExec(xmltree.MustParseXML(bytes.NewBufferString(xml.Header + "")))
162 | }
163 |
164 | func TestDummyType(t *testing.T) {
165 | ns := map[string]string{"foo": "http://foo.com"}
166 | x := ``
167 | execErr(`dummy() = 1`, x, "Cannot convert data type to number", nil, t)
168 | execErr(`dummy() = true()`, x, "Cannot convert argument to boolean", nil, t)
169 | execErr(`dummy() and true()`, x, "Cannot convert argument to boolean", nil, t)
170 | execErr(`not(dummy()) = 1`, x, "Cannot convert object to a boolean", nil, t)
171 | execErr(`1 = not(dummy())`, x, "Cannot convert object to a boolean", nil, t)
172 | execErr(`not(dummy() = 1)`, x, "Cannot convert data type to number", nil, t)
173 | execErr(`/p1[dummy()]`, x, "Cannot convert argument to boolean", nil, t)
174 | for _, i := range []string{"boolean", "not"} {
175 | execErr(i+`(dummy())`, x, "Cannot convert object to a boolean", nil, t)
176 | }
177 | for _, i := range []string{"number", "floor", "ceiling", "round"} {
178 | execErr(i+`(dummy())`, x, "Cannot convert object to a number", nil, t)
179 | }
180 | execErr(`substring("12345", dummy(), 2)`, x, "Cannot convert object to a number", nil, t)
181 | execErr(`substring("12345", 2, dummy())`, x, "Cannot convert object to a number", nil, t)
182 | execErr(`foo:spaceDummy() = 1`, x, "Cannot convert data type to number", ns, t)
183 | }
184 |
185 | func TestGoxpathBool(t *testing.T) {
186 | opts := func(o *Opts) { o.Funcs = custFns }
187 | x := xmltree.MustParseXML(bytes.NewBufferString(``))
188 | _, err := MustParse(`dummy() = 1`).ExecBool(x, opts)
189 | if err == nil {
190 | t.Error("Error not nil")
191 | }
192 | _, err = MustParse(`dummy()`).ExecBool(x, opts)
193 | if err == nil {
194 | t.Error("Error not nil")
195 | }
196 | b, err := MustParse(`/p1`).ExecBool(x, opts)
197 | if !b || err != nil {
198 | t.Error("Incorrect result")
199 | }
200 | }
201 |
202 | func TestGoxpathNum(t *testing.T) {
203 | opts := func(o *Opts) { o.Funcs = custFns }
204 | x := xmltree.MustParseXML(bytes.NewBufferString(`3`))
205 | _, err := MustParse(`dummy() = 1`).ExecNum(x, opts)
206 | if err == nil {
207 | t.Error("Error not nil")
208 | }
209 | _, err = MustParse(`dummy()`).ExecNum(x, opts)
210 | if err == nil {
211 | t.Error("Error not nil")
212 | }
213 | n, err := MustParse(`/p1`).ExecNum(x, opts)
214 | if n != 3 || err != nil {
215 | t.Error("Incorrect result")
216 | }
217 | }
218 |
219 | func TestGoxpathNode(t *testing.T) {
220 | opts := func(o *Opts) { o.Funcs = custFns }
221 | x := xmltree.MustParseXML(bytes.NewBufferString(``))
222 | _, err := MustParse(`dummy() = 1`).ExecNode(x, opts)
223 | if err == nil {
224 | t.Error("Error not nil")
225 | }
226 | _, err = MustParse(`dummy()`).ExecNode(x, opts)
227 | if err == nil {
228 | t.Error("Error not nil")
229 | }
230 | n, err := MustParse(`/p1`).ExecNode(x, opts)
231 | if len(n) != 1 || err != nil {
232 | t.Error("Incorrect result")
233 | }
234 | }
235 |
--------------------------------------------------------------------------------
/misc_test.go:
--------------------------------------------------------------------------------
1 | package goxpath
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "testing"
7 |
8 | "github.com/ChrisTrenkamp/goxpath/lexer"
9 | "github.com/ChrisTrenkamp/goxpath/parser"
10 | "github.com/ChrisTrenkamp/goxpath/tree"
11 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree"
12 | )
13 |
14 | func TestISO_8859_1(t *testing.T) {
15 | p := `/test`
16 | x := `testpathtest2`
17 | exp := "testpathtest2"
18 | execVal(p, x, exp, nil, t)
19 | }
20 |
21 | func TestNodePos(t *testing.T) {
22 | ns := map[string]string{"test": "http://test", "test2": "http://test2", "test3": "http://test3"}
23 | x := `text`
24 | testPos := func(path string, pos int) {
25 | res := MustParse(path).MustExec(xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns }).(tree.NodeSet)
26 | if len(res) != 1 {
27 | t.Errorf("Result length not 1: %s", path)
28 | return
29 | }
30 | exPos := res[0].(tree.Node).Pos()
31 | if exPos != pos {
32 | t.Errorf("Node position not correct. Recieved %d, expected %d", exPos, pos)
33 | }
34 | }
35 | testPos("/", 0)
36 | testPos("/*", 1)
37 | testPos("/*/namespace::*[1]", 2)
38 | testPos("/*/namespace::*[2]", 3)
39 | testPos("/*/attribute::*[1]", 4)
40 | testPos("//*:p2", 5)
41 | testPos("//*:p2/namespace::*[1]", 6)
42 | testPos("//*:p2/namespace::*[2]", 7)
43 | testPos("//*:p2/namespace::*[3]", 8)
44 | testPos("//*:p2/attribute::*[1]", 9)
45 | testPos("//text()", 10)
46 | }
47 |
48 | func TestNSSort(t *testing.T) {
49 | testNS := func(n tree.Node, url string) {
50 | if n.(tree.NS).Value != url {
51 | t.Errorf("Unexpected namespace %s. Expecting %s", n.(tree.NS).Value, url)
52 | }
53 | }
54 | ns := map[string]string{"test": "http://test", "test2": "http://test2", "test3": "http://test3"}
55 | x := ``
56 | res := MustParse("/*:p1/namespace::*").MustExec(xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns }).(tree.NodeSet)
57 | testNS(res[0], ns["test"])
58 | testNS(res[1], ns["test2"])
59 | testNS(res[2], ns["test3"])
60 | testNS(res[3], "http://www.w3.org/XML/1998/namespace")
61 | }
62 |
63 | func TestFindNodeByPos(t *testing.T) {
64 | x := `text`
65 | nt := xmltree.MustParseXML(bytes.NewBufferString(x))
66 | if tree.FindNodeByPos(nt, 5).GetNodeType() != tree.NtElem {
67 | t.Error("Node 5 not element")
68 | }
69 | if tree.FindNodeByPos(nt, 14).GetNodeType() != tree.NtChd {
70 | t.Error("Node 14 not char data")
71 | }
72 | if tree.FindNodeByPos(nt, 4).GetNodeType() != tree.NtAttr {
73 | t.Error("Node 4 not attribute")
74 | }
75 | if tree.FindNodeByPos(nt, 3).GetNodeType() != tree.NtNs {
76 | t.Error("Node 3 not namespace")
77 | }
78 | if tree.FindNodeByPos(nt, 19) != nil {
79 | t.Error("Invalid node returned")
80 | }
81 | }
82 |
83 | func TestFindAttr(t *testing.T) {
84 | x := ``
85 | nt := xmltree.MustParseXML(bytes.NewBufferString(x))
86 | res, _ := ParseExec("/p1", nt)
87 | node := res.(tree.NodeSet)[0].(tree.Elem)
88 | if val, ok := tree.GetAttributeVal(node, "attr1", ""); !ok || val != "foo" {
89 | t.Error("attr1 not foo")
90 | }
91 | if val, ok := tree.GetAttributeVal(node, "attr2", "http://test"); !ok || val != "bar" {
92 | t.Error("attr2 not bar")
93 | }
94 | if val, ok := tree.GetAttributeVal(node, "attr3", ""); ok || val != "" {
95 | t.Error("attr3 is set")
96 | }
97 | if val := tree.GetAttrValOrEmpty(node, "attr3", ""); val != "" {
98 | t.Error("attr3 is set")
99 | }
100 | if val := tree.GetAttrValOrEmpty(node, "attr1", ""); val != "foo" {
101 | t.Error("attr1 not foo")
102 | }
103 | }
104 |
105 | func TestVariable(t *testing.T) {
106 | x := xmltree.MustParseXML(bytes.NewBufferString(xml.Header + "foobar"))
107 | xp := MustParse(`/p1/p2`)
108 | res := xp.MustExec(x)
109 | opt := func(o *Opts) {
110 | o.Vars["prev"] = res
111 | }
112 | xp = MustParse(`$prev = 'foo'`)
113 | if res, err := xp.ExecBool(x, opt); err != nil || !res {
114 | t.Error("Incorrect result", res, err)
115 | }
116 | if _, err := xp.ExecBool(x); err == nil {
117 | t.Error("Error not nil")
118 | }
119 | if _, err := Parse(`$ = 'foo'`); err == nil {
120 | t.Error("Parse error not nil")
121 | }
122 | }
123 |
124 | func TestFunctionInteractions(t *testing.T) {
125 | cases := []struct {
126 | name string
127 | expected *parser.Node
128 | xpath string
129 | }{{
130 | name: "TestFunctionInteractions_1",
131 | expected: &parser.Node{
132 | Val: lexer.XItem{lexer.XItemOperator, "="},
133 | Left: &parser.Node{Val: lexer.XItem{lexer.XItemFunction, "current"}},
134 | Right: &parser.Node{Val: lexer.XItem{lexer.XItemNumLit, "1"}},
135 | },
136 | xpath: `current()=1`,
137 | }, {
138 | name: "TestFunctionInteractions_2",
139 | expected: &parser.Node{
140 | Val: lexer.XItem{lexer.XItemOperator, "="},
141 | Left: &parser.Node{Val: lexer.XItem{lexer.XItemFunction, "current"}},
142 | Right: &parser.Node{Val: lexer.XItem{lexer.XItemStrLit, "abc"}},
143 | },
144 | xpath: `current() = 'abc'`,
145 | }, {
146 | name: "TestFunctionInteractions_3",
147 | expected: &parser.Node{
148 | Val: lexer.XItem{lexer.XItemOperator, "="},
149 | Left: &parser.Node{Val: lexer.XItem{lexer.XItemFunction, "current"}},
150 | Right: &parser.Node{
151 | Val: lexer.XItem{lexer.XItemRelLocPath, ""},
152 | Left: &parser.Node{Val: lexer.XItem{lexer.XItemQName, "abc"}},
153 | },
154 | },
155 | xpath: `current() = abc`,
156 | }, {
157 | name: "TestFunctionInteractions_4",
158 | expected: &parser.Node{
159 | Val: lexer.XItem{lexer.XItemFunction, "current"},
160 | Left: nil,
161 | Right: &parser.Node{Val: lexer.XItem{lexer.XItemRelLocPath, ""},
162 | Left: &parser.Node{Val: lexer.XItem{lexer.XItemNCName, "ns"},
163 | Left: &parser.Node{
164 | Val: lexer.XItem{lexer.XItemQName, "sub"},
165 | },
166 | },
167 | },
168 | },
169 | xpath: `current()/ns:sub`,
170 | }, {
171 | name: "TestFunctionInteractions_5",
172 | expected: &parser.Node{
173 | Val: lexer.XItem{lexer.XItemOperator, "="},
174 | Left: &parser.Node{
175 | Val: lexer.XItem{lexer.XItemFunction, "current"},
176 | Left: nil,
177 | Right: &parser.Node{
178 | Val: lexer.XItem{lexer.XItemRelLocPath, ""},
179 | Left: &parser.Node{
180 | Val: lexer.XItem{lexer.XItemNCName, "ns"},
181 | Left: &parser.Node{
182 | Val: lexer.XItem{lexer.XItemQName, "sub"},
183 | },
184 | },
185 | },
186 | },
187 | Right: &parser.Node{
188 | Val: lexer.XItem{lexer.XItemStrLit, "abc"},
189 | },
190 | },
191 | xpath: `current()/ns:sub = 'abc'`,
192 | }, {
193 | name: "TestFunctionInteractions_6",
194 | expected: &parser.Node{
195 | Val: lexer.XItem{lexer.XItemOperator, "="},
196 | Left: &parser.Node{
197 | Val: lexer.XItem{lexer.XItemFunction, "current"},
198 | Left: nil,
199 | Right: &parser.Node{
200 | Val: lexer.XItem{lexer.XItemRelLocPath, ""},
201 | Left: &parser.Node{
202 | Val: lexer.XItem{lexer.XItemNCName, "ns"},
203 | Left: &parser.Node{
204 | Val: lexer.XItem{lexer.XItemQName, "sub"},
205 | },
206 | },
207 | },
208 | },
209 | Right: &parser.Node{
210 | Val: lexer.XItem{lexer.XItemRelLocPath, ""},
211 | Left: &parser.Node{
212 | Val: lexer.XItem{lexer.XItemQName, "abc"},
213 | },
214 | },
215 | },
216 | xpath: `current()/ns:sub = abc`,
217 | },
218 | }
219 |
220 | for _, val := range cases {
221 | t.Run(val.name, func(t *testing.T) {
222 | actual := MustParse(val.xpath).n
223 | if !isEquivalentAST(val.expected, actual) {
224 | t.Errorf("Expected AST tree is not the same as actual tree that goxpath parser returns")
225 | }
226 | })
227 | }
228 | }
229 |
230 | func isEquivalentAST(node1, node2 *parser.Node) bool {
231 | if node1 == nil && node2 == nil {
232 | return true
233 | }
234 | if node1 != nil && node2 != nil {
235 | return (node1.Val == node2.Val && isEquivalentAST(node1.Left, node2.Left) && isEquivalentAST(node1.Right, node2.Right))
236 | }
237 | return false
238 | }
239 |
--------------------------------------------------------------------------------
/lexer/xmlchars.go:
--------------------------------------------------------------------------------
1 | package lexer
2 |
3 | import "unicode"
4 |
5 | //first and second was copied from src/encoding/xml/xml.go
6 | var first = &unicode.RangeTable{
7 | R16: []unicode.Range16{
8 | {0x003A, 0x003A, 1},
9 | {0x0041, 0x005A, 1},
10 | {0x005F, 0x005F, 1},
11 | {0x0061, 0x007A, 1},
12 | {0x00C0, 0x00D6, 1},
13 | {0x00D8, 0x00F6, 1},
14 | {0x00F8, 0x00FF, 1},
15 | {0x0100, 0x0131, 1},
16 | {0x0134, 0x013E, 1},
17 | {0x0141, 0x0148, 1},
18 | {0x014A, 0x017E, 1},
19 | {0x0180, 0x01C3, 1},
20 | {0x01CD, 0x01F0, 1},
21 | {0x01F4, 0x01F5, 1},
22 | {0x01FA, 0x0217, 1},
23 | {0x0250, 0x02A8, 1},
24 | {0x02BB, 0x02C1, 1},
25 | {0x0386, 0x0386, 1},
26 | {0x0388, 0x038A, 1},
27 | {0x038C, 0x038C, 1},
28 | {0x038E, 0x03A1, 1},
29 | {0x03A3, 0x03CE, 1},
30 | {0x03D0, 0x03D6, 1},
31 | {0x03DA, 0x03E0, 2},
32 | {0x03E2, 0x03F3, 1},
33 | {0x0401, 0x040C, 1},
34 | {0x040E, 0x044F, 1},
35 | {0x0451, 0x045C, 1},
36 | {0x045E, 0x0481, 1},
37 | {0x0490, 0x04C4, 1},
38 | {0x04C7, 0x04C8, 1},
39 | {0x04CB, 0x04CC, 1},
40 | {0x04D0, 0x04EB, 1},
41 | {0x04EE, 0x04F5, 1},
42 | {0x04F8, 0x04F9, 1},
43 | {0x0531, 0x0556, 1},
44 | {0x0559, 0x0559, 1},
45 | {0x0561, 0x0586, 1},
46 | {0x05D0, 0x05EA, 1},
47 | {0x05F0, 0x05F2, 1},
48 | {0x0621, 0x063A, 1},
49 | {0x0641, 0x064A, 1},
50 | {0x0671, 0x06B7, 1},
51 | {0x06BA, 0x06BE, 1},
52 | {0x06C0, 0x06CE, 1},
53 | {0x06D0, 0x06D3, 1},
54 | {0x06D5, 0x06D5, 1},
55 | {0x06E5, 0x06E6, 1},
56 | {0x0905, 0x0939, 1},
57 | {0x093D, 0x093D, 1},
58 | {0x0958, 0x0961, 1},
59 | {0x0985, 0x098C, 1},
60 | {0x098F, 0x0990, 1},
61 | {0x0993, 0x09A8, 1},
62 | {0x09AA, 0x09B0, 1},
63 | {0x09B2, 0x09B2, 1},
64 | {0x09B6, 0x09B9, 1},
65 | {0x09DC, 0x09DD, 1},
66 | {0x09DF, 0x09E1, 1},
67 | {0x09F0, 0x09F1, 1},
68 | {0x0A05, 0x0A0A, 1},
69 | {0x0A0F, 0x0A10, 1},
70 | {0x0A13, 0x0A28, 1},
71 | {0x0A2A, 0x0A30, 1},
72 | {0x0A32, 0x0A33, 1},
73 | {0x0A35, 0x0A36, 1},
74 | {0x0A38, 0x0A39, 1},
75 | {0x0A59, 0x0A5C, 1},
76 | {0x0A5E, 0x0A5E, 1},
77 | {0x0A72, 0x0A74, 1},
78 | {0x0A85, 0x0A8B, 1},
79 | {0x0A8D, 0x0A8D, 1},
80 | {0x0A8F, 0x0A91, 1},
81 | {0x0A93, 0x0AA8, 1},
82 | {0x0AAA, 0x0AB0, 1},
83 | {0x0AB2, 0x0AB3, 1},
84 | {0x0AB5, 0x0AB9, 1},
85 | {0x0ABD, 0x0AE0, 0x23},
86 | {0x0B05, 0x0B0C, 1},
87 | {0x0B0F, 0x0B10, 1},
88 | {0x0B13, 0x0B28, 1},
89 | {0x0B2A, 0x0B30, 1},
90 | {0x0B32, 0x0B33, 1},
91 | {0x0B36, 0x0B39, 1},
92 | {0x0B3D, 0x0B3D, 1},
93 | {0x0B5C, 0x0B5D, 1},
94 | {0x0B5F, 0x0B61, 1},
95 | {0x0B85, 0x0B8A, 1},
96 | {0x0B8E, 0x0B90, 1},
97 | {0x0B92, 0x0B95, 1},
98 | {0x0B99, 0x0B9A, 1},
99 | {0x0B9C, 0x0B9C, 1},
100 | {0x0B9E, 0x0B9F, 1},
101 | {0x0BA3, 0x0BA4, 1},
102 | {0x0BA8, 0x0BAA, 1},
103 | {0x0BAE, 0x0BB5, 1},
104 | {0x0BB7, 0x0BB9, 1},
105 | {0x0C05, 0x0C0C, 1},
106 | {0x0C0E, 0x0C10, 1},
107 | {0x0C12, 0x0C28, 1},
108 | {0x0C2A, 0x0C33, 1},
109 | {0x0C35, 0x0C39, 1},
110 | {0x0C60, 0x0C61, 1},
111 | {0x0C85, 0x0C8C, 1},
112 | {0x0C8E, 0x0C90, 1},
113 | {0x0C92, 0x0CA8, 1},
114 | {0x0CAA, 0x0CB3, 1},
115 | {0x0CB5, 0x0CB9, 1},
116 | {0x0CDE, 0x0CDE, 1},
117 | {0x0CE0, 0x0CE1, 1},
118 | {0x0D05, 0x0D0C, 1},
119 | {0x0D0E, 0x0D10, 1},
120 | {0x0D12, 0x0D28, 1},
121 | {0x0D2A, 0x0D39, 1},
122 | {0x0D60, 0x0D61, 1},
123 | {0x0E01, 0x0E2E, 1},
124 | {0x0E30, 0x0E30, 1},
125 | {0x0E32, 0x0E33, 1},
126 | {0x0E40, 0x0E45, 1},
127 | {0x0E81, 0x0E82, 1},
128 | {0x0E84, 0x0E84, 1},
129 | {0x0E87, 0x0E88, 1},
130 | {0x0E8A, 0x0E8D, 3},
131 | {0x0E94, 0x0E97, 1},
132 | {0x0E99, 0x0E9F, 1},
133 | {0x0EA1, 0x0EA3, 1},
134 | {0x0EA5, 0x0EA7, 2},
135 | {0x0EAA, 0x0EAB, 1},
136 | {0x0EAD, 0x0EAE, 1},
137 | {0x0EB0, 0x0EB0, 1},
138 | {0x0EB2, 0x0EB3, 1},
139 | {0x0EBD, 0x0EBD, 1},
140 | {0x0EC0, 0x0EC4, 1},
141 | {0x0F40, 0x0F47, 1},
142 | {0x0F49, 0x0F69, 1},
143 | {0x10A0, 0x10C5, 1},
144 | {0x10D0, 0x10F6, 1},
145 | {0x1100, 0x1100, 1},
146 | {0x1102, 0x1103, 1},
147 | {0x1105, 0x1107, 1},
148 | {0x1109, 0x1109, 1},
149 | {0x110B, 0x110C, 1},
150 | {0x110E, 0x1112, 1},
151 | {0x113C, 0x1140, 2},
152 | {0x114C, 0x1150, 2},
153 | {0x1154, 0x1155, 1},
154 | {0x1159, 0x1159, 1},
155 | {0x115F, 0x1161, 1},
156 | {0x1163, 0x1169, 2},
157 | {0x116D, 0x116E, 1},
158 | {0x1172, 0x1173, 1},
159 | {0x1175, 0x119E, 0x119E - 0x1175},
160 | {0x11A8, 0x11AB, 0x11AB - 0x11A8},
161 | {0x11AE, 0x11AF, 1},
162 | {0x11B7, 0x11B8, 1},
163 | {0x11BA, 0x11BA, 1},
164 | {0x11BC, 0x11C2, 1},
165 | {0x11EB, 0x11F0, 0x11F0 - 0x11EB},
166 | {0x11F9, 0x11F9, 1},
167 | {0x1E00, 0x1E9B, 1},
168 | {0x1EA0, 0x1EF9, 1},
169 | {0x1F00, 0x1F15, 1},
170 | {0x1F18, 0x1F1D, 1},
171 | {0x1F20, 0x1F45, 1},
172 | {0x1F48, 0x1F4D, 1},
173 | {0x1F50, 0x1F57, 1},
174 | {0x1F59, 0x1F5B, 0x1F5B - 0x1F59},
175 | {0x1F5D, 0x1F5D, 1},
176 | {0x1F5F, 0x1F7D, 1},
177 | {0x1F80, 0x1FB4, 1},
178 | {0x1FB6, 0x1FBC, 1},
179 | {0x1FBE, 0x1FBE, 1},
180 | {0x1FC2, 0x1FC4, 1},
181 | {0x1FC6, 0x1FCC, 1},
182 | {0x1FD0, 0x1FD3, 1},
183 | {0x1FD6, 0x1FDB, 1},
184 | {0x1FE0, 0x1FEC, 1},
185 | {0x1FF2, 0x1FF4, 1},
186 | {0x1FF6, 0x1FFC, 1},
187 | {0x2126, 0x2126, 1},
188 | {0x212A, 0x212B, 1},
189 | {0x212E, 0x212E, 1},
190 | {0x2180, 0x2182, 1},
191 | {0x3007, 0x3007, 1},
192 | {0x3021, 0x3029, 1},
193 | {0x3041, 0x3094, 1},
194 | {0x30A1, 0x30FA, 1},
195 | {0x3105, 0x312C, 1},
196 | {0x4E00, 0x9FA5, 1},
197 | {0xAC00, 0xD7A3, 1},
198 | },
199 | }
200 |
201 | var second = &unicode.RangeTable{
202 | R16: []unicode.Range16{
203 | {0x002D, 0x002E, 1},
204 | {0x0030, 0x0039, 1},
205 | {0x00B7, 0x00B7, 1},
206 | {0x02D0, 0x02D1, 1},
207 | {0x0300, 0x0345, 1},
208 | {0x0360, 0x0361, 1},
209 | {0x0387, 0x0387, 1},
210 | {0x0483, 0x0486, 1},
211 | {0x0591, 0x05A1, 1},
212 | {0x05A3, 0x05B9, 1},
213 | {0x05BB, 0x05BD, 1},
214 | {0x05BF, 0x05BF, 1},
215 | {0x05C1, 0x05C2, 1},
216 | {0x05C4, 0x0640, 0x0640 - 0x05C4},
217 | {0x064B, 0x0652, 1},
218 | {0x0660, 0x0669, 1},
219 | {0x0670, 0x0670, 1},
220 | {0x06D6, 0x06DC, 1},
221 | {0x06DD, 0x06DF, 1},
222 | {0x06E0, 0x06E4, 1},
223 | {0x06E7, 0x06E8, 1},
224 | {0x06EA, 0x06ED, 1},
225 | {0x06F0, 0x06F9, 1},
226 | {0x0901, 0x0903, 1},
227 | {0x093C, 0x093C, 1},
228 | {0x093E, 0x094C, 1},
229 | {0x094D, 0x094D, 1},
230 | {0x0951, 0x0954, 1},
231 | {0x0962, 0x0963, 1},
232 | {0x0966, 0x096F, 1},
233 | {0x0981, 0x0983, 1},
234 | {0x09BC, 0x09BC, 1},
235 | {0x09BE, 0x09BF, 1},
236 | {0x09C0, 0x09C4, 1},
237 | {0x09C7, 0x09C8, 1},
238 | {0x09CB, 0x09CD, 1},
239 | {0x09D7, 0x09D7, 1},
240 | {0x09E2, 0x09E3, 1},
241 | {0x09E6, 0x09EF, 1},
242 | {0x0A02, 0x0A3C, 0x3A},
243 | {0x0A3E, 0x0A3F, 1},
244 | {0x0A40, 0x0A42, 1},
245 | {0x0A47, 0x0A48, 1},
246 | {0x0A4B, 0x0A4D, 1},
247 | {0x0A66, 0x0A6F, 1},
248 | {0x0A70, 0x0A71, 1},
249 | {0x0A81, 0x0A83, 1},
250 | {0x0ABC, 0x0ABC, 1},
251 | {0x0ABE, 0x0AC5, 1},
252 | {0x0AC7, 0x0AC9, 1},
253 | {0x0ACB, 0x0ACD, 1},
254 | {0x0AE6, 0x0AEF, 1},
255 | {0x0B01, 0x0B03, 1},
256 | {0x0B3C, 0x0B3C, 1},
257 | {0x0B3E, 0x0B43, 1},
258 | {0x0B47, 0x0B48, 1},
259 | {0x0B4B, 0x0B4D, 1},
260 | {0x0B56, 0x0B57, 1},
261 | {0x0B66, 0x0B6F, 1},
262 | {0x0B82, 0x0B83, 1},
263 | {0x0BBE, 0x0BC2, 1},
264 | {0x0BC6, 0x0BC8, 1},
265 | {0x0BCA, 0x0BCD, 1},
266 | {0x0BD7, 0x0BD7, 1},
267 | {0x0BE7, 0x0BEF, 1},
268 | {0x0C01, 0x0C03, 1},
269 | {0x0C3E, 0x0C44, 1},
270 | {0x0C46, 0x0C48, 1},
271 | {0x0C4A, 0x0C4D, 1},
272 | {0x0C55, 0x0C56, 1},
273 | {0x0C66, 0x0C6F, 1},
274 | {0x0C82, 0x0C83, 1},
275 | {0x0CBE, 0x0CC4, 1},
276 | {0x0CC6, 0x0CC8, 1},
277 | {0x0CCA, 0x0CCD, 1},
278 | {0x0CD5, 0x0CD6, 1},
279 | {0x0CE6, 0x0CEF, 1},
280 | {0x0D02, 0x0D03, 1},
281 | {0x0D3E, 0x0D43, 1},
282 | {0x0D46, 0x0D48, 1},
283 | {0x0D4A, 0x0D4D, 1},
284 | {0x0D57, 0x0D57, 1},
285 | {0x0D66, 0x0D6F, 1},
286 | {0x0E31, 0x0E31, 1},
287 | {0x0E34, 0x0E3A, 1},
288 | {0x0E46, 0x0E46, 1},
289 | {0x0E47, 0x0E4E, 1},
290 | {0x0E50, 0x0E59, 1},
291 | {0x0EB1, 0x0EB1, 1},
292 | {0x0EB4, 0x0EB9, 1},
293 | {0x0EBB, 0x0EBC, 1},
294 | {0x0EC6, 0x0EC6, 1},
295 | {0x0EC8, 0x0ECD, 1},
296 | {0x0ED0, 0x0ED9, 1},
297 | {0x0F18, 0x0F19, 1},
298 | {0x0F20, 0x0F29, 1},
299 | {0x0F35, 0x0F39, 2},
300 | {0x0F3E, 0x0F3F, 1},
301 | {0x0F71, 0x0F84, 1},
302 | {0x0F86, 0x0F8B, 1},
303 | {0x0F90, 0x0F95, 1},
304 | {0x0F97, 0x0F97, 1},
305 | {0x0F99, 0x0FAD, 1},
306 | {0x0FB1, 0x0FB7, 1},
307 | {0x0FB9, 0x0FB9, 1},
308 | {0x20D0, 0x20DC, 1},
309 | {0x20E1, 0x3005, 0x3005 - 0x20E1},
310 | {0x302A, 0x302F, 1},
311 | {0x3031, 0x3035, 1},
312 | {0x3099, 0x309A, 1},
313 | {0x309D, 0x309E, 1},
314 | {0x30FC, 0x30FE, 1},
315 | },
316 | }
317 |
--------------------------------------------------------------------------------
/fns_test.go:
--------------------------------------------------------------------------------
1 | package goxpath
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "fmt"
7 | "testing"
8 |
9 | "github.com/ChrisTrenkamp/goxpath/tree"
10 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree"
11 | )
12 |
13 | func TestStrLit(t *testing.T) {
14 | p := `'strlit'`
15 | x := ``
16 | exp := "strlit"
17 | execVal(p, x, exp, nil, t)
18 | }
19 |
20 | func TestNumLit(t *testing.T) {
21 | p := `123`
22 | x := ``
23 | exp := "123"
24 | execVal(p, x, exp, nil, t)
25 | p = `123.456`
26 | exp = "123.456"
27 | execVal(p, x, exp, nil, t)
28 | }
29 |
30 | func TestLast(t *testing.T) {
31 | p := `/p1/*[last()]`
32 | x := ``
33 | exp := []string{""}
34 | execPath(p, x, exp, nil, t)
35 | p = `/p1/p5[last()]`
36 | exp = []string{}
37 | execPath(p, x, exp, nil, t)
38 | p = `/p1[last()]`
39 | exp = []string{""}
40 | execPath(p, x, exp, nil, t)
41 | }
42 |
43 | func TestCount(t *testing.T) {
44 | p := `count(//p1)`
45 | x := ``
46 | exp := "2"
47 | execVal(p, x, exp, nil, t)
48 | }
49 |
50 | func TestCount2(t *testing.T) {
51 | x := `
52 |
53 |
54 |
55 |
56 | y31
57 | y32
58 |
59 |
60 |
61 |
62 |
63 | y21
64 | y22
65 |
66 |
67 |
68 | y11
69 | y12
70 |
71 |
72 | y03
73 | y04
74 |
75 |
76 | `
77 | execVal(`count(//x)`, x, "7", nil, t)
78 | execVal(`count(//x[1])`, x, "4", nil, t)
79 | execVal(`count(//x/y)`, x, "8", nil, t)
80 | execVal(`count(//x/y[1])`, x, "4", nil, t)
81 | execVal(`count(//x[1]/y[1])`, x, "2", nil, t)
82 | }
83 |
84 | func TestNames(t *testing.T) {
85 | x := ``
86 | testMap := make(map[string]map[string]string)
87 | testMap["/*"] = make(map[string]string)
88 | testMap["/*"]["local-name"] = "test"
89 | testMap["/*"]["namespace-uri"] = "http://foo.com"
90 | testMap["/*"]["name"] = "{http://foo.com}test"
91 |
92 | testMap["/none"] = make(map[string]string)
93 | testMap["/none"]["local-name"] = ""
94 | testMap["/none"]["namespace-uri"] = ""
95 | testMap["/none"]["name"] = ""
96 |
97 | testMap["/*/@*:attr"] = make(map[string]string)
98 | testMap["/*/@*:attr"]["local-name"] = "attr"
99 | testMap["/*/@*:attr"]["namespace-uri"] = "http://bar.com"
100 | testMap["/*/@*:attr"]["name"] = "{http://bar.com}attr"
101 |
102 | testMap["//processing-instruction()"] = make(map[string]string)
103 | testMap["//processing-instruction()"]["local-name"] = "pi"
104 | testMap["//processing-instruction()"]["namespace-uri"] = ""
105 | testMap["//processing-instruction()"]["name"] = "pi"
106 |
107 | testMap["//comment()"] = make(map[string]string)
108 | testMap["//comment()"]["local-name"] = ""
109 | testMap["//comment()"]["namespace-uri"] = ""
110 | testMap["//comment()"]["name"] = ""
111 |
112 | for path, i := range testMap {
113 | for nt, res := range i {
114 | p := fmt.Sprintf("%s(%s)", nt, path)
115 | exp := res
116 | execVal(p, x, exp, nil, t)
117 | }
118 | }
119 |
120 | x = ``
121 | execPath("/*[local-name() = 'test']", x, []string{``}, nil, t)
122 | execPath("/*[namespace-uri() = 'http://foo.com']", x, []string{``}, nil, t)
123 | execPath("/*[name() = '{http://foo.com}test']", x, []string{``}, nil, t)
124 | }
125 |
126 | func TestBoolean(t *testing.T) {
127 | x := ``
128 | execVal(`true()`, x, "true", nil, t)
129 | execVal(`false()`, x, "false", nil, t)
130 | p := `boolean(/p1/p2)`
131 | exp := "true"
132 | execVal(p, x, exp, nil, t)
133 | p = `boolean(/p1/p5)`
134 | exp = "false"
135 | execVal(p, x, exp, nil, t)
136 | p = `boolean('123')`
137 | exp = "true"
138 | execVal(p, x, exp, nil, t)
139 | p = `boolean(123)`
140 | exp = "true"
141 | execVal(p, x, exp, nil, t)
142 | p = `boolean('')`
143 | exp = "false"
144 | execVal(p, x, exp, nil, t)
145 | p = `boolean(0)`
146 | exp = "false"
147 | execVal(p, x, exp, nil, t)
148 | }
149 |
150 | func TestNot(t *testing.T) {
151 | x := ``
152 | execVal(`not(false())`, x, "true", nil, t)
153 | execVal(`not(true())`, x, "false", nil, t)
154 | }
155 |
156 | func TestConversions(t *testing.T) {
157 | x := `foo`
158 | execVal(`number(true())`, x, "1", nil, t)
159 | execVal(`number(false())`, x, "0", nil, t)
160 | execVal(`string(/p2)`, x, "", nil, t)
161 | execVal(`/p1[string() = 'foo']`, x, "foo", nil, t)
162 | execVal(`number('abc')`, x, "NaN", nil, t)
163 | }
164 |
165 | func TestLang(t *testing.T) {
166 | x := `
167 |
168 | I went up a floor.
169 | I took the lift.
170 | I rode the elevator.
171 | `
172 | execVal(`count(//p[lang('en')])`, x, "3", nil, t)
173 | execVal(`count(//text()[lang('en-GB')])`, x, "1", nil, t)
174 | execVal(`count(//p[lang('en-US')])`, x, "1", nil, t)
175 | execVal(`count(//p[lang('de')])`, x, "0", nil, t)
176 | execVal(`count(/p1[lang('en')])`, x, "0", nil, t)
177 | }
178 |
179 | func TestString(t *testing.T) {
180 | x := `text`
181 | execVal(`string(2 + 2)`, x, "4", nil, t)
182 | execVal(`string(/p1)`, x, "text", nil, t)
183 | }
184 |
185 | func TestConcat(t *testing.T) {
186 | x := ``
187 | execVal(`concat('abc', 'def', 'hij', '123')`, x, "abcdefhij123", nil, t)
188 | }
189 |
190 | func TestStartsWith(t *testing.T) {
191 | x := ``
192 | execVal(`starts-with('abcd', 'ab')`, x, "true", nil, t)
193 | execVal(`starts-with('abcd', 'abd')`, x, "false", nil, t)
194 | }
195 |
196 | func TestContains(t *testing.T) {
197 | x := ``
198 | execVal(`contains('abcd', 'bcd')`, x, "true", nil, t)
199 | execVal(`contains('abcd', 'bd')`, x, "false", nil, t)
200 | }
201 |
202 | func TestSubstrBefore(t *testing.T) {
203 | x := ``
204 | execVal(`substring-before("1999/04/01","/")`, x, "1999", nil, t)
205 | execVal(`substring-before("1999/04/01","2")`, x, "", nil, t)
206 | }
207 |
208 | func TestSubstrAfter(t *testing.T) {
209 | x := ``
210 | execVal(`substring-after("1999/04/01","/")`, x, "04/01", nil, t)
211 | execVal(`substring-after("1999/04/01","19")`, x, "99/04/01", nil, t)
212 | execVal(`substring-after("1999/04/01","a")`, x, "", nil, t)
213 | }
214 |
215 | func TestSubstring(t *testing.T) {
216 | x := ``
217 | execVal(`substring("12345", 2, 3)`, x, "234", nil, t)
218 | execVal(`substring("12345", 2)`, x, "2345", nil, t)
219 | execVal(`substring('abcd', -2, 5)`, x, "ab", nil, t)
220 | execVal(`substring('abcd', 0)`, x, "abcd", nil, t)
221 | execVal(`substring('abcd', 1, 4)`, x, "abcd", nil, t)
222 | execVal(`substring("12345", 1.5, 2.6)`, x, "234", nil, t)
223 | execVal(`substring("12345", 0 div 0, 3)`, x, "", nil, t)
224 | execVal(`substring("12345", 1, 0 div 0)`, x, "", nil, t)
225 | execVal(`substring("12345", -42, 1 div 0)`, x, "12345", nil, t)
226 | execVal(`substring("12345", -1 div 0, 1 div 0)`, x, "", nil, t)
227 | }
228 |
229 | func TestStrLength(t *testing.T) {
230 | x := `abc`
231 | execVal(`string-length('def')`, x, "3", nil, t)
232 | execVal(`/p1[string-length() = 3]`, x, "abc", nil, t)
233 | }
234 |
235 | func TestNormalizeSpace(t *testing.T) {
236 | x := `
237 | a b
238 | `
239 | execVal(`normalize-space(/p1)`, x, "a b", nil, t)
240 | execVal(`/p1[normalize-space(/p1) = 'a b']`, x, `
241 | a b
242 | `, nil, t)
243 | execVal(`/p1[normalize-space() = 'a b']`, x, `
244 | a b
245 | `, nil, t)
246 | }
247 |
248 | func TestTranslate(t *testing.T) {
249 | x := ``
250 | execVal(`translate("bar","abc","ABC")`, x, "BAr", nil, t)
251 | execVal(`translate("--aaa--","abc-","ABC")`, x, "AAA", nil, t)
252 | }
253 |
254 | func TestPathAfterFunction(t *testing.T) {
255 | x := xmltree.MustParseXML(bytes.NewBufferString(`
256 |
257 | aabb
258 | cc
259 |
260 | `))
261 |
262 | current, err := MustParse(`/root/a`).ExecNode(x)
263 | if err != nil {
264 | t.Error("err not nil")
265 | }
266 |
267 | currentFn := func(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
268 | return current, nil
269 | }
270 | custFns := map[xml.Name]tree.Wrap{
271 | {Local: "current"}: {Fn: currentFn},
272 | }
273 | opts := func(o *Opts) { o.Funcs = custFns }
274 |
275 | result := MustParse(`/root/c/@a[. = current()/b]`).MustExec(x, opts)
276 | if result.String() != `bb` {
277 | t.Error("result not foo:", result.String())
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/lexer/lexer.go:
--------------------------------------------------------------------------------
1 | package lexer
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | "unicode"
7 | "unicode/utf8"
8 | )
9 |
10 | const (
11 | //XItemError is an error with the parser input
12 | XItemError XItemType = "Error"
13 | //XItemAbsLocPath is an absolute path
14 | XItemAbsLocPath = "Absolute path"
15 | //XItemAbbrAbsLocPath represents an abbreviated absolute path
16 | XItemAbbrAbsLocPath = "Abbreviated absolute path"
17 | //XItemAbbrRelLocPath marks the start of a path expression
18 | XItemAbbrRelLocPath = "Abbreviated relative path"
19 | //XItemRelLocPath represents a relative location path
20 | XItemRelLocPath = "Relative path"
21 | //XItemEndPath marks the end of a path
22 | XItemEndPath = "End path instruction"
23 | //XItemAxis marks an axis specifier of a path
24 | XItemAxis = "Axis"
25 | //XItemAbbrAxis marks an abbreviated axis specifier (just @ at this point)
26 | XItemAbbrAxis = "Abbreviated attribute axis"
27 | //XItemNCName marks a namespace name in a node test
28 | XItemNCName = "Namespace"
29 | //XItemQName marks the local name in an a node test
30 | XItemQName = "Local name"
31 | //XItemNodeType marks a node type in a node test
32 | XItemNodeType = "Node type"
33 | //XItemProcLit marks a processing-instruction literal
34 | XItemProcLit = "processing-instruction"
35 | //XItemFunction marks a function call
36 | XItemFunction = "function"
37 | //XItemArgument marks a function argument
38 | XItemArgument = "function argument"
39 | //XItemEndFunction marks the end of a function
40 | XItemEndFunction = "end of function"
41 | //XItemPredicate marks a predicate in an axis
42 | XItemPredicate = "predicate"
43 | //XItemEndPredicate marks a predicate in an axis
44 | XItemEndPredicate = "end of predicate"
45 | //XItemStrLit marks a string literal
46 | XItemStrLit = "string literal"
47 | //XItemNumLit marks a numeric literal
48 | XItemNumLit = "numeric literal"
49 | //XItemOperator marks an operator
50 | XItemOperator = "operator"
51 | //XItemVariable marks a variable reference
52 | XItemVariable = "variable"
53 | )
54 |
55 | const (
56 | eof = -(iota + 1)
57 | )
58 |
59 | //XItemType is the parser token types
60 | type XItemType string
61 |
62 | //XItem is the token emitted from the parser
63 | type XItem struct {
64 | Typ XItemType
65 | Val string
66 | }
67 |
68 | type stateFn func(*Lexer) stateFn
69 |
70 | //Lexer lexes out XPath expressions
71 | type Lexer struct {
72 | input string
73 | start int
74 | pos int
75 | width int
76 | items chan XItem
77 | }
78 |
79 | //Lex an XPath expresion on the io.Reader
80 | func Lex(xpath string) chan XItem {
81 | l := &Lexer{
82 | input: xpath,
83 | items: make(chan XItem),
84 | }
85 | go l.run()
86 | return l.items
87 | }
88 |
89 | func (l *Lexer) run() {
90 | for state := startState; state != nil; {
91 | state = state(l)
92 | }
93 |
94 | if l.peek() != eof {
95 | l.errorf("Malformed XPath expression")
96 | }
97 |
98 | close(l.items)
99 | }
100 |
101 | func (l *Lexer) emit(t XItemType) {
102 | l.items <- XItem{t, l.input[l.start:l.pos]}
103 | l.start = l.pos
104 | }
105 |
106 | func (l *Lexer) emitVal(t XItemType, val string) {
107 | l.items <- XItem{t, val}
108 | l.start = l.pos
109 | }
110 |
111 | func (l *Lexer) next() (r rune) {
112 | if l.pos >= len(l.input) {
113 | l.width = 0
114 | return eof
115 | }
116 |
117 | r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
118 |
119 | l.pos += l.width
120 |
121 | return r
122 | }
123 |
124 | func (l *Lexer) ignore() {
125 | l.start = l.pos
126 | }
127 |
128 | func (l *Lexer) backup() {
129 | l.pos -= l.width
130 | }
131 |
132 | func (l *Lexer) peek() rune {
133 | r := l.next()
134 |
135 | l.backup()
136 | return r
137 | }
138 |
139 | func (l *Lexer) peekAt(n int) rune {
140 | if n <= 1 {
141 | return l.peek()
142 | }
143 |
144 | width := 0
145 | var ret rune
146 |
147 | for count := 0; count < n; count++ {
148 | r, s := utf8.DecodeRuneInString(l.input[l.pos+width:])
149 | width += s
150 |
151 | if l.pos+width > len(l.input) {
152 | return eof
153 | }
154 |
155 | ret = r
156 | }
157 |
158 | return ret
159 | }
160 |
161 | func (l *Lexer) accept(valid string) bool {
162 | if strings.ContainsRune(valid, l.next()) {
163 | return true
164 | }
165 |
166 | l.backup()
167 | return false
168 | }
169 |
170 | func (l *Lexer) acceptRun(valid string) {
171 | for strings.ContainsRune(valid, l.next()) {
172 | }
173 | l.backup()
174 | }
175 |
176 | func (l *Lexer) skip(num int) {
177 | for i := 0; i < num; i++ {
178 | l.next()
179 | }
180 | l.ignore()
181 | }
182 |
183 | func (l *Lexer) skipWS(ig bool) {
184 | for {
185 | n := l.next()
186 |
187 | if n == eof || !unicode.IsSpace(n) {
188 | break
189 | }
190 | }
191 |
192 | l.backup()
193 |
194 | if ig {
195 | l.ignore()
196 | }
197 | }
198 |
199 | func (l *Lexer) errorf(format string, args ...interface{}) stateFn {
200 | l.items <- XItem{
201 | XItemError,
202 | fmt.Sprintf(format, args...),
203 | }
204 |
205 | return nil
206 | }
207 |
208 | func isElemChar(r rune) bool {
209 | return string(r) != ":" && string(r) != "/" &&
210 | (unicode.Is(first, r) || unicode.Is(second, r) || string(r) == "*") &&
211 | r != eof
212 | }
213 |
214 | func startState(l *Lexer) stateFn {
215 | l.skipWS(true)
216 |
217 | if string(l.peek()) == "/" {
218 | l.next()
219 | l.ignore()
220 |
221 | if string(l.next()) == "/" {
222 | l.ignore()
223 | return abbrAbsLocPathState
224 | }
225 |
226 | l.backup()
227 | return absLocPathState
228 | } else if string(l.peek()) == `'` || string(l.peek()) == `"` {
229 | if err := getStrLit(l, XItemStrLit); err != nil {
230 | return l.errorf(err.Error())
231 | }
232 |
233 | if l.peek() != eof {
234 | return startState
235 | }
236 | } else if getNumLit(l) {
237 | l.skipWS(true)
238 | if l.peek() != eof {
239 | return startState
240 | }
241 | } else if string(l.peek()) == "$" {
242 | l.next()
243 | l.ignore()
244 | r := l.peek()
245 | for unicode.Is(first, r) || unicode.Is(second, r) {
246 | l.next()
247 | r = l.peek()
248 | }
249 | tok := l.input[l.start:l.pos]
250 | if len(tok) == 0 {
251 | return l.errorf("Empty variable name")
252 | }
253 | l.emit(XItemVariable)
254 | l.skipWS(true)
255 | if l.peek() != eof {
256 | return startState
257 | }
258 | } else if st := findOperatorState(l); st != nil {
259 | return st
260 | } else {
261 | if isElemChar(l.peek()) {
262 | colons := 0
263 |
264 | for {
265 | if isElemChar(l.peek()) {
266 | l.next()
267 | } else if string(l.peek()) == ":" {
268 | l.next()
269 | colons++
270 | } else {
271 | break
272 | }
273 | }
274 |
275 | if string(l.peek()) == "(" && colons <= 1 {
276 | tok := l.input[l.start:l.pos]
277 | err := procFunc(l, tok)
278 | if err != nil {
279 | return l.errorf(err.Error())
280 | }
281 |
282 | l.skipWS(true)
283 |
284 | if string(l.peek()) == "/" {
285 | l.next()
286 | l.ignore()
287 |
288 | if string(l.next()) == "/" {
289 | l.ignore()
290 | return abbrRelLocPathState
291 | }
292 |
293 | l.backup()
294 | return relLocPathState
295 | }
296 |
297 | return startState
298 | }
299 |
300 | l.pos = l.start
301 | return relLocPathState
302 | } else if string(l.peek()) == "@" {
303 | return relLocPathState
304 | }
305 | }
306 |
307 | return nil
308 | }
309 |
310 | func strPeek(str string, l *Lexer) bool {
311 | for i := 0; i < len(str); i++ {
312 | if string(l.peekAt(i+1)) != string(str[i]) {
313 | return false
314 | }
315 | }
316 | return true
317 | }
318 |
319 | func findOperatorState(l *Lexer) stateFn {
320 | l.skipWS(true)
321 |
322 | switch string(l.peek()) {
323 | case ">", "<", "!":
324 | l.next()
325 | if string(l.peek()) == "=" {
326 | l.next()
327 | }
328 | l.emit(XItemOperator)
329 | return startState
330 | case "|", "+", "-", "*", "=":
331 | l.next()
332 | l.emit(XItemOperator)
333 | return startState
334 | case "(":
335 | l.next()
336 | l.emit(XItemOperator)
337 | for state := startState; state != nil; {
338 | state = state(l)
339 | }
340 | l.skipWS(true)
341 | if string(l.next()) != ")" {
342 | return l.errorf("Missing end )")
343 | }
344 | l.emit(XItemOperator)
345 | return startState
346 | }
347 |
348 | if strPeek("and", l) {
349 | l.next()
350 | l.next()
351 | l.next()
352 | l.emit(XItemOperator)
353 | return startState
354 | }
355 |
356 | if strPeek("or", l) {
357 | l.next()
358 | l.next()
359 | l.emit(XItemOperator)
360 | return startState
361 | }
362 |
363 | if strPeek("mod", l) {
364 | l.next()
365 | l.next()
366 | l.next()
367 | l.emit(XItemOperator)
368 | return startState
369 | }
370 |
371 | if strPeek("div", l) {
372 | l.next()
373 | l.next()
374 | l.next()
375 | l.emit(XItemOperator)
376 | return startState
377 | }
378 |
379 | return nil
380 | }
381 |
382 | func getStrLit(l *Lexer, tok XItemType) error {
383 | q := l.next()
384 | var r rune
385 |
386 | l.ignore()
387 |
388 | for r != q {
389 | r = l.next()
390 | if r == eof {
391 | return fmt.Errorf("Unexpected end of string literal.")
392 | }
393 | }
394 |
395 | l.backup()
396 | l.emit(tok)
397 | l.next()
398 | l.ignore()
399 |
400 | return nil
401 | }
402 |
403 | func getNumLit(l *Lexer) bool {
404 | const dig = "0123456789"
405 | l.accept("-")
406 | start := l.pos
407 | l.acceptRun(dig)
408 |
409 | if l.pos == start {
410 | return false
411 | }
412 |
413 | if l.accept(".") {
414 | l.acceptRun(dig)
415 | }
416 |
417 | l.emit(XItemNumLit)
418 | return true
419 | }
420 |
--------------------------------------------------------------------------------
/internal/execxp/paths.go:
--------------------------------------------------------------------------------
1 | package execxp
2 |
3 | import (
4 | "encoding/xml"
5 | "fmt"
6 | "strconv"
7 | "strings"
8 |
9 | "github.com/ChrisTrenkamp/goxpath/internal/execxp/findutil"
10 | "github.com/ChrisTrenkamp/goxpath/internal/execxp/intfns"
11 | "github.com/ChrisTrenkamp/goxpath/internal/xsort"
12 | "github.com/ChrisTrenkamp/goxpath/lexer"
13 | "github.com/ChrisTrenkamp/goxpath/parser"
14 | "github.com/ChrisTrenkamp/goxpath/parser/pathexpr"
15 | "github.com/ChrisTrenkamp/goxpath/tree"
16 | "github.com/ChrisTrenkamp/goxpath/xconst"
17 | )
18 |
19 | type xpFilt struct {
20 | t tree.Node
21 | ctx tree.Result
22 | expr pathexpr.PathExpr
23 | ns map[string]string
24 | ctxPos int
25 | ctxSize int
26 | proxPos map[int]int
27 | fns map[xml.Name]tree.Wrap
28 | variables map[string]tree.Result
29 | }
30 |
31 | type xpExecFn func(*xpFilt, string)
32 |
33 | var xpFns = map[lexer.XItemType]xpExecFn{
34 | lexer.XItemAbsLocPath: xfAbsLocPath,
35 | lexer.XItemAbbrAbsLocPath: xfAbbrAbsLocPath,
36 | lexer.XItemRelLocPath: xfRelLocPath,
37 | lexer.XItemAbbrRelLocPath: xfAbbrRelLocPath,
38 | lexer.XItemAxis: xfAxis,
39 | lexer.XItemAbbrAxis: xfAbbrAxis,
40 | lexer.XItemNCName: xfNCName,
41 | lexer.XItemQName: xfQName,
42 | lexer.XItemNodeType: xfNodeType,
43 | lexer.XItemProcLit: xfProcInstLit,
44 | lexer.XItemStrLit: xfStrLit,
45 | lexer.XItemNumLit: xfNumLit,
46 | }
47 |
48 | func xfExec(f *xpFilt, n *parser.Node) (err error) {
49 | for n != nil {
50 | if fn, ok := xpFns[n.Val.Typ]; ok {
51 | fn(f, n.Val.Val)
52 | n = n.Left
53 | } else if n.Val.Typ == lexer.XItemPredicate {
54 | if err = xfPredicate(f, n.Left); err != nil {
55 | return
56 | }
57 |
58 | n = n.Right
59 | } else if n.Val.Typ == lexer.XItemFunction {
60 | if err = xfFunction(f, n); err != nil {
61 | return
62 | }
63 |
64 | n = n.Right
65 | } else if n.Val.Typ == lexer.XItemOperator {
66 | lf := xpFilt{
67 | t: f.t,
68 | ns: f.ns,
69 | ctx: f.ctx,
70 | ctxPos: f.ctxPos,
71 | ctxSize: f.ctxSize,
72 | proxPos: f.proxPos,
73 | fns: f.fns,
74 | variables: f.variables,
75 | }
76 | left, err := exec(&lf, n.Left)
77 | if err != nil {
78 | return err
79 | }
80 |
81 | rf := xpFilt{
82 | t: f.t,
83 | ns: f.ns,
84 | ctx: f.ctx,
85 | fns: f.fns,
86 | variables: f.variables,
87 | }
88 | right, err := exec(&rf, n.Right)
89 | if err != nil {
90 | return err
91 | }
92 |
93 | return xfOperator(left, right, f, n.Val.Val)
94 | } else if n.Val.Typ == lexer.XItemVariable {
95 | if res, ok := f.variables[n.Val.Val]; ok {
96 | f.ctx = res
97 | return nil
98 | }
99 | return fmt.Errorf("Invalid variable '%s'", n.Val.Val)
100 | } else if string(n.Val.Typ) == "" {
101 | n = n.Left
102 | //} else {
103 | // return fmt.Errorf("Cannot process " + string(n.Val.Typ))
104 | }
105 | }
106 |
107 | return
108 | }
109 |
110 | func xfPredicate(f *xpFilt, n *parser.Node) (err error) {
111 | res := f.ctx.(tree.NodeSet)
112 | newRes := make(tree.NodeSet, 0, len(res))
113 |
114 | for i := range res {
115 | pf := xpFilt{
116 | t: f.t,
117 | ns: f.ns,
118 | ctxPos: i,
119 | ctxSize: f.ctxSize,
120 | ctx: tree.NodeSet{res[i]},
121 | fns: f.fns,
122 | variables: f.variables,
123 | }
124 |
125 | predRes, err := exec(&pf, n)
126 | if err != nil {
127 | return err
128 | }
129 |
130 | ok, err := checkPredRes(predRes, f, res[i])
131 | if err != nil {
132 | return err
133 | }
134 |
135 | if ok {
136 | newRes = append(newRes, res[i])
137 | }
138 | }
139 |
140 | f.proxPos = make(map[int]int)
141 | for pos, j := range newRes {
142 | f.proxPos[j.Pos()] = pos + 1
143 | }
144 |
145 | f.ctx = newRes
146 | f.ctxSize = len(newRes)
147 |
148 | return
149 | }
150 |
151 | func checkPredRes(ret tree.Result, f *xpFilt, node tree.Node) (bool, error) {
152 | if num, ok := ret.(tree.Num); ok {
153 | if float64(f.proxPos[node.Pos()]) == float64(num) {
154 | return true, nil
155 | }
156 | return false, nil
157 | }
158 |
159 | if b, ok := ret.(tree.IsBool); ok {
160 | return bool(b.Bool()), nil
161 | }
162 |
163 | return false, fmt.Errorf("Cannot convert argument to boolean")
164 | }
165 |
166 | func xfFunction(f *xpFilt, n *parser.Node) error {
167 | spl := strings.Split(n.Val.Val, ":")
168 | var name xml.Name
169 | if len(spl) == 1 {
170 | name.Local = spl[0]
171 | } else {
172 | name.Space = f.ns[spl[0]]
173 | name.Local = spl[1]
174 | }
175 | fn, ok := intfns.BuiltIn[name]
176 | if !ok {
177 | fn, ok = f.fns[name]
178 | }
179 |
180 | if ok {
181 | args := []tree.Result{}
182 | param := n.Left
183 |
184 | for param != nil {
185 | pf := xpFilt{
186 | t: f.t,
187 | ctx: f.ctx,
188 | ns: f.ns,
189 | ctxPos: f.ctxPos,
190 | ctxSize: f.ctxSize,
191 | fns: f.fns,
192 | variables: f.variables,
193 | }
194 | res, err := exec(&pf, param.Left)
195 | if err != nil {
196 | return err
197 | }
198 |
199 | args = append(args, res)
200 | param = param.Right
201 | }
202 |
203 | filt, err := fn.Call(tree.Ctx{NodeSet: f.ctx.(tree.NodeSet), Size: f.ctxSize, Pos: f.ctxPos + 1}, args...)
204 | f.ctx = filt
205 | return err
206 | }
207 |
208 | return fmt.Errorf("Unknown function: %s", n.Val.Val)
209 | }
210 |
211 | var eqOps = map[string]bool{
212 | "=": true,
213 | "!=": true,
214 | }
215 |
216 | var booleanOps = map[string]bool{
217 | "=": true,
218 | "!=": true,
219 | "<": true,
220 | "<=": true,
221 | ">": true,
222 | ">=": true,
223 | }
224 |
225 | var numOps = map[string]bool{
226 | "*": true,
227 | "div": true,
228 | "mod": true,
229 | "+": true,
230 | "-": true,
231 | "=": true,
232 | "!=": true,
233 | "<": true,
234 | "<=": true,
235 | ">": true,
236 | ">=": true,
237 | }
238 |
239 | var andOrOps = map[string]bool{
240 | "and": true,
241 | "or": true,
242 | }
243 |
244 | func xfOperator(left, right tree.Result, f *xpFilt, op string) error {
245 | if booleanOps[op] {
246 | lNode, lOK := left.(tree.NodeSet)
247 | rNode, rOK := right.(tree.NodeSet)
248 | if lOK && rOK {
249 | return bothNodeOperator(lNode, rNode, f, op)
250 | }
251 |
252 | if lOK {
253 | return leftNodeOperator(lNode, right, f, op)
254 | }
255 |
256 | if rOK {
257 | return rightNodeOperator(left, rNode, f, op)
258 | }
259 |
260 | if eqOps[op] {
261 | return equalsOperator(left, right, f, op)
262 | }
263 | }
264 |
265 | if numOps[op] {
266 | return numberOperator(left, right, f, op)
267 | }
268 |
269 | if andOrOps[op] {
270 | return andOrOperator(left, right, f, op)
271 | }
272 |
273 | //if op == "|" {
274 | return unionOperator(left, right, f, op)
275 | //}
276 |
277 | //return fmt.Errorf("Unknown operator " + op)
278 | }
279 |
280 | func xfAbsLocPath(f *xpFilt, val string) {
281 | i := f.t
282 | for i.GetNodeType() != tree.NtRoot {
283 | i = i.GetParent()
284 | }
285 | f.ctx = tree.NodeSet{i}
286 | }
287 |
288 | func xfAbbrAbsLocPath(f *xpFilt, val string) {
289 | i := f.t
290 | for i.GetNodeType() != tree.NtRoot {
291 | i = i.GetParent()
292 | }
293 | f.ctx = tree.NodeSet{i}
294 | f.expr = abbrPathExpr()
295 | find(f)
296 | }
297 |
298 | func xfRelLocPath(f *xpFilt, val string) {
299 | }
300 |
301 | func xfAbbrRelLocPath(f *xpFilt, val string) {
302 | f.expr = abbrPathExpr()
303 | find(f)
304 | }
305 |
306 | func xfAxis(f *xpFilt, val string) {
307 | f.expr.Axis = val
308 | }
309 |
310 | func xfAbbrAxis(f *xpFilt, val string) {
311 | f.expr.Axis = xconst.AxisAttribute
312 | }
313 |
314 | func xfNCName(f *xpFilt, val string) {
315 | f.expr.Name.Space = val
316 | }
317 |
318 | func xfQName(f *xpFilt, val string) {
319 | f.expr.Name.Local = val
320 | find(f)
321 | }
322 |
323 | func xfNodeType(f *xpFilt, val string) {
324 | f.expr.NodeType = val
325 | find(f)
326 | }
327 |
328 | func xfProcInstLit(f *xpFilt, val string) {
329 | filt := tree.NodeSet{}
330 | for _, i := range f.ctx.(tree.NodeSet) {
331 | if i.GetToken().(xml.ProcInst).Target == val {
332 | filt = append(filt, i)
333 | }
334 | }
335 | f.ctx = filt
336 | }
337 |
338 | func xfStrLit(f *xpFilt, val string) {
339 | f.ctx = tree.String(val)
340 | }
341 |
342 | func xfNumLit(f *xpFilt, val string) {
343 | num, _ := strconv.ParseFloat(val, 64)
344 | f.ctx = tree.Num(num)
345 | }
346 |
347 | func abbrPathExpr() pathexpr.PathExpr {
348 | return pathexpr.PathExpr{
349 | Name: xml.Name{},
350 | Axis: xconst.AxisDescendentOrSelf,
351 | NodeType: xconst.NodeTypeNode,
352 | }
353 | }
354 |
355 | func find(f *xpFilt) {
356 | dupFilt := make(map[int]tree.Node)
357 | f.proxPos = make(map[int]int)
358 |
359 | if f.expr.Axis == "" && f.expr.NodeType == "" && f.expr.Name.Space == "" {
360 | if f.expr.Name.Local == "." {
361 | f.expr = pathexpr.PathExpr{
362 | Name: xml.Name{},
363 | Axis: xconst.AxisSelf,
364 | NodeType: xconst.NodeTypeNode,
365 | }
366 | }
367 |
368 | if f.expr.Name.Local == ".." {
369 | f.expr = pathexpr.PathExpr{
370 | Name: xml.Name{},
371 | Axis: xconst.AxisParent,
372 | NodeType: xconst.NodeTypeNode,
373 | }
374 | }
375 | }
376 |
377 | f.expr.NS = f.ns
378 |
379 | for _, i := range f.ctx.(tree.NodeSet) {
380 | for pos, j := range findutil.Find(i, f.expr) {
381 | dupFilt[j.Pos()] = j
382 | f.proxPos[j.Pos()] = pos + 1
383 | }
384 | }
385 |
386 | res := make(tree.NodeSet, 0, len(dupFilt))
387 | for _, i := range dupFilt {
388 | res = append(res, i)
389 | }
390 |
391 | xsort.SortNodes(res)
392 |
393 | f.expr = pathexpr.PathExpr{}
394 | f.ctxSize = len(res)
395 | f.ctx = res
396 | }
397 |
--------------------------------------------------------------------------------
/path_test.go:
--------------------------------------------------------------------------------
1 | package goxpath
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "runtime/debug"
7 | "testing"
8 |
9 | "github.com/ChrisTrenkamp/goxpath/tree"
10 | "github.com/ChrisTrenkamp/goxpath/tree/xmltree"
11 | )
12 |
13 | func execPath(xp, x string, exp []string, ns map[string]string, t *testing.T) {
14 | defer func() {
15 | if r := recover(); r != nil {
16 | t.Error("Panicked: from XPath expr: '" + xp)
17 | t.Error(r)
18 | t.Error(string(debug.Stack()))
19 | }
20 | }()
21 | res := MustParse(xp).MustExec(xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns }).(tree.NodeSet)
22 |
23 | if len(res) != len(exp) {
24 | t.Error("Result length not valid in XPath expression '"+xp+"':", len(res), ", expecting", len(exp))
25 | for i := range res {
26 | t.Error(MarshalStr(res[i].(tree.Node)))
27 | }
28 | return
29 | }
30 |
31 | for i := range res {
32 | r, err := MarshalStr(res[i].(tree.Node))
33 | if err != nil {
34 | t.Error(err.Error())
35 | return
36 | }
37 | valid := false
38 | for j := range exp {
39 | if r == exp[j] {
40 | valid = true
41 | }
42 | }
43 | if !valid {
44 | t.Error("Incorrect result in XPath expression '" + xp + "':" + r)
45 | t.Error("Expecting one of:")
46 | for j := range exp {
47 | t.Error(exp[j])
48 | }
49 | return
50 | }
51 | }
52 | }
53 |
54 | func TestRoot(t *testing.T) {
55 | p := `/`
56 | x := ``
57 | exp := []string{""}
58 | execPath(p, x, exp, nil, t)
59 | }
60 |
61 | func TestRoot2(t *testing.T) {
62 | x := xmltree.MustParseXML(bytes.NewBufferString(``))
63 | x = MustParse("/test/path").MustExec(x).(tree.NodeSet)[0]
64 | for _, i := range []string{"/", "//test"} {
65 | res, err := MarshalStr(MustParse(i).MustExec(x).(tree.NodeSet)[0])
66 | if err != nil {
67 | t.Error(err)
68 | }
69 | if res != "" {
70 | t.Error("Incorrect result", res)
71 | }
72 | }
73 | }
74 |
75 | func TestAbsPath(t *testing.T) {
76 | p := ` / test / path`
77 | x := ``
78 | exp := []string{""}
79 | execPath(p, x, exp, nil, t)
80 | }
81 |
82 | func TestNonAbsPath(t *testing.T) {
83 | p := ` test `
84 | x := ``
85 | exp := []string{""}
86 | execPath(p, x, exp, nil, t)
87 | }
88 |
89 | func TestRelPath(t *testing.T) {
90 | p := ` // path `
91 | x := ``
92 | exp := []string{""}
93 | execPath(p, x, exp, nil, t)
94 | }
95 |
96 | func TestRelPath2(t *testing.T) {
97 | p := ` // path `
98 | x := `test`
99 | exp := []string{"test", `test`}
100 | execPath(p, x, exp, nil, t)
101 | }
102 |
103 | func TestRelNonRootPath(t *testing.T) {
104 | p := ` / test // path `
105 | x := ``
106 | exp := []string{""}
107 | execPath(p, x, exp, nil, t)
108 | }
109 |
110 | func TestParent(t *testing.T) {
111 | p := `/test/path/ parent:: test`
112 | x := ``
113 | exp := []string{""}
114 | execPath(p, x, exp, nil, t)
115 | }
116 |
117 | func TestAncestor(t *testing.T) {
118 | p := `/p1/p2/p3/p1/ancestor::p1`
119 | x := ``
120 | exp := []string{``}
121 | execPath(p, x, exp, nil, t)
122 | }
123 |
124 | func TestAncestorOrSelf(t *testing.T) {
125 | p := `/p1/p2/p3/p1/ancestor-or-self::p1`
126 | x := ``
127 | exp := []string{``, ``}
128 | execPath(p, x, exp, nil, t)
129 | }
130 |
131 | func TestDescendant(t *testing.T) {
132 | p := `/p1/descendant::p1`
133 | x := ``
134 | exp := []string{``}
135 | execPath(p, x, exp, nil, t)
136 | }
137 |
138 | func TestDescendantOrSelf(t *testing.T) {
139 | p := `/p1/descendant-or-self::p1`
140 | x := ``
141 | exp := []string{``, ``}
142 | execPath(p, x, exp, nil, t)
143 | }
144 |
145 | func TestAttribute(t *testing.T) {
146 | p := `/p1/attribute::test`
147 | x := ``
148 | exp := []string{``}
149 | execPath(p, x, exp, nil, t)
150 | }
151 |
152 | func TestAttributeSelf(t *testing.T) {
153 | p := `/p1/attribute::test/self::node()`
154 | x := ``
155 | exp := []string{``}
156 | execPath(p, x, exp, nil, t)
157 | }
158 |
159 | func TestAttributeAbbr(t *testing.T) {
160 | p := `/p1/ @ test`
161 | x := ``
162 | exp := []string{``}
163 | execPath(p, x, exp, nil, t)
164 | }
165 |
166 | func TestAttributeNoChild(t *testing.T) {
167 | p := `/p1/attribute::test/p2`
168 | x := ``
169 | exp := []string{}
170 | execPath(p, x, exp, nil, t)
171 | }
172 |
173 | func TestAttributeParent(t *testing.T) {
174 | p := `/p1/attribute::test/ ..`
175 | x := ``
176 | exp := []string{``}
177 | execPath(p, x, exp, nil, t)
178 | }
179 |
180 | func TestNodeTypeNode(t *testing.T) {
181 | p := `/p1/child:: node( ) `
182 | x := ``
183 | exp := []string{``}
184 | execPath(p, x, exp, nil, t)
185 | }
186 |
187 | func TestNodeTypeNodeAbbr(t *testing.T) {
188 | p := `/p1/ .`
189 | x := ``
190 | exp := []string{``}
191 | execPath(p, x, exp, nil, t)
192 | }
193 |
194 | func TestNodeTypeParent(t *testing.T) {
195 | p := `/p1/p2/parent::node()`
196 | x := ``
197 | exp := []string{``}
198 | execPath(p, x, exp, nil, t)
199 | }
200 |
201 | func TestNodeTypeParentAbbr(t *testing.T) {
202 | p := `/p1/p2/..`
203 | x := ``
204 | exp := []string{``}
205 | execPath(p, x, exp, nil, t)
206 | }
207 |
208 | func TestFollowing(t *testing.T) {
209 | p := `//p3/following::node()`
210 | x := ``
211 | exp := []string{``, ``, ``}
212 | execPath(p, x, exp, nil, t)
213 | }
214 |
215 | func TestPreceding(t *testing.T) {
216 | p := `//p6/preceding::node()`
217 | x := ``
218 | exp := []string{``, ``, ``}
219 | execPath(p, x, exp, nil, t)
220 | }
221 |
222 | func TestPrecedingSibling(t *testing.T) {
223 | p := `//p4/preceding-sibling::node()`
224 | x := ``
225 | exp := []string{``}
226 | execPath(p, x, exp, nil, t)
227 | }
228 |
229 | func TestPrecedingSibling2(t *testing.T) {
230 | p := `/preceding-sibling::node()`
231 | x := ``
232 | exp := []string{}
233 | execPath(p, x, exp, nil, t)
234 | }
235 |
236 | func TestFollowingSibling(t *testing.T) {
237 | p := `//p2/following-sibling::node()`
238 | x := ``
239 | exp := []string{``}
240 | execPath(p, x, exp, nil, t)
241 | }
242 |
243 | func TestFollowingSibling2(t *testing.T) {
244 | p := `/following-sibling::node()`
245 | x := ``
246 | exp := []string{}
247 | execPath(p, x, exp, nil, t)
248 | }
249 |
250 | func TestComment(t *testing.T) {
251 | p := `// comment()`
252 | x := ``
253 | exp := []string{``}
254 | execPath(p, x, exp, nil, t)
255 | }
256 |
257 | func TestCommentInvPath(t *testing.T) {
258 | p := `//comment()/ self :: processing-instruction ( ) `
259 | x := ``
260 | exp := []string{}
261 | execPath(p, x, exp, nil, t)
262 | }
263 |
264 | func TestCommentParent(t *testing.T) {
265 | p := `//comment()/..`
266 | x := ``
267 | exp := []string{``}
268 | execPath(p, x, exp, nil, t)
269 | }
270 |
271 | func TestText(t *testing.T) {
272 | p := `//text()`
273 | x := `text`
274 | exp := []string{`text`}
275 | execPath(p, x, exp, nil, t)
276 | }
277 |
278 | func TestTextParent(t *testing.T) {
279 | p := `//text()/..`
280 | x := `text`
281 | exp := []string{`text`}
282 | execPath(p, x, exp, nil, t)
283 | }
284 |
285 | func TestProcInst(t *testing.T) {
286 | p := `//processing-instruction()`
287 | x := ``
288 | exp := []string{``}
289 | execPath(p, x, exp, nil, t)
290 | }
291 |
292 | func TestProcInstParent(t *testing.T) {
293 | p := `//processing-instruction()/..`
294 | x := ``
295 | exp := []string{``}
296 | execPath(p, x, exp, nil, t)
297 | }
298 |
299 | func TestProcInstInvPath(t *testing.T) {
300 | p := `//processing-instruction()/self::comment()`
301 | x := ``
302 | exp := []string{}
303 | execPath(p, x, exp, nil, t)
304 | }
305 |
306 | func TestProcInst2(t *testing.T) {
307 | p := `//processing-instruction( 'proc2' ) `
308 | x := ``
309 | exp := []string{``}
310 | execPath(p, x, exp, nil, t)
311 | }
312 |
313 | func TestNamespace(t *testing.T) {
314 | p := `/*:p1/*:p2/namespace::*`
315 | x := ``
316 | exp := []string{``, ``, ``}
317 | execPath(p, x, exp, nil, t)
318 | }
319 |
320 | func TestRootNamespace(t *testing.T) {
321 | p := `/namespace::*`
322 | x := ``
323 | exp := []string{}
324 | execPath(p, x, exp, nil, t)
325 | }
326 |
327 | func TestNamespaceXml(t *testing.T) {
328 | p := `/*:p1/ * : p2 /namespace::xml`
329 | x := ``
330 | exp := []string{``}
331 | execPath(p, x, exp, nil, t)
332 | }
333 |
334 | func TestNamespaceNodeType(t *testing.T) {
335 | p := `/*:p1/*:p2/namespace::foo/ self :: node ( ) `
336 | x := ``
337 | exp := []string{``}
338 | execPath(p, x, exp, nil, t)
339 | }
340 |
341 | func TestNamespaceNodeWildcard(t *testing.T) {
342 | p := `/*:p1/*:p2/namespace::*:foo`
343 | x := ``
344 | exp := []string{``}
345 | execPath(p, x, exp, nil, t)
346 | p = `/*:p1/*:p2/namespace::invalid:foo`
347 | exp = []string{}
348 | execPath(p, x, exp, nil, t)
349 | }
350 |
351 | func TestNamespaceChild(t *testing.T) {
352 | p := `/*:p1/namespace::*/ * : p2`
353 | x := ``
354 | exp := []string{}
355 | execPath(p, x, exp, nil, t)
356 | }
357 |
358 | func TestNamespaceParent(t *testing.T) {
359 | p := `/*:p1/*:p2/namespace::foo/..`
360 | x := ``
361 | exp := []string{``}
362 | execPath(p, x, exp, nil, t)
363 | }
364 |
365 | func TestNamespace2(t *testing.T) {
366 | p := `//namespace::test`
367 | x := ``
368 | exp := []string{``}
369 | execPath(p, x, exp, nil, t)
370 | }
371 |
372 | func TestNamespace3(t *testing.T) {
373 | p := `/p1/p2/p3/namespace :: foo`
374 | x := ``
375 | exp := []string{``}
376 | execPath(p, x, exp, nil, t)
377 | }
378 |
379 | func TestNamespace4(t *testing.T) {
380 | p := `/test:p1/p2/p3/namespace::*`
381 | x := ``
382 | exp := []string{``}
383 | execPath(p, x, exp, map[string]string{"test": "http://test"}, t)
384 | }
385 |
386 | func TestNodeNamespace(t *testing.T) {
387 | p := `/ test : p1 `
388 | x := ``
389 | exp := []string{``}
390 | execPath(p, x, exp, map[string]string{"test": "http://test"}, t)
391 | }
392 |
393 | func TestNodeNamespace2(t *testing.T) {
394 | p := `/test:p1/test2:p2`
395 | x := ``
396 | exp := []string{``}
397 | execPath(p, x, exp, map[string]string{"test": "http://test", "test2": "http://test2"}, t)
398 | }
399 |
400 | func TestNodeNamespace3(t *testing.T) {
401 | p := `/test:p1/foo:p2`
402 | x := ``
403 | exp := []string{``}
404 | execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
405 | }
406 |
407 | func TestAttrNamespace(t *testing.T) {
408 | p := `/test:p1/@foo:test`
409 | x := ``
410 | exp := []string{``}
411 | execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
412 | p = `/test:p1/@test:test`
413 | exp = []string{}
414 | execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
415 | }
416 |
417 | func TestWildcard(t *testing.T) {
418 | p := `/p1/*`
419 | x := ``
420 | exp := []string{``, ``, ``}
421 | execPath(p, x, exp, nil, t)
422 | }
423 |
424 | func TestWildcardNS(t *testing.T) {
425 | p := `//*:p1`
426 | x := ``
427 | exp := []string{``, ``}
428 | execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
429 | }
430 |
431 | func TestWildcardLocal(t *testing.T) {
432 | p := `//foo:*`
433 | x := ``
434 | exp := []string{``, ``}
435 | execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
436 | }
437 |
438 | func TestWildcardNSAttr(t *testing.T) {
439 | p := `/p1/@*:attr`
440 | x := ``
441 | exp := []string{``}
442 | execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
443 | }
444 |
445 | func TestWildcardLocalAttr(t *testing.T) {
446 | p := `/p1/@foo:*`
447 | x := ``
448 | exp := []string{``, ``}
449 | execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
450 | }
451 |
452 | func TestPredicate1(t *testing.T) {
453 | p := `/p1/p2 [ p3 ] `
454 | x := ``
455 | exp := []string{``}
456 | execPath(p, x, exp, nil, t)
457 | }
458 |
459 | func TestPredicate2(t *testing.T) {
460 | p := `/p1/p2 [ 2 ] `
461 | x := ``
462 | exp := []string{``}
463 | execPath(p, x, exp, nil, t)
464 | p = `/p1/p2 [ position() = 2 ] `
465 | execPath(p, x, exp, nil, t)
466 | }
467 |
468 | func TestPredicate3(t *testing.T) {
469 | p := `/p1/p2 [ last() ] /p3`
470 | x := ``
471 | exp := []string{``}
472 | execPath(p, x, exp, nil, t)
473 | }
474 |
475 | func TestPredicate4(t *testing.T) {
476 | p := `/p1/p2[not(@test)]`
477 | x := ``
478 | exp := []string{``, ``}
479 | execPath(p, x, exp, nil, t)
480 | }
481 |
482 | func TestPredicate5(t *testing.T) {
483 | p := `/p1/p2[.//p1]`
484 | x := `lkj`
485 | exp := []string{`lkj`}
486 | execPath(p, x, exp, nil, t)
487 | }
488 |
489 | func TestPredicate6(t *testing.T) {
490 | p := `/p1/p2[//p1]`
491 | x := `lkj`
492 | exp := []string{``, ``, `lkj`}
493 | execPath(p, x, exp, nil, t)
494 | }
495 |
496 | func TestPredicate7(t *testing.T) {
497 | p := `/p3[//p1]`
498 | x := `lkj`
499 | exp := []string{}
500 | execPath(p, x, exp, nil, t)
501 | }
502 |
503 | func TestPredicate8(t *testing.T) {
504 | p := `/test[@attr1='a' and @attr2='b']/a`
505 | x := `aaaabbb`
506 | exp := []string{`aaaa`}
507 | execPath(p, x, exp, nil, t)
508 | }
509 |
510 | func TestPredicate9(t *testing.T) {
511 | p := `/root/text[@attr="2"][2]`
512 | x := xml.Header + `
513 | This is some text 1.1.
514 | This is some text 1.2.
515 | This is some text. 2.1
516 | This is some text. 2.2
517 | This is some text. 2.3
518 | `
519 | exp := []string{`This is some text. 2.2`}
520 | execPath(p, x, exp, nil, t)
521 | }
522 |
523 | func TestPredicate10(t *testing.T) {
524 | p := `/root/text[@attr="2"][last()]`
525 | x := xml.Header + `
526 | This is some text 1.1.
527 | This is some text 1.2.
528 | This is some text. 2.1
529 | This is some text. 2.2
530 | This is some text. 2.3
531 | `
532 | exp := []string{`This is some text. 2.3`}
533 | execPath(p, x, exp, nil, t)
534 | }
535 |
536 | func TestUnion(t *testing.T) {
537 | p := `/test/test2 | /test/test3`
538 | x := `foobarhamneggs`
539 | exp := []string{"foobar", "hamneggs"}
540 | execPath(p, x, exp, nil, t)
541 | }
542 |
--------------------------------------------------------------------------------