pax_global_header 0000666 0000000 0000000 00000000064 13161151513 0014507 g ustar 00root root 0000000 0000000 52 comment=c385f95c6022e7756e91beac5f5510872f7dcb7d
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/ 0000775 0000000 0000000 00000000000 13161151513 0024320 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/.travis.yml 0000664 0000000 0000000 00000000753 13161151513 0026436 0 ustar 00root root 0000000 0000000 language: go
go:
- 1.6
before_install:
- go get -u github.com/ChrisTrenkamp/goxpath
- go get -u github.com/ChrisTrenkamp/goxpath/cmd/goxpath
- go get -u github.com/wadey/gocovmerge
script:
- go list -f '{{if gt (len .TestGoFiles) 0}}"go test -covermode count -coverprofile {{.Name}}.coverprofile -coverpkg ./... {{.ImportPath}}"{{end}}' ./... | xargs -I {} bash -c {}
- gocovmerge `ls *.coverprofile` > coverage.txt
after_success:
- bash <(curl -s https://codecov.io/bash)
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/LICENSE 0000664 0000000 0000000 00000002071 13161151513 0025325 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2015 ChrisTrenkamp
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/README.md 0000664 0000000 0000000 00000001260 13161151513 0025576 0 ustar 00root root 0000000 0000000 # goxpath [](https://godoc.org/github.com/ChrisTrenkamp/goxpath) [](https://travis-ci.org/ChrisTrenkamp/goxpath) [](https://codecov.io/github/ChrisTrenkamp/goxpath?branch=master) [](https://goreportcard.com/report/github.com/ChrisTrenkamp/goxpath)
An XPath 1.0 implementation written in Go. See the [wiki](https://github.com/ChrisTrenkamp/goxpath/wiki) for more information.
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/ 0000775 0000000 0000000 00000000000 13161151513 0025063 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/goxpath/ 0000775 0000000 0000000 00000000000 13161151513 0026535 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/goxpath/goxpath.go 0000664 0000000 0000000 00000007617 13161151513 0030551 0 ustar 00root root 0000000 0000000 package main
import (
"bytes"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/ChrisTrenkamp/goxpath"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
)
type namespace map[string]string
func (n *namespace) String() string {
return fmt.Sprint(*n)
}
func (n *namespace) Set(value string) error {
nsMap := strings.Split(value, "=")
if len(nsMap) != 2 {
nsErr = fmt.Errorf("Invalid namespace mapping: %s\n", value)
}
(*n)[nsMap[0]] = nsMap[1]
return nil
}
type variables map[string]string
func (v *variables) String() string {
return fmt.Sprint(*v)
}
func (v *variables) Set(value string) error {
varMap := strings.Split(value, "=")
if len(varMap) != 2 {
nsErr = fmt.Errorf("Invalid variable mapping: %s\n", value)
}
(*v)[varMap[0]] = varMap[1]
return nil
}
var rec bool
var value bool
var ns = make(namespace)
var vars = make(variables)
var nsErr error
var unstrict bool
var noFileName bool
var args = []string{}
var stdin io.Reader = os.Stdin
var stdout io.ReadWriter = os.Stdout
var stderr io.ReadWriter = os.Stderr
var retCode = 0
func main() {
exec()
os.Exit(retCode)
}
func exec() {
flag.BoolVar(&rec, "r", false, "Recursive")
flag.BoolVar(&value, "v", false, "Output the string value of the XPath result")
flag.Var(&ns, "ns", "Namespace mappings. e.g. -ns myns=http://example.com")
flag.Var(&vars, "var", "Variables mappings. e.g. -var myvar=myvalue")
flag.BoolVar(&unstrict, "u", false, "Turns off strict XML validation")
flag.BoolVar(&noFileName, "h", false, "Suppress filename prefixes.")
flag.Parse()
args = flag.Args()
if nsErr != nil {
fmt.Fprintf(stderr, nsErr.Error())
retCode = 1
return
}
if len(args) < 1 {
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")
retCode = 1
return
}
xp, err := goxpath.Parse(args[0])
if err != nil {
fmt.Fprintf(stderr, "%s\n", err.Error())
retCode = 1
return
}
if len(args) == 1 {
ret, err := runXPath(xp, stdin, ns, value)
if err != nil {
fmt.Fprintf(stderr, "%s\n", err.Error())
retCode = 1
}
printResult(ret, "")
}
for i := 1; i < len(args); i++ {
procPath(args[i], xp, ns, value)
}
}
func procPath(path string, x goxpath.XPathExec, ns namespace, value bool) {
fi, err := os.Stat(path)
if err != nil {
fmt.Fprintf(stderr, "Could not open file: %s\n", path)
retCode = 1
return
}
if fi.IsDir() {
procDir(path, x, ns, value)
return
}
data, _ := ioutil.ReadFile(path)
ret, err := runXPath(x, bytes.NewBuffer(data), ns, value)
if err != nil {
fmt.Fprintf(stderr, "%s: %s\n", path, err.Error())
retCode = 1
}
printResult(ret, path)
}
func printResult(ret []string, path string) {
for _, j := range ret {
if (len(flag.Args()) > 2 || rec) && !noFileName {
fmt.Fprintf(stdout, "%s:", path)
}
fmt.Fprintf(stdout, "%s\n", j)
}
}
func procDir(path string, x goxpath.XPathExec, ns namespace, value bool) {
if !rec {
fmt.Fprintf(stderr, "%s: Is a directory\n", path)
retCode = 1
return
}
list, _ := ioutil.ReadDir(path)
for _, i := range list {
procPath(filepath.Join(path, i.Name()), x, ns, value)
}
}
func runXPath(x goxpath.XPathExec, r io.Reader, ns namespace, value bool) ([]string, error) {
t, err := xmltree.ParseXML(r, func(o *xmltree.ParseOptions) {
o.Strict = !unstrict
})
if err != nil {
return nil, err
}
res, err := x.Exec(t, func(o *goxpath.Opts) {
o.NS = ns
for k, v := range vars {
o.Vars[k] = tree.String(v)
}
})
if err != nil {
return nil, err
}
var ret []string
if nodes, ok := res.(tree.NodeSet); ok && !value {
ret = make([]string, len(nodes))
for i, v := range nodes {
ret[i], _ = goxpath.MarshalStr(v)
ret[i] = strings.Replace(ret[i], "\n", "
", -1)
}
} else {
str := res.String()
if str != "" {
ret = strings.Split(str, "\n")
}
}
return ret, nil
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/goxpath/goxpath_test.go 0000664 0000000 0000000 00000010477 13161151513 0031606 0 ustar 00root root 0000000 0000000 package main
import (
"bytes"
"encoding/xml"
"flag"
"os"
"strings"
"testing"
)
func setup(in string, args ...string) (*bytes.Buffer, *bytes.Buffer) {
retCode = 0
os.Args = append([]string{"test"}, args...)
flag.CommandLine = flag.NewFlagSet("test", flag.ExitOnError)
out := &bytes.Buffer{}
err := &bytes.Buffer{}
stdout = out
stderr = err
stdin = strings.NewReader(in)
exec()
return out, err
}
func TestStdinVal(t *testing.T) {
out, _ := setup(xml.Header+"test", "-v", "/root/tag")
if out.String() != "test\n" {
t.Error("Expecting 'test' for the result. Recieved: ", out.String())
}
if retCode != 0 {
t.Error("Incorrect return value")
}
}
func TestStdinNonVal(t *testing.T) {
out, _ := setup(xml.Header+"test", "/root/tag")
if out.String() != "test\n" {
t.Error("Expecting 'test' for the result. Recieved: ", out.String())
}
if retCode != 0 {
t.Error("Incorrect return value")
}
}
func TestFile(t *testing.T) {
out, _ := setup("", "-ns", "foo=http://foo.bar", "/foo:test/foo:path", "test/1.xml")
if out.String() != `path`+"\n" {
t.Error(`Expecting 'path' for the result. Recieved: `, out.String())
}
if retCode != 0 {
t.Error("Incorrect return value")
}
}
func TestDir(t *testing.T) {
out, _ := setup("", "-r", "/foo", "test/subdir")
val := strings.Replace(out.String(), "test\\subdir\\", "test/subdir/", -1)
if val != `test/subdir/2.xml:bar`+"\n"+`test/subdir/3.xml:bar2`+"\n" {
t.Error(`Incorrect result. Recieved: `, val)
}
if retCode != 0 {
t.Error("Incorrect return value")
}
}
func TestDirNonRec(t *testing.T) {
_, err := setup("", "/foo", "test/subdir")
val := strings.Replace(err.String(), "test\\subdir\\", "test/subdir/", -1)
if val != `test/subdir: Is a directory`+"\n" {
t.Error(`Incorrect result. Recieved: `, val)
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
func TestNoXPath(t *testing.T) {
_, err := setup("")
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" {
t.Error("No XPath error")
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
func TestInvalidXPathExpr(t *testing.T) {
_, err := setup("", "/foo()", "test/1.xml")
if err.String() != "Invalid node-type foo\n" {
t.Error("Invalid XPath error")
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
func TestInvalidFilePath(t *testing.T) {
_, err := setup("", "/foo", "foo.xml")
if err.String() != "Could not open file: foo.xml\n" {
t.Error("Invalid error")
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
func TestXPathExecErr(t *testing.T) {
_, err := setup("", "foobar()", "test/1.xml")
if err.String() != "test/1.xml: Unknown function: foobar\n" {
t.Error("Invalid error", err.String())
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
func TestXPathExecErrStdin(t *testing.T) {
_, err := setup(xml.Header+"test", "foobar()")
if err.String() != "Unknown function: foobar\n" {
t.Error("Invalid error", err.String())
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
func TestInvalidXML(t *testing.T) {
_, err := setup("", "/root")
if err.String() != "XML syntax error on line 1: unexpected EOF\n" {
t.Error("Invalid error", err.String())
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
func TestVarRef(t *testing.T) {
out, _ := setup(xml.Header+"test", "-var=foo=test", "/root/tag = $foo")
if out.String() != "true\n" {
t.Error("Expecting 'true' for the result. Recieved: ", out.String())
}
if retCode != 0 {
t.Error("Incorrect return value")
}
}
func TestInvalidNSMap(t *testing.T) {
_, err := setup(xml.Header+"", "-ns=foo=http://foo=bar", "/root")
if err.String() != "Invalid namespace mapping: foo=http://foo=bar\n" {
t.Error("Invalid error", err.String())
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
func TestInvalidVarMap(t *testing.T) {
_, err := setup(xml.Header+"", "-var=test=blag=foo", "/root")
if err.String() != "Invalid variable mapping: test=blag=foo\n" {
t.Error("Invalid error", err.String())
}
if retCode != 1 {
t.Error("Incorrect return value")
}
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/goxpath/test/ 0000775 0000000 0000000 00000000000 13161151513 0027514 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/goxpath/test/1.xml 0000664 0000000 0000000 00000000145 13161151513 0030376 0 ustar 00root root 0000000 0000000 testpathtest2
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/goxpath/test/subdir/ 0000775 0000000 0000000 00000000000 13161151513 0031004 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/goxpath/test/subdir/2.xml 0000664 0000000 0000000 00000000065 13161151513 0031670 0 ustar 00root root 0000000 0000000 bar
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/cmd/goxpath/test/subdir/3.xml 0000664 0000000 0000000 00000000066 13161151513 0031672 0 ustar 00root root 0000000 0000000 bar2
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/comp_test.go 0000664 0000000 0000000 00000005131 13161151513 0026644 0 ustar 00root root 0000000 0000000 package goxpath
import (
"fmt"
"testing"
)
func TestAddition(t *testing.T) {
p := `1 + 1`
x := ``
exp := "2"
execVal(p, x, exp, nil, t)
}
func TestParenths(t *testing.T) {
p := `(1 + 2) * 3`
x := ``
exp := "9"
execVal(p, x, exp, nil, t)
}
func TestParenths2(t *testing.T) {
p := `(1 + 2 * 3)`
x := ``
exp := "7"
execVal(p, x, exp, nil, t)
}
func TestEquals(t *testing.T) {
p := `/test/test2 = 3`
x := `3`
exp := "true"
execVal(p, x, exp, nil, t)
}
func TestEqualsStr(t *testing.T) {
p := `/test/test2 = 'foobar'`
x := `foobar`
exp := "true"
execVal(p, x, exp, nil, t)
}
func TestEqualsStr2(t *testing.T) {
p := `/root[@test="foo"]`
x := `test`
exp := "test"
execVal(p, x, exp, nil, t)
}
func TestNumberOps(t *testing.T) {
x := `2352`
testFloatMap := make(map[string]float64)
testFloatMap[`/t/t1 * 3`] = 2 * 3
testFloatMap[`5 div /t/t1`] = 5.0 / 2.0
testFloatMap[`/t/t2 + /t/t3`] = 3 + 5
testFloatMap[`/t/t2 - /t/t3`] = 3 - 5
testFloatMap[`/t/t3 mod /t/t1`] = 5 % 2
testFloatMap[`number('5')`] = 5
testFloatMap[`sum(/t/*)`] = 2 + 3 + 5 + 2
testFloatMap[`floor(/t/t3 div /t/t1)`] = 2
testFloatMap[`ceiling(t/t3 div /t/t1)`] = 3
testFloatMap[`round(-1.5)`] = -2
testFloatMap[`round(1.5)`] = 2
testFloatMap[`round(0)`] = 0
for k, v := range testFloatMap {
execVal(k, x, fmt.Sprintf("%g", float64(v)), nil, t)
}
execVal(`/t/t3 div 0`, x, "Infinity", nil, t)
execVal(`-1 div 0`, x, "-Infinity", nil, t)
execVal(`0 div 0`, x, "NaN", nil, t)
testBoolMap := make(map[string]string)
testBoolMap[`/t/t1 = 2`] = "true"
testBoolMap[`2 = /t/t1`] = "true"
testBoolMap[`/t/t1 != 2`] = "false"
testBoolMap[`4 = /t/t2`] = "false"
testBoolMap[`/t/t1 != /t/t2`] = "true"
testBoolMap[`2 < /t/t4`] = "false"
testBoolMap[`/t/t1 <= 2`] = "true"
testBoolMap[`/t/t1 > /t/t4`] = "false"
testBoolMap[`2 >= /t/t4`] = "true"
testBoolMap[`/t/t1 >= /t/t4`] = "true"
testBoolMap[`/t/t1 != /t/t2 and /t/t1 < /t/t4`] = "false"
testBoolMap[`/t/t1 != /t/t2 or /t/t1 < /t/t4`] = "true"
testBoolMap[`(/t/t1 != /t/t2 or /t/t1 < /t/t4) = true()`] = "true"
testBoolMap[`(/t/t1 != /t/t2 and /t/t1 < /t/t4) != true()`] = "true"
for k, v := range testBoolMap {
execVal(k, x, v, nil, t)
}
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/coverage.sh 0000775 0000000 0000000 00000001144 13161151513 0026452 0 ustar 00root root 0000000 0000000 #!/bin/bash
cd "$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
go get github.com/ChrisTrenkamp/goxpath/cmd/goxpath
if [ $? != 0 ]; then
exit 1
fi
go test >/dev/null 2>&1
if [ $? != 0 ]; then
go test
exit 1
fi
gometalinter --deadline=1m ./...
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
gocovmerge `ls *.coverprofile` > coverage.txt
go tool cover -html=coverage.txt -o coverage.html
firefox coverage.html
rm coverage.html coverage.txt *.coverprofile
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/err_test.go 0000664 0000000 0000000 00000016166 13161151513 0026510 0 ustar 00root root 0000000 0000000 package goxpath
import (
"bytes"
"encoding/xml"
"runtime/debug"
"testing"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlele"
)
type dummyType string
func (d dummyType) String() string {
return string(d)
}
func dummyFunc(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
return dummyType(""), nil
}
var custFns = map[xml.Name]tree.Wrap{
{Local: "dummy"}: {Fn: dummyFunc},
{Space: "http://foo.com", Local: "spaceDummy"}: {Fn: dummyFunc},
}
func execErr(xp, x string, errStr string, ns map[string]string, t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("Panicked: from XPath expr: '" + xp)
t.Error(r)
t.Error(string(debug.Stack()))
}
}()
_, err := ParseExec(xp, xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns; o.Funcs = custFns })
if err.Error() != errStr {
t.Error("Incorrect result:'" + err.Error() + "' from XPath expr: '" + xp + "'. Expecting: '" + errStr + "'")
return
}
}
func TestBadAxis(t *testing.T) {
x := ``
execErr(`/test/chil::p2`, x, "Invalid Axis specifier, chil", nil, t)
}
func TestIncompleteStep(t *testing.T) {
x := ``
execErr(`/child::+2`, x, "Step is not complete", nil, t)
execErr(`/foo:`, x, "Step is not complete", nil, t)
}
func TestBadNodeType(t *testing.T) {
x := ``
execErr(`/test/foo()`, x, "Invalid node-type foo", nil, t)
}
func TestXPathErr(t *testing.T) {
x := ``
execErr(`/test/chil::p2`, x, "Invalid Axis specifier, chil", nil, t)
}
func TestNodeSetConvErr(t *testing.T) {
x := ``
for _, i := range []string{"sum", "count", "local-name", "namespace-uri", "name"} {
execErr("/p1["+i+"(1)]", x, "Cannot convert object to a node-set", nil, t)
}
}
func TestNodeSetConvUnionErr(t *testing.T) {
x := ``
execErr(`/p1 | 'invalid'`, x, "Cannot convert data type to node-set", nil, t)
}
func TestUnknownFunction(t *testing.T) {
x := ``
execErr(`invFunc()`, x, "Unknown function: invFunc", nil, t)
}
func TestUnterminatedString(t *testing.T) {
x := ``
execErr(`"asdf`, x, "Unexpected end of string literal.", nil, t)
}
func TestUnterminatedParenths(t *testing.T) {
x := ``
execErr(`(1 + 2 * 3`, x, "Missing end )", nil, t)
}
func TestUnterminatedNTQuotes(t *testing.T) {
x := ``
execErr(`//processing-instruction('foo)`, x, "Unexpected end of string literal.", nil, t)
}
func TestUnterminatedNTParenths(t *testing.T) {
x := ``
execErr(`//processing-instruction('foo'`, x, "Missing ) at end of NodeType declaration.", nil, t)
}
func TestUnterminatedFnParenths(t *testing.T) {
x := ``
execErr(`true(`, x, "Missing ) at end of function declaration.", nil, t)
}
func TestEmptyPred(t *testing.T) {
x := `text`
execErr(`/p1[ ]`, x, "Missing content in predicate.", nil, t)
}
func TestUnterminatedPred(t *testing.T) {
x := `text`
execErr(`/p1[. = 'text'`, x, "Missing ] at end of predicate.", nil, t)
}
func TestNotEnoughArgs(t *testing.T) {
x := `text`
execErr(`concat('test')`, x, "Invalid number of arguments", nil, t)
}
func TestMarshalErr(t *testing.T) {
x := ``
n := xmltree.MustParseXML(bytes.NewBufferString(x))
f := tree.FindNodeByPos(n, 3).(*xmlele.XMLEle)
f.Name.Local = ""
buf := &bytes.Buffer{}
err := Marshal(n, buf)
if err == nil {
t.Error("No error")
}
}
func TestParsePanic(t *testing.T) {
errs := 0
defer func() {
if errs != 1 {
t.Error("Err not 1")
}
}()
defer func() {
if r := recover(); r != nil {
errs++
}
}()
MustParse(`/foo()`)
}
func TestExecPanic(t *testing.T) {
errs := 0
defer func() {
if errs != 1 {
t.Error("Err not 1")
}
}()
defer func() {
if r := recover(); r != nil {
errs++
}
}()
MustParse("foo()").MustExec(xmltree.MustParseXML(bytes.NewBufferString(xml.Header + "")))
}
func TestDummyType(t *testing.T) {
ns := map[string]string{"foo": "http://foo.com"}
x := ``
execErr(`dummy() = 1`, x, "Cannot convert data type to number", nil, t)
execErr(`dummy() = true()`, x, "Cannot convert argument to boolean", nil, t)
execErr(`dummy() and true()`, x, "Cannot convert argument to boolean", nil, t)
execErr(`not(dummy()) = 1`, x, "Cannot convert object to a boolean", nil, t)
execErr(`1 = not(dummy())`, x, "Cannot convert object to a boolean", nil, t)
execErr(`not(dummy() = 1)`, x, "Cannot convert data type to number", nil, t)
execErr(`/p1[dummy()]`, x, "Cannot convert argument to boolean", nil, t)
for _, i := range []string{"boolean", "not"} {
execErr(i+`(dummy())`, x, "Cannot convert object to a boolean", nil, t)
}
for _, i := range []string{"number", "floor", "ceiling", "round"} {
execErr(i+`(dummy())`, x, "Cannot convert object to a number", nil, t)
}
execErr(`substring("12345", dummy(), 2)`, x, "Cannot convert object to a number", nil, t)
execErr(`substring("12345", 2, dummy())`, x, "Cannot convert object to a number", nil, t)
execErr(`foo:spaceDummy() = 1`, x, "Cannot convert data type to number", ns, t)
}
func TestGoxpathBool(t *testing.T) {
opts := func(o *Opts) { o.Funcs = custFns }
x := xmltree.MustParseXML(bytes.NewBufferString(``))
_, err := MustParse(`dummy() = 1`).ExecBool(x, opts)
if err == nil {
t.Error("Error not nil")
}
_, err = MustParse(`dummy()`).ExecBool(x, opts)
if err == nil {
t.Error("Error not nil")
}
b, err := MustParse(`/p1`).ExecBool(x, opts)
if !b || err != nil {
t.Error("Incorrect result")
}
}
func TestGoxpathNum(t *testing.T) {
opts := func(o *Opts) { o.Funcs = custFns }
x := xmltree.MustParseXML(bytes.NewBufferString(`3`))
_, err := MustParse(`dummy() = 1`).ExecNum(x, opts)
if err == nil {
t.Error("Error not nil")
}
_, err = MustParse(`dummy()`).ExecNum(x, opts)
if err == nil {
t.Error("Error not nil")
}
n, err := MustParse(`/p1`).ExecNum(x, opts)
if n != 3 || err != nil {
t.Error("Incorrect result")
}
}
func TestGoxpathNode(t *testing.T) {
opts := func(o *Opts) { o.Funcs = custFns }
x := xmltree.MustParseXML(bytes.NewBufferString(``))
_, err := MustParse(`dummy() = 1`).ExecNode(x, opts)
if err == nil {
t.Error("Error not nil")
}
_, err = MustParse(`dummy()`).ExecNode(x, opts)
if err == nil {
t.Error("Error not nil")
}
n, err := MustParse(`/p1`).ExecNode(x, opts)
if len(n) != 1 || err != nil {
t.Error("Incorrect result")
}
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/fns_test.go 0000664 0000000 0000000 00000020104 13161151513 0026471 0 ustar 00root root 0000000 0000000 package goxpath
import (
"bytes"
"encoding/xml"
"fmt"
"testing"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
)
func TestStrLit(t *testing.T) {
p := `'strlit'`
x := ``
exp := "strlit"
execVal(p, x, exp, nil, t)
}
func TestNumLit(t *testing.T) {
p := `123`
x := ``
exp := "123"
execVal(p, x, exp, nil, t)
p = `123.456`
exp = "123.456"
execVal(p, x, exp, nil, t)
}
func TestLast(t *testing.T) {
p := `/p1/*[last()]`
x := ``
exp := []string{""}
execPath(p, x, exp, nil, t)
p = `/p1/p5[last()]`
exp = []string{}
execPath(p, x, exp, nil, t)
p = `/p1[last()]`
exp = []string{""}
execPath(p, x, exp, nil, t)
}
func TestCount(t *testing.T) {
p := `count(//p1)`
x := ``
exp := "2"
execVal(p, x, exp, nil, t)
}
func TestCount2(t *testing.T) {
x := `
y31
y32
y21
y22
y11
y12
y03
y04
`
execVal(`count(//x)`, x, "7", nil, t)
execVal(`count(//x[1])`, x, "4", nil, t)
execVal(`count(//x/y)`, x, "8", nil, t)
execVal(`count(//x/y[1])`, x, "4", nil, t)
execVal(`count(//x[1]/y[1])`, x, "2", nil, t)
}
func TestNames(t *testing.T) {
x := ``
testMap := make(map[string]map[string]string)
testMap["/*"] = make(map[string]string)
testMap["/*"]["local-name"] = "test"
testMap["/*"]["namespace-uri"] = "http://foo.com"
testMap["/*"]["name"] = "{http://foo.com}test"
testMap["/none"] = make(map[string]string)
testMap["/none"]["local-name"] = ""
testMap["/none"]["namespace-uri"] = ""
testMap["/none"]["name"] = ""
testMap["/*/@*:attr"] = make(map[string]string)
testMap["/*/@*:attr"]["local-name"] = "attr"
testMap["/*/@*:attr"]["namespace-uri"] = "http://bar.com"
testMap["/*/@*:attr"]["name"] = "{http://bar.com}attr"
testMap["//processing-instruction()"] = make(map[string]string)
testMap["//processing-instruction()"]["local-name"] = "pi"
testMap["//processing-instruction()"]["namespace-uri"] = ""
testMap["//processing-instruction()"]["name"] = "pi"
testMap["//comment()"] = make(map[string]string)
testMap["//comment()"]["local-name"] = ""
testMap["//comment()"]["namespace-uri"] = ""
testMap["//comment()"]["name"] = ""
for path, i := range testMap {
for nt, res := range i {
p := fmt.Sprintf("%s(%s)", nt, path)
exp := res
execVal(p, x, exp, nil, t)
}
}
x = ``
execPath("/*[local-name() = 'test']", x, []string{``}, nil, t)
execPath("/*[namespace-uri() = 'http://foo.com']", x, []string{``}, nil, t)
execPath("/*[name() = '{http://foo.com}test']", x, []string{``}, nil, t)
}
func TestBoolean(t *testing.T) {
x := ``
execVal(`true()`, x, "true", nil, t)
execVal(`false()`, x, "false", nil, t)
p := `boolean(/p1/p2)`
exp := "true"
execVal(p, x, exp, nil, t)
p = `boolean(/p1/p5)`
exp = "false"
execVal(p, x, exp, nil, t)
p = `boolean('123')`
exp = "true"
execVal(p, x, exp, nil, t)
p = `boolean(123)`
exp = "true"
execVal(p, x, exp, nil, t)
p = `boolean('')`
exp = "false"
execVal(p, x, exp, nil, t)
p = `boolean(0)`
exp = "false"
execVal(p, x, exp, nil, t)
}
func TestNot(t *testing.T) {
x := ``
execVal(`not(false())`, x, "true", nil, t)
execVal(`not(true())`, x, "false", nil, t)
}
func TestConversions(t *testing.T) {
x := `foo`
execVal(`number(true())`, x, "1", nil, t)
execVal(`number(false())`, x, "0", nil, t)
execVal(`string(/p2)`, x, "", nil, t)
execVal(`/p1[string() = 'foo']`, x, "foo", nil, t)
execVal(`number('abc')`, x, "NaN", nil, t)
}
func TestLang(t *testing.T) {
x := `
I went up a floor.
I took the lift.
I rode the elevator.
`
execVal(`count(//p[lang('en')])`, x, "3", nil, t)
execVal(`count(//text()[lang('en-GB')])`, x, "1", nil, t)
execVal(`count(//p[lang('en-US')])`, x, "1", nil, t)
execVal(`count(//p[lang('de')])`, x, "0", nil, t)
execVal(`count(/p1[lang('en')])`, x, "0", nil, t)
}
func TestString(t *testing.T) {
x := `text`
execVal(`string(2 + 2)`, x, "4", nil, t)
execVal(`string(/p1)`, x, "text", nil, t)
}
func TestConcat(t *testing.T) {
x := ``
execVal(`concat('abc', 'def', 'hij', '123')`, x, "abcdefhij123", nil, t)
}
func TestStartsWith(t *testing.T) {
x := ``
execVal(`starts-with('abcd', 'ab')`, x, "true", nil, t)
execVal(`starts-with('abcd', 'abd')`, x, "false", nil, t)
}
func TestContains(t *testing.T) {
x := ``
execVal(`contains('abcd', 'bcd')`, x, "true", nil, t)
execVal(`contains('abcd', 'bd')`, x, "false", nil, t)
}
func TestSubstrBefore(t *testing.T) {
x := ``
execVal(`substring-before("1999/04/01","/")`, x, "1999", nil, t)
execVal(`substring-before("1999/04/01","2")`, x, "", nil, t)
}
func TestSubstrAfter(t *testing.T) {
x := ``
execVal(`substring-after("1999/04/01","/")`, x, "04/01", nil, t)
execVal(`substring-after("1999/04/01","19")`, x, "99/04/01", nil, t)
execVal(`substring-after("1999/04/01","a")`, x, "", nil, t)
}
func TestSubstring(t *testing.T) {
x := ``
execVal(`substring("12345", 2, 3)`, x, "234", nil, t)
execVal(`substring("12345", 2)`, x, "2345", nil, t)
execVal(`substring('abcd', -2, 5)`, x, "ab", nil, t)
execVal(`substring('abcd', 0)`, x, "abcd", nil, t)
execVal(`substring('abcd', 1, 4)`, x, "abcd", nil, t)
execVal(`substring("12345", 1.5, 2.6)`, x, "234", nil, t)
execVal(`substring("12345", 0 div 0, 3)`, x, "", nil, t)
execVal(`substring("12345", 1, 0 div 0)`, x, "", nil, t)
execVal(`substring("12345", -42, 1 div 0)`, x, "12345", nil, t)
execVal(`substring("12345", -1 div 0, 1 div 0)`, x, "", nil, t)
}
func TestStrLength(t *testing.T) {
x := `abc`
execVal(`string-length('def')`, x, "3", nil, t)
execVal(`/p1[string-length() = 3]`, x, "abc", nil, t)
}
func TestNormalizeSpace(t *testing.T) {
x := `
a b
`
execVal(`normalize-space(/p1)`, x, "a b", nil, t)
execVal(`/p1[normalize-space(/p1) = 'a b']`, x, `
a b
`, nil, t)
execVal(`/p1[normalize-space() = 'a b']`, x, `
a b
`, nil, t)
}
func TestTranslate(t *testing.T) {
x := ``
execVal(`translate("bar","abc","ABC")`, x, "BAr", nil, t)
execVal(`translate("--aaa--","abc-","ABC")`, x, "AAA", nil, t)
}
func TestPathAfterFunction(t *testing.T) {
x := xmltree.MustParseXML(bytes.NewBufferString(`
aabb
cc
`))
current, err := MustParse(`/root/a`).ExecNode(x)
if err != nil {
t.Error("err not nil")
}
currentFn := func(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
return current, nil
}
custFns := map[xml.Name]tree.Wrap{
{Local: "current"}: {Fn: currentFn},
}
opts := func(o *Opts) { o.Funcs = custFns }
result := MustParse(`/root/c/@a[. = current()/b]`).MustExec(x, opts)
if result.String() != `bb` {
t.Error("result not foo:", result.String())
}
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/goxpath.go 0000664 0000000 0000000 00000005537 13161151513 0026333 0 ustar 00root root 0000000 0000000 package goxpath
import (
"encoding/xml"
"fmt"
"github.com/ChrisTrenkamp/goxpath/internal/execxp"
"github.com/ChrisTrenkamp/goxpath/parser"
"github.com/ChrisTrenkamp/goxpath/tree"
)
//Opts defines namespace mappings and custom functions for XPath expressions.
type Opts struct {
NS map[string]string
Funcs map[xml.Name]tree.Wrap
Vars map[string]tree.Result
}
//FuncOpts is a function wrapper for Opts.
type FuncOpts func(*Opts)
//XPathExec is the XPath executor, compiled from an XPath string
type XPathExec struct {
n *parser.Node
}
//Parse parses the XPath expression, xp, returning an XPath executor.
func Parse(xp string) (XPathExec, error) {
n, err := parser.Parse(xp)
return XPathExec{n: n}, err
}
//MustParse is like Parse, but panics instead of returning an error.
func MustParse(xp string) XPathExec {
ret, err := Parse(xp)
if err != nil {
panic(err)
}
return ret
}
//Exec executes the XPath expression, xp, against the tree, t, with the
//namespace mappings, ns, and returns the result as a stringer.
func (xp XPathExec) Exec(t tree.Node, opts ...FuncOpts) (tree.Result, error) {
o := &Opts{
NS: make(map[string]string),
Funcs: make(map[xml.Name]tree.Wrap),
Vars: make(map[string]tree.Result),
}
for _, i := range opts {
i(o)
}
return execxp.Exec(xp.n, t, o.NS, o.Funcs, o.Vars)
}
//ExecBool is like Exec, except it will attempt to convert the result to its boolean value.
func (xp XPathExec) ExecBool(t tree.Node, opts ...FuncOpts) (bool, error) {
res, err := xp.Exec(t, opts...)
if err != nil {
return false, err
}
b, ok := res.(tree.IsBool)
if !ok {
return false, fmt.Errorf("Cannot convert result to a boolean")
}
return bool(b.Bool()), nil
}
//ExecNum is like Exec, except it will attempt to convert the result to its number value.
func (xp XPathExec) ExecNum(t tree.Node, opts ...FuncOpts) (float64, error) {
res, err := xp.Exec(t, opts...)
if err != nil {
return 0, err
}
n, ok := res.(tree.IsNum)
if !ok {
return 0, fmt.Errorf("Cannot convert result to a number")
}
return float64(n.Num()), nil
}
//ExecNode is like Exec, except it will attempt to return the result as a node-set.
func (xp XPathExec) ExecNode(t tree.Node, opts ...FuncOpts) (tree.NodeSet, error) {
res, err := xp.Exec(t, opts...)
if err != nil {
return nil, err
}
n, ok := res.(tree.NodeSet)
if !ok {
return nil, fmt.Errorf("Cannot convert result to a node-set")
}
return n, nil
}
//MustExec is like Exec, but panics instead of returning an error.
func (xp XPathExec) MustExec(t tree.Node, opts ...FuncOpts) tree.Result {
res, err := xp.Exec(t, opts...)
if err != nil {
panic(err)
}
return res
}
//ParseExec parses the XPath string, xpstr, and runs Exec.
func ParseExec(xpstr string, t tree.Node, opts ...FuncOpts) (tree.Result, error) {
xp, err := Parse(xpstr)
if err != nil {
return nil, err
}
return xp.Exec(t, opts...)
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/ 0000775 0000000 0000000 00000000000 13161151513 0026134 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/ 0000775 0000000 0000000 00000000000 13161151513 0027430 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/execxp.go 0000664 0000000 0000000 00000001135 13161151513 0031253 0 ustar 00root root 0000000 0000000 package execxp
import (
"encoding/xml"
"github.com/ChrisTrenkamp/goxpath/parser"
"github.com/ChrisTrenkamp/goxpath/tree"
)
//Exec executes the XPath expression, xp, against the tree, t, with the
//namespace mappings, ns.
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) {
f := xpFilt{
t: t,
ns: ns,
ctx: tree.NodeSet{t},
fns: fns,
variables: v,
}
return exec(&f, n)
}
func exec(f *xpFilt, n *parser.Node) (tree.Result, error) {
err := xfExec(f, n)
return f.ctx, err
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/findutil/ 0000775 0000000 0000000 00000000000 13161151513 0031246 5 ustar 00root root 0000000 0000000 findUtil.go 0000664 0000000 0000000 00000014031 13161151513 0033273 0 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/findutil package findutil
import (
"encoding/xml"
"github.com/ChrisTrenkamp/goxpath/parser/pathexpr"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/xconst"
)
const (
wildcard = "*"
)
type findFunc func(tree.Node, *pathexpr.PathExpr, *[]tree.Node)
var findMap = map[string]findFunc{
xconst.AxisAncestor: findAncestor,
xconst.AxisAncestorOrSelf: findAncestorOrSelf,
xconst.AxisAttribute: findAttribute,
xconst.AxisChild: findChild,
xconst.AxisDescendent: findDescendent,
xconst.AxisDescendentOrSelf: findDescendentOrSelf,
xconst.AxisFollowing: findFollowing,
xconst.AxisFollowingSibling: findFollowingSibling,
xconst.AxisNamespace: findNamespace,
xconst.AxisParent: findParent,
xconst.AxisPreceding: findPreceding,
xconst.AxisPrecedingSibling: findPrecedingSibling,
xconst.AxisSelf: findSelf,
}
//Find finds nodes based on the pathexpr.PathExpr
func Find(x tree.Node, p pathexpr.PathExpr) []tree.Node {
ret := []tree.Node{}
if p.Axis == "" {
findChild(x, &p, &ret)
return ret
}
f := findMap[p.Axis]
f(x, &p, &ret)
return ret
}
func findAncestor(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if x.GetNodeType() == tree.NtRoot {
return
}
addNode(x.GetParent(), p, ret)
findAncestor(x.GetParent(), p, ret)
}
func findAncestorOrSelf(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
findSelf(x, p, ret)
findAncestor(x, p, ret)
}
func findAttribute(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if ele, ok := x.(tree.Elem); ok {
for _, i := range ele.GetAttrs() {
addNode(i, p, ret)
}
}
}
func findChild(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if ele, ok := x.(tree.Elem); ok {
ch := ele.GetChildren()
for i := range ch {
addNode(ch[i], p, ret)
}
}
}
func findDescendent(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if ele, ok := x.(tree.Elem); ok {
ch := ele.GetChildren()
for i := range ch {
addNode(ch[i], p, ret)
findDescendent(ch[i], p, ret)
}
}
}
func findDescendentOrSelf(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
findSelf(x, p, ret)
findDescendent(x, p, ret)
}
func findFollowing(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if x.GetNodeType() == tree.NtRoot {
return
}
par := x.GetParent()
ch := par.GetChildren()
i := 0
for x != ch[i] {
i++
}
i++
for i < len(ch) {
findDescendentOrSelf(ch[i], p, ret)
i++
}
findFollowing(par, p, ret)
}
func findFollowingSibling(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if x.GetNodeType() == tree.NtRoot {
return
}
par := x.GetParent()
ch := par.GetChildren()
i := 0
for x != ch[i] {
i++
}
i++
for i < len(ch) {
findSelf(ch[i], p, ret)
i++
}
}
func findNamespace(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if ele, ok := x.(tree.NSElem); ok {
ns := tree.BuildNS(ele)
for _, i := range ns {
addNode(i, p, ret)
}
}
}
func findParent(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if x.GetNodeType() != tree.NtRoot {
addNode(x.GetParent(), p, ret)
}
}
func findPreceding(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if x.GetNodeType() == tree.NtRoot {
return
}
par := x.GetParent()
ch := par.GetChildren()
i := len(ch) - 1
for x != ch[i] {
i--
}
i--
for i >= 0 {
findDescendentOrSelf(ch[i], p, ret)
i--
}
findPreceding(par, p, ret)
}
func findPrecedingSibling(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
if x.GetNodeType() == tree.NtRoot {
return
}
par := x.GetParent()
ch := par.GetChildren()
i := len(ch) - 1
for x != ch[i] {
i--
}
i--
for i >= 0 {
findSelf(ch[i], p, ret)
i--
}
}
func findSelf(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
addNode(x, p, ret)
}
func addNode(x tree.Node, p *pathexpr.PathExpr, ret *[]tree.Node) {
add := false
tok := x.GetToken()
switch x.GetNodeType() {
case tree.NtAttr:
add = evalAttr(p, tok.(xml.Attr))
case tree.NtChd:
add = evalChd(p)
case tree.NtComm:
add = evalComm(p)
case tree.NtElem, tree.NtRoot:
add = evalEle(p, tok.(xml.StartElement))
case tree.NtNs:
add = evalNS(p, tok.(xml.Attr))
case tree.NtPi:
add = evalPI(p)
}
if add {
*ret = append(*ret, x)
}
}
func evalAttr(p *pathexpr.PathExpr, a xml.Attr) bool {
if p.NodeType == "" {
if p.Name.Space != wildcard {
if a.Name.Space != p.NS[p.Name.Space] {
return false
}
}
if p.Name.Local == wildcard && p.Axis == xconst.AxisAttribute {
return true
}
if p.Name.Local == a.Name.Local {
return true
}
} else {
if p.NodeType == xconst.NodeTypeNode {
return true
}
}
return false
}
func evalChd(p *pathexpr.PathExpr) bool {
if p.NodeType == xconst.NodeTypeText || p.NodeType == xconst.NodeTypeNode {
return true
}
return false
}
func evalComm(p *pathexpr.PathExpr) bool {
if p.NodeType == xconst.NodeTypeComment || p.NodeType == xconst.NodeTypeNode {
return true
}
return false
}
func evalEle(p *pathexpr.PathExpr, ele xml.StartElement) bool {
if p.NodeType == "" {
return checkNameAndSpace(p, ele)
}
if p.NodeType == xconst.NodeTypeNode {
return true
}
return false
}
func checkNameAndSpace(p *pathexpr.PathExpr, ele xml.StartElement) bool {
if p.Name.Local == wildcard && p.Name.Space == "" {
return true
}
if p.Name.Space != wildcard && ele.Name.Space != p.NS[p.Name.Space] {
return false
}
if p.Name.Local == wildcard && p.Axis != xconst.AxisAttribute && p.Axis != xconst.AxisNamespace {
return true
}
if p.Name.Local == ele.Name.Local {
return true
}
return false
}
func evalNS(p *pathexpr.PathExpr, ns xml.Attr) bool {
if p.NodeType == "" {
if p.Name.Space != "" && p.Name.Space != wildcard {
return false
}
if p.Name.Local == wildcard && p.Axis == xconst.AxisNamespace {
return true
}
if p.Name.Local == ns.Name.Local {
return true
}
} else {
if p.NodeType == xconst.NodeTypeNode {
return true
}
}
return false
}
func evalPI(p *pathexpr.PathExpr) bool {
if p.NodeType == xconst.NodeTypeProcInst || p.NodeType == xconst.NodeTypeNode {
return true
}
return false
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/intfns/ 0000775 0000000 0000000 00000000000 13161151513 0030731 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/intfns/boolfns.go0000664 0000000 0000000 00000003310 13161151513 0032717 0 ustar 00root root 0000000 0000000 package intfns
import (
"fmt"
"github.com/ChrisTrenkamp/goxpath/tree"
"golang.org/x/text/language"
)
func boolean(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
if b, ok := args[0].(tree.IsBool); ok {
return b.Bool(), nil
}
return nil, fmt.Errorf("Cannot convert object to a boolean")
}
func not(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
b, ok := args[0].(tree.IsBool)
if !ok {
return nil, fmt.Errorf("Cannot convert object to a boolean")
}
return !b.Bool(), nil
}
func _true(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
return tree.Bool(true), nil
}
func _false(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
return tree.Bool(false), nil
}
func lang(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
lStr := args[0].String()
var n tree.Elem
for _, i := range c.NodeSet {
if i.GetNodeType() == tree.NtElem {
n = i.(tree.Elem)
} else {
n = i.GetParent()
}
for n.GetNodeType() != tree.NtRoot {
if attr, ok := tree.GetAttribute(n, "lang", tree.XMLSpace); ok {
return checkLang(lStr, attr.Value), nil
}
n = n.GetParent()
}
}
return tree.Bool(false), nil
}
func checkLang(srcStr, targStr string) tree.Bool {
srcLang := language.Make(srcStr)
srcRegion, srcRegionConf := srcLang.Region()
targLang := language.Make(targStr)
targRegion, targRegionConf := targLang.Region()
if srcRegionConf == language.Exact && targRegionConf != language.Exact {
return tree.Bool(false)
}
if srcRegion != targRegion && srcRegionConf == language.Exact && targRegionConf == language.Exact {
return tree.Bool(false)
}
_, _, conf := language.NewMatcher([]language.Tag{srcLang}).Match(targLang)
return tree.Bool(conf >= language.High)
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/intfns/intfns.go 0000664 0000000 0000000 00000003415 13161151513 0032564 0 ustar 00root root 0000000 0000000 package intfns
import (
"encoding/xml"
"github.com/ChrisTrenkamp/goxpath/tree"
)
//BuiltIn contains the list of built-in XPath functions
var BuiltIn = map[xml.Name]tree.Wrap{
//String functions
{Local: "string"}: {Fn: _string, NArgs: 1, LastArgOpt: tree.Optional},
{Local: "concat"}: {Fn: concat, NArgs: 3, LastArgOpt: tree.Variadic},
{Local: "starts-with"}: {Fn: startsWith, NArgs: 2},
{Local: "contains"}: {Fn: contains, NArgs: 2},
{Local: "substring-before"}: {Fn: substringBefore, NArgs: 2},
{Local: "substring-after"}: {Fn: substringAfter, NArgs: 2},
{Local: "substring"}: {Fn: substring, NArgs: 3, LastArgOpt: tree.Optional},
{Local: "string-length"}: {Fn: stringLength, NArgs: 1, LastArgOpt: tree.Optional},
{Local: "normalize-space"}: {Fn: normalizeSpace, NArgs: 1, LastArgOpt: tree.Optional},
{Local: "translate"}: {Fn: translate, NArgs: 3},
//Node set functions
{Local: "last"}: {Fn: last},
{Local: "position"}: {Fn: position},
{Local: "count"}: {Fn: count, NArgs: 1},
{Local: "local-name"}: {Fn: localName, NArgs: 1, LastArgOpt: tree.Optional},
{Local: "namespace-uri"}: {Fn: namespaceURI, NArgs: 1, LastArgOpt: tree.Optional},
{Local: "name"}: {Fn: name, NArgs: 1, LastArgOpt: tree.Optional},
//boolean functions
{Local: "boolean"}: {Fn: boolean, NArgs: 1},
{Local: "not"}: {Fn: not, NArgs: 1},
{Local: "true"}: {Fn: _true},
{Local: "false"}: {Fn: _false},
{Local: "lang"}: {Fn: lang, NArgs: 1},
//number functions
{Local: "number"}: {Fn: number, NArgs: 1, LastArgOpt: tree.Optional},
{Local: "sum"}: {Fn: sum, NArgs: 1},
{Local: "floor"}: {Fn: floor, NArgs: 1},
{Local: "ceiling"}: {Fn: ceiling, NArgs: 1},
{Local: "round"}: {Fn: round, NArgs: 1},
}
nodesetfns.go 0000664 0000000 0000000 00000004743 13161151513 0033361 0 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/intfns package intfns
import (
"encoding/xml"
"fmt"
"github.com/ChrisTrenkamp/goxpath/tree"
)
func last(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
return tree.Num(c.Size), nil
}
func position(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
return tree.Num(c.Pos), nil
}
func count(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
n, ok := args[0].(tree.NodeSet)
if !ok {
return nil, fmt.Errorf("Cannot convert object to a node-set")
}
return tree.Num(len(n)), nil
}
func localName(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
var n tree.NodeSet
ok := true
if len(args) == 1 {
n, ok = args[0].(tree.NodeSet)
} else {
n = c.NodeSet
}
if !ok {
return nil, fmt.Errorf("Cannot convert object to a node-set")
}
ret := ""
if len(n) == 0 {
return tree.String(ret), nil
}
node := n[0]
tok := node.GetToken()
switch node.GetNodeType() {
case tree.NtElem:
ret = tok.(xml.StartElement).Name.Local
case tree.NtAttr:
ret = tok.(xml.Attr).Name.Local
case tree.NtPi:
ret = tok.(xml.ProcInst).Target
}
return tree.String(ret), nil
}
func namespaceURI(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
var n tree.NodeSet
ok := true
if len(args) == 1 {
n, ok = args[0].(tree.NodeSet)
} else {
n = c.NodeSet
}
if !ok {
return nil, fmt.Errorf("Cannot convert object to a node-set")
}
ret := ""
if len(n) == 0 {
return tree.String(ret), nil
}
node := n[0]
tok := node.GetToken()
switch node.GetNodeType() {
case tree.NtElem:
ret = tok.(xml.StartElement).Name.Space
case tree.NtAttr:
ret = tok.(xml.Attr).Name.Space
}
return tree.String(ret), nil
}
func name(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
var n tree.NodeSet
ok := true
if len(args) == 1 {
n, ok = args[0].(tree.NodeSet)
} else {
n = c.NodeSet
}
if !ok {
return nil, fmt.Errorf("Cannot convert object to a node-set")
}
ret := ""
if len(n) == 0 {
return tree.String(ret), nil
}
node := n[0]
switch node.GetNodeType() {
case tree.NtElem:
t := node.GetToken().(xml.StartElement)
space := ""
if t.Name.Space != "" {
space = fmt.Sprintf("{%s}", t.Name.Space)
}
ret = fmt.Sprintf("%s%s", space, t.Name.Local)
case tree.NtAttr:
t := node.GetToken().(xml.Attr)
space := ""
if t.Name.Space != "" {
space = fmt.Sprintf("{%s}", t.Name.Space)
}
ret = fmt.Sprintf("%s%s", space, t.Name.Local)
case tree.NtPi:
ret = fmt.Sprintf("%s", node.GetToken().(xml.ProcInst).Target)
}
return tree.String(ret), nil
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/intfns/numfns.go 0000664 0000000 0000000 00000002624 13161151513 0032572 0 ustar 00root root 0000000 0000000 package intfns
import (
"fmt"
"math"
"github.com/ChrisTrenkamp/goxpath/tree"
)
func number(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
if b, ok := args[0].(tree.IsNum); ok {
return b.Num(), nil
}
return nil, fmt.Errorf("Cannot convert object to a number")
}
func sum(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
n, ok := args[0].(tree.NodeSet)
if !ok {
return nil, fmt.Errorf("Cannot convert object to a node-set")
}
ret := 0.0
for _, i := range n {
ret += float64(tree.GetNodeNum(i))
}
return tree.Num(ret), nil
}
func floor(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
n, ok := args[0].(tree.IsNum)
if !ok {
return nil, fmt.Errorf("Cannot convert object to a number")
}
return tree.Num(math.Floor(float64(n.Num()))), nil
}
func ceiling(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
n, ok := args[0].(tree.IsNum)
if !ok {
return nil, fmt.Errorf("Cannot convert object to a number")
}
return tree.Num(math.Ceil(float64(n.Num()))), nil
}
func round(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
isn, ok := args[0].(tree.IsNum)
if !ok {
return nil, fmt.Errorf("Cannot convert object to a number")
}
n := isn.Num()
if math.IsNaN(float64(n)) || math.IsInf(float64(n), 0) {
return n, nil
}
if n < -0.5 {
n = tree.Num(int(n - 0.5))
} else if n > 0.5 {
n = tree.Num(int(n + 0.5))
} else {
n = 0
}
return n, nil
}
stringfns.go 0000664 0000000 0000000 00000005526 13161151513 0033226 0 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/intfns package intfns
import (
"math"
"regexp"
"strings"
"github.com/ChrisTrenkamp/goxpath/tree"
)
func _string(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
if len(args) == 1 {
return tree.String(args[0].String()), nil
}
return tree.String(c.NodeSet.String()), nil
}
func concat(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
ret := ""
for _, i := range args {
ret += i.String()
}
return tree.String(ret), nil
}
func startsWith(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
return tree.Bool(strings.Index(args[0].String(), args[1].String()) == 0), nil
}
func contains(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
return tree.Bool(strings.Contains(args[0].String(), args[1].String())), nil
}
func substringBefore(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
ind := strings.Index(args[0].String(), args[1].String())
if ind == -1 {
return tree.String(""), nil
}
return tree.String(args[0].String()[:ind]), nil
}
func substringAfter(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
ind := strings.Index(args[0].String(), args[1].String())
if ind == -1 {
return tree.String(""), nil
}
return tree.String(args[0].String()[ind+len(args[1].String()):]), nil
}
func substring(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
str := args[0].String()
bNum, bErr := round(c, args[1])
if bErr != nil {
return nil, bErr
}
b := bNum.(tree.Num).Num()
if float64(b-1) >= float64(len(str)) || math.IsNaN(float64(b)) {
return tree.String(""), nil
}
if len(args) == 2 {
if b <= 1 {
b = 1
}
return tree.String(str[int(b)-1:]), nil
}
eNum, eErr := round(c, args[2])
if eErr != nil {
return nil, eErr
}
e := eNum.(tree.Num).Num()
if e <= 0 || math.IsNaN(float64(e)) || (math.IsInf(float64(b), 0) && math.IsInf(float64(e), 0)) {
return tree.String(""), nil
}
if b <= 1 {
e = b + e - 1
b = 1
}
if float64(b+e-1) >= float64(len(str)) {
e = tree.Num(len(str)) - b + 1
}
return tree.String(str[int(b)-1 : int(b+e)-1]), nil
}
func stringLength(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
var str string
if len(args) == 1 {
str = args[0].String()
} else {
str = c.NodeSet.String()
}
return tree.Num(len(str)), nil
}
var spaceTrim = regexp.MustCompile(`\s+`)
func normalizeSpace(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
var str string
if len(args) == 1 {
str = args[0].String()
} else {
str = c.NodeSet.String()
}
str = strings.TrimSpace(str)
return tree.String(spaceTrim.ReplaceAllString(str, " ")), nil
}
func translate(c tree.Ctx, args ...tree.Result) (tree.Result, error) {
ret := args[0].String()
src := args[1].String()
repl := args[2].String()
for i := range src {
r := ""
if i < len(repl) {
r = string(repl[i])
}
ret = strings.Replace(ret, string(src[i]), r, -1)
}
return tree.String(ret), nil
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/operators.go 0000664 0000000 0000000 00000010040 13161151513 0031770 0 ustar 00root root 0000000 0000000 package execxp
import (
"fmt"
"math"
"github.com/ChrisTrenkamp/goxpath/tree"
)
func bothNodeOperator(left tree.NodeSet, right tree.NodeSet, f *xpFilt, op string) error {
var err error
for _, l := range left {
for _, r := range right {
lStr := l.ResValue()
rStr := r.ResValue()
if eqOps[op] {
err = equalsOperator(tree.String(lStr), tree.String(rStr), f, op)
if err == nil && f.ctx.String() == tree.True {
return nil
}
} else {
err = numberOperator(tree.String(lStr), tree.String(rStr), f, op)
if err == nil && f.ctx.String() == tree.True {
return nil
}
}
}
}
f.ctx = tree.Bool(false)
return nil
}
func leftNodeOperator(left tree.NodeSet, right tree.Result, f *xpFilt, op string) error {
var err error
for _, l := range left {
lStr := l.ResValue()
if eqOps[op] {
err = equalsOperator(tree.String(lStr), right, f, op)
if err == nil && f.ctx.String() == tree.True {
return nil
}
} else {
err = numberOperator(tree.String(lStr), right, f, op)
if err == nil && f.ctx.String() == tree.True {
return nil
}
}
}
f.ctx = tree.Bool(false)
return nil
}
func rightNodeOperator(left tree.Result, right tree.NodeSet, f *xpFilt, op string) error {
var err error
for _, r := range right {
rStr := r.ResValue()
if eqOps[op] {
err = equalsOperator(left, tree.String(rStr), f, op)
if err == nil && f.ctx.String() == "true" {
return nil
}
} else {
err = numberOperator(left, tree.String(rStr), f, op)
if err == nil && f.ctx.String() == "true" {
return nil
}
}
}
f.ctx = tree.Bool(false)
return nil
}
func equalsOperator(left, right tree.Result, f *xpFilt, op string) error {
_, lOK := left.(tree.Bool)
_, rOK := right.(tree.Bool)
if lOK || rOK {
lTest, lt := left.(tree.IsBool)
rTest, rt := right.(tree.IsBool)
if !lt || !rt {
return fmt.Errorf("Cannot convert argument to boolean")
}
if op == "=" {
f.ctx = tree.Bool(lTest.Bool() == rTest.Bool())
} else {
f.ctx = tree.Bool(lTest.Bool() != rTest.Bool())
}
return nil
}
_, lOK = left.(tree.Num)
_, rOK = right.(tree.Num)
if lOK || rOK {
return numberOperator(left, right, f, op)
}
lStr := left.String()
rStr := right.String()
if op == "=" {
f.ctx = tree.Bool(lStr == rStr)
} else {
f.ctx = tree.Bool(lStr != rStr)
}
return nil
}
func numberOperator(left, right tree.Result, f *xpFilt, op string) error {
lt, lOK := left.(tree.IsNum)
rt, rOK := right.(tree.IsNum)
if !lOK || !rOK {
return fmt.Errorf("Cannot convert data type to number")
}
ln, rn := lt.Num(), rt.Num()
switch op {
case "*":
f.ctx = ln * rn
case "div":
if rn != 0 {
f.ctx = ln / rn
} else {
if ln == 0 {
f.ctx = tree.Num(math.NaN())
} else {
if math.Signbit(float64(ln)) == math.Signbit(float64(rn)) {
f.ctx = tree.Num(math.Inf(1))
} else {
f.ctx = tree.Num(math.Inf(-1))
}
}
}
case "mod":
f.ctx = tree.Num(int(ln) % int(rn))
case "+":
f.ctx = ln + rn
case "-":
f.ctx = ln - rn
case "=":
f.ctx = tree.Bool(ln == rn)
case "!=":
f.ctx = tree.Bool(ln != rn)
case "<":
f.ctx = tree.Bool(ln < rn)
case "<=":
f.ctx = tree.Bool(ln <= rn)
case ">":
f.ctx = tree.Bool(ln > rn)
case ">=":
f.ctx = tree.Bool(ln >= rn)
}
return nil
}
func andOrOperator(left, right tree.Result, f *xpFilt, op string) error {
lt, lOK := left.(tree.IsBool)
rt, rOK := right.(tree.IsBool)
if !lOK || !rOK {
return fmt.Errorf("Cannot convert argument to boolean")
}
l, r := lt.Bool(), rt.Bool()
if op == "and" {
f.ctx = l && r
} else {
f.ctx = l || r
}
return nil
}
func unionOperator(left, right tree.Result, f *xpFilt, op string) error {
lNode, lOK := left.(tree.NodeSet)
rNode, rOK := right.(tree.NodeSet)
if !lOK || !rOK {
return fmt.Errorf("Cannot convert data type to node-set")
}
uniq := make(map[int]tree.Node)
for _, i := range lNode {
uniq[i.Pos()] = i
}
for _, i := range rNode {
uniq[i.Pos()] = i
}
res := make(tree.NodeSet, 0, len(uniq))
for _, v := range uniq {
res = append(res, v)
}
f.ctx = res
return nil
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/execxp/paths.go 0000664 0000000 0000000 00000017252 13161151513 0031105 0 ustar 00root root 0000000 0000000 package execxp
import (
"encoding/xml"
"fmt"
"strconv"
"strings"
"github.com/ChrisTrenkamp/goxpath/internal/execxp/findutil"
"github.com/ChrisTrenkamp/goxpath/internal/execxp/intfns"
"github.com/ChrisTrenkamp/goxpath/internal/xsort"
"github.com/ChrisTrenkamp/goxpath/lexer"
"github.com/ChrisTrenkamp/goxpath/parser"
"github.com/ChrisTrenkamp/goxpath/parser/pathexpr"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/xconst"
)
type xpFilt struct {
t tree.Node
ctx tree.Result
expr pathexpr.PathExpr
ns map[string]string
ctxPos int
ctxSize int
proxPos map[int]int
fns map[xml.Name]tree.Wrap
variables map[string]tree.Result
}
type xpExecFn func(*xpFilt, string)
var xpFns = map[lexer.XItemType]xpExecFn{
lexer.XItemAbsLocPath: xfAbsLocPath,
lexer.XItemAbbrAbsLocPath: xfAbbrAbsLocPath,
lexer.XItemRelLocPath: xfRelLocPath,
lexer.XItemAbbrRelLocPath: xfAbbrRelLocPath,
lexer.XItemAxis: xfAxis,
lexer.XItemAbbrAxis: xfAbbrAxis,
lexer.XItemNCName: xfNCName,
lexer.XItemQName: xfQName,
lexer.XItemNodeType: xfNodeType,
lexer.XItemProcLit: xfProcInstLit,
lexer.XItemStrLit: xfStrLit,
lexer.XItemNumLit: xfNumLit,
}
func xfExec(f *xpFilt, n *parser.Node) (err error) {
for n != nil {
if fn, ok := xpFns[n.Val.Typ]; ok {
fn(f, n.Val.Val)
n = n.Left
} else if n.Val.Typ == lexer.XItemPredicate {
if err = xfPredicate(f, n.Left); err != nil {
return
}
n = n.Right
} else if n.Val.Typ == lexer.XItemFunction {
if err = xfFunction(f, n); err != nil {
return
}
n = n.Right
} else if n.Val.Typ == lexer.XItemOperator {
lf := xpFilt{
t: f.t,
ns: f.ns,
ctx: f.ctx,
ctxPos: f.ctxPos,
ctxSize: f.ctxSize,
proxPos: f.proxPos,
fns: f.fns,
variables: f.variables,
}
left, err := exec(&lf, n.Left)
if err != nil {
return err
}
rf := xpFilt{
t: f.t,
ns: f.ns,
ctx: f.ctx,
fns: f.fns,
variables: f.variables,
}
right, err := exec(&rf, n.Right)
if err != nil {
return err
}
return xfOperator(left, right, f, n.Val.Val)
} else if n.Val.Typ == lexer.XItemVariable {
if res, ok := f.variables[n.Val.Val]; ok {
f.ctx = res
return nil
}
return fmt.Errorf("Invalid variable '%s'", n.Val.Val)
} else if string(n.Val.Typ) == "" {
n = n.Left
//} else {
// return fmt.Errorf("Cannot process " + string(n.Val.Typ))
}
}
return
}
func xfPredicate(f *xpFilt, n *parser.Node) (err error) {
res := f.ctx.(tree.NodeSet)
newRes := make(tree.NodeSet, 0, len(res))
for i := range res {
pf := xpFilt{
t: f.t,
ns: f.ns,
ctxPos: i,
ctxSize: f.ctxSize,
ctx: tree.NodeSet{res[i]},
fns: f.fns,
variables: f.variables,
}
predRes, err := exec(&pf, n)
if err != nil {
return err
}
ok, err := checkPredRes(predRes, f, res[i])
if err != nil {
return err
}
if ok {
newRes = append(newRes, res[i])
}
}
f.proxPos = make(map[int]int)
for pos, j := range newRes {
f.proxPos[j.Pos()] = pos + 1
}
f.ctx = newRes
f.ctxSize = len(newRes)
return
}
func checkPredRes(ret tree.Result, f *xpFilt, node tree.Node) (bool, error) {
if num, ok := ret.(tree.Num); ok {
if float64(f.proxPos[node.Pos()]) == float64(num) {
return true, nil
}
return false, nil
}
if b, ok := ret.(tree.IsBool); ok {
return bool(b.Bool()), nil
}
return false, fmt.Errorf("Cannot convert argument to boolean")
}
func xfFunction(f *xpFilt, n *parser.Node) error {
spl := strings.Split(n.Val.Val, ":")
var name xml.Name
if len(spl) == 1 {
name.Local = spl[0]
} else {
name.Space = f.ns[spl[0]]
name.Local = spl[1]
}
fn, ok := intfns.BuiltIn[name]
if !ok {
fn, ok = f.fns[name]
}
if ok {
args := []tree.Result{}
param := n.Left
for param != nil {
pf := xpFilt{
t: f.t,
ctx: f.ctx,
ns: f.ns,
ctxPos: f.ctxPos,
ctxSize: f.ctxSize,
fns: f.fns,
variables: f.variables,
}
res, err := exec(&pf, param.Left)
if err != nil {
return err
}
args = append(args, res)
param = param.Right
}
filt, err := fn.Call(tree.Ctx{NodeSet: f.ctx.(tree.NodeSet), Size: f.ctxSize, Pos: f.ctxPos + 1}, args...)
f.ctx = filt
return err
}
return fmt.Errorf("Unknown function: %s", n.Val.Val)
}
var eqOps = map[string]bool{
"=": true,
"!=": true,
}
var booleanOps = map[string]bool{
"=": true,
"!=": true,
"<": true,
"<=": true,
">": true,
">=": true,
}
var numOps = map[string]bool{
"*": true,
"div": true,
"mod": true,
"+": true,
"-": true,
"=": true,
"!=": true,
"<": true,
"<=": true,
">": true,
">=": true,
}
var andOrOps = map[string]bool{
"and": true,
"or": true,
}
func xfOperator(left, right tree.Result, f *xpFilt, op string) error {
if booleanOps[op] {
lNode, lOK := left.(tree.NodeSet)
rNode, rOK := right.(tree.NodeSet)
if lOK && rOK {
return bothNodeOperator(lNode, rNode, f, op)
}
if lOK {
return leftNodeOperator(lNode, right, f, op)
}
if rOK {
return rightNodeOperator(left, rNode, f, op)
}
if eqOps[op] {
return equalsOperator(left, right, f, op)
}
}
if numOps[op] {
return numberOperator(left, right, f, op)
}
if andOrOps[op] {
return andOrOperator(left, right, f, op)
}
//if op == "|" {
return unionOperator(left, right, f, op)
//}
//return fmt.Errorf("Unknown operator " + op)
}
func xfAbsLocPath(f *xpFilt, val string) {
i := f.t
for i.GetNodeType() != tree.NtRoot {
i = i.GetParent()
}
f.ctx = tree.NodeSet{i}
}
func xfAbbrAbsLocPath(f *xpFilt, val string) {
i := f.t
for i.GetNodeType() != tree.NtRoot {
i = i.GetParent()
}
f.ctx = tree.NodeSet{i}
f.expr = abbrPathExpr()
find(f)
}
func xfRelLocPath(f *xpFilt, val string) {
}
func xfAbbrRelLocPath(f *xpFilt, val string) {
f.expr = abbrPathExpr()
find(f)
}
func xfAxis(f *xpFilt, val string) {
f.expr.Axis = val
}
func xfAbbrAxis(f *xpFilt, val string) {
f.expr.Axis = xconst.AxisAttribute
}
func xfNCName(f *xpFilt, val string) {
f.expr.Name.Space = val
}
func xfQName(f *xpFilt, val string) {
f.expr.Name.Local = val
find(f)
}
func xfNodeType(f *xpFilt, val string) {
f.expr.NodeType = val
find(f)
}
func xfProcInstLit(f *xpFilt, val string) {
filt := tree.NodeSet{}
for _, i := range f.ctx.(tree.NodeSet) {
if i.GetToken().(xml.ProcInst).Target == val {
filt = append(filt, i)
}
}
f.ctx = filt
}
func xfStrLit(f *xpFilt, val string) {
f.ctx = tree.String(val)
}
func xfNumLit(f *xpFilt, val string) {
num, _ := strconv.ParseFloat(val, 64)
f.ctx = tree.Num(num)
}
func abbrPathExpr() pathexpr.PathExpr {
return pathexpr.PathExpr{
Name: xml.Name{},
Axis: xconst.AxisDescendentOrSelf,
NodeType: xconst.NodeTypeNode,
}
}
func find(f *xpFilt) {
dupFilt := make(map[int]tree.Node)
f.proxPos = make(map[int]int)
if f.expr.Axis == "" && f.expr.NodeType == "" && f.expr.Name.Space == "" {
if f.expr.Name.Local == "." {
f.expr = pathexpr.PathExpr{
Name: xml.Name{},
Axis: xconst.AxisSelf,
NodeType: xconst.NodeTypeNode,
}
}
if f.expr.Name.Local == ".." {
f.expr = pathexpr.PathExpr{
Name: xml.Name{},
Axis: xconst.AxisParent,
NodeType: xconst.NodeTypeNode,
}
}
}
f.expr.NS = f.ns
for _, i := range f.ctx.(tree.NodeSet) {
for pos, j := range findutil.Find(i, f.expr) {
dupFilt[j.Pos()] = j
f.proxPos[j.Pos()] = pos + 1
}
}
res := make(tree.NodeSet, 0, len(dupFilt))
for _, i := range dupFilt {
res = append(res, i)
}
xsort.SortNodes(res)
f.expr = pathexpr.PathExpr{}
f.ctxSize = len(res)
f.ctx = res
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/xsort/ 0000775 0000000 0000000 00000000000 13161151513 0027313 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/internal/xsort/xsort.go 0000664 0000000 0000000 00000000642 13161151513 0031023 0 ustar 00root root 0000000 0000000 package xsort
import (
"sort"
"github.com/ChrisTrenkamp/goxpath/tree"
)
type nodeSort []tree.Node
func (ns nodeSort) Len() int { return len(ns) }
func (ns nodeSort) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
func (ns nodeSort) Less(i, j int) bool {
return ns[i].Pos() < ns[j].Pos()
}
//SortNodes sorts the array by the node document order
func SortNodes(res []tree.Node) {
sort.Sort(nodeSort(res))
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/lexer/ 0000775 0000000 0000000 00000000000 13161151513 0025437 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/lexer/lexer.go 0000664 0000000 0000000 00000016466 13161151513 0027122 0 ustar 00root root 0000000 0000000 package lexer
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
const (
//XItemError is an error with the parser input
XItemError XItemType = "Error"
//XItemAbsLocPath is an absolute path
XItemAbsLocPath = "Absolute path"
//XItemAbbrAbsLocPath represents an abbreviated absolute path
XItemAbbrAbsLocPath = "Abbreviated absolute path"
//XItemAbbrRelLocPath marks the start of a path expression
XItemAbbrRelLocPath = "Abbreviated relative path"
//XItemRelLocPath represents a relative location path
XItemRelLocPath = "Relative path"
//XItemEndPath marks the end of a path
XItemEndPath = "End path instruction"
//XItemAxis marks an axis specifier of a path
XItemAxis = "Axis"
//XItemAbbrAxis marks an abbreviated axis specifier (just @ at this point)
XItemAbbrAxis = "Abbreviated attribute axis"
//XItemNCName marks a namespace name in a node test
XItemNCName = "Namespace"
//XItemQName marks the local name in an a node test
XItemQName = "Local name"
//XItemNodeType marks a node type in a node test
XItemNodeType = "Node type"
//XItemProcLit marks a processing-instruction literal
XItemProcLit = "processing-instruction"
//XItemFunction marks a function call
XItemFunction = "function"
//XItemArgument marks a function argument
XItemArgument = "function argument"
//XItemEndFunction marks the end of a function
XItemEndFunction = "end of function"
//XItemPredicate marks a predicate in an axis
XItemPredicate = "predicate"
//XItemEndPredicate marks a predicate in an axis
XItemEndPredicate = "end of predicate"
//XItemStrLit marks a string literal
XItemStrLit = "string literal"
//XItemNumLit marks a numeric literal
XItemNumLit = "numeric literal"
//XItemOperator marks an operator
XItemOperator = "operator"
//XItemVariable marks a variable reference
XItemVariable = "variable"
)
const (
eof = -(iota + 1)
)
//XItemType is the parser token types
type XItemType string
//XItem is the token emitted from the parser
type XItem struct {
Typ XItemType
Val string
}
type stateFn func(*Lexer) stateFn
//Lexer lexes out XPath expressions
type Lexer struct {
input string
start int
pos int
width int
items chan XItem
}
//Lex an XPath expresion on the io.Reader
func Lex(xpath string) chan XItem {
l := &Lexer{
input: xpath,
items: make(chan XItem),
}
go l.run()
return l.items
}
func (l *Lexer) run() {
for state := startState; state != nil; {
state = state(l)
}
if l.peek() != eof {
l.errorf("Malformed XPath expression")
}
close(l.items)
}
func (l *Lexer) emit(t XItemType) {
l.items <- XItem{t, l.input[l.start:l.pos]}
l.start = l.pos
}
func (l *Lexer) emitVal(t XItemType, val string) {
l.items <- XItem{t, val}
l.start = l.pos
}
func (l *Lexer) next() (r rune) {
if l.pos >= len(l.input) {
l.width = 0
return eof
}
r, l.width = utf8.DecodeRuneInString(l.input[l.pos:])
l.pos += l.width
return r
}
func (l *Lexer) ignore() {
l.start = l.pos
}
func (l *Lexer) backup() {
l.pos -= l.width
}
func (l *Lexer) peek() rune {
r := l.next()
l.backup()
return r
}
func (l *Lexer) peekAt(n int) rune {
if n <= 1 {
return l.peek()
}
width := 0
var ret rune
for count := 0; count < n; count++ {
r, s := utf8.DecodeRuneInString(l.input[l.pos+width:])
width += s
if l.pos+width > len(l.input) {
return eof
}
ret = r
}
return ret
}
func (l *Lexer) accept(valid string) bool {
if strings.ContainsRune(valid, l.next()) {
return true
}
l.backup()
return false
}
func (l *Lexer) acceptRun(valid string) {
for strings.ContainsRune(valid, l.next()) {
}
l.backup()
}
func (l *Lexer) skip(num int) {
for i := 0; i < num; i++ {
l.next()
}
l.ignore()
}
func (l *Lexer) skipWS(ig bool) {
for {
n := l.next()
if n == eof || !unicode.IsSpace(n) {
break
}
}
l.backup()
if ig {
l.ignore()
}
}
func (l *Lexer) errorf(format string, args ...interface{}) stateFn {
l.items <- XItem{
XItemError,
fmt.Sprintf(format, args...),
}
return nil
}
func isElemChar(r rune) bool {
return string(r) != ":" && string(r) != "/" &&
(unicode.Is(first, r) || unicode.Is(second, r) || string(r) == "*") &&
r != eof
}
func startState(l *Lexer) stateFn {
l.skipWS(true)
if string(l.peek()) == "/" {
l.next()
l.ignore()
if string(l.next()) == "/" {
l.ignore()
return abbrAbsLocPathState
}
l.backup()
return absLocPathState
} else if string(l.peek()) == `'` || string(l.peek()) == `"` {
if err := getStrLit(l, XItemStrLit); err != nil {
return l.errorf(err.Error())
}
if l.peek() != eof {
return startState
}
} else if getNumLit(l) {
l.skipWS(true)
if l.peek() != eof {
return startState
}
} else if string(l.peek()) == "$" {
l.next()
l.ignore()
r := l.peek()
for unicode.Is(first, r) || unicode.Is(second, r) {
l.next()
r = l.peek()
}
tok := l.input[l.start:l.pos]
if len(tok) == 0 {
return l.errorf("Empty variable name")
}
l.emit(XItemVariable)
l.skipWS(true)
if l.peek() != eof {
return startState
}
} else if st := findOperatorState(l); st != nil {
return st
} else {
if isElemChar(l.peek()) {
colons := 0
for {
if isElemChar(l.peek()) {
l.next()
} else if string(l.peek()) == ":" {
l.next()
colons++
} else {
break
}
}
if string(l.peek()) == "(" && colons <= 1 {
tok := l.input[l.start:l.pos]
err := procFunc(l, tok)
if err != nil {
return l.errorf(err.Error())
}
l.skipWS(true)
if string(l.peek()) == "/" {
l.next()
l.ignore()
if string(l.next()) == "/" {
l.ignore()
return abbrRelLocPathState
}
l.backup()
return relLocPathState
}
return startState
}
l.pos = l.start
return relLocPathState
} else if string(l.peek()) == "@" {
return relLocPathState
}
}
return nil
}
func strPeek(str string, l *Lexer) bool {
for i := 0; i < len(str); i++ {
if string(l.peekAt(i+1)) != string(str[i]) {
return false
}
}
return true
}
func findOperatorState(l *Lexer) stateFn {
l.skipWS(true)
switch string(l.peek()) {
case ">", "<", "!":
l.next()
if string(l.peek()) == "=" {
l.next()
}
l.emit(XItemOperator)
return startState
case "|", "+", "-", "*", "=":
l.next()
l.emit(XItemOperator)
return startState
case "(":
l.next()
l.emit(XItemOperator)
for state := startState; state != nil; {
state = state(l)
}
l.skipWS(true)
if string(l.next()) != ")" {
return l.errorf("Missing end )")
}
l.emit(XItemOperator)
return startState
}
if strPeek("and", l) {
l.next()
l.next()
l.next()
l.emit(XItemOperator)
return startState
}
if strPeek("or", l) {
l.next()
l.next()
l.emit(XItemOperator)
return startState
}
if strPeek("mod", l) {
l.next()
l.next()
l.next()
l.emit(XItemOperator)
return startState
}
if strPeek("div", l) {
l.next()
l.next()
l.next()
l.emit(XItemOperator)
return startState
}
return nil
}
func getStrLit(l *Lexer, tok XItemType) error {
q := l.next()
var r rune
l.ignore()
for r != q {
r = l.next()
if r == eof {
return fmt.Errorf("Unexpected end of string literal.")
}
}
l.backup()
l.emit(tok)
l.next()
l.ignore()
return nil
}
func getNumLit(l *Lexer) bool {
const dig = "0123456789"
l.accept("-")
start := l.pos
l.acceptRun(dig)
if l.pos == start {
return false
}
if l.accept(".") {
l.acceptRun(dig)
}
l.emit(XItemNumLit)
return true
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/lexer/paths.go 0000664 0000000 0000000 00000010137 13161151513 0027107 0 ustar 00root root 0000000 0000000 package lexer
import (
"fmt"
"github.com/ChrisTrenkamp/goxpath/xconst"
)
func absLocPathState(l *Lexer) stateFn {
l.emit(XItemAbsLocPath)
return stepState
}
func abbrAbsLocPathState(l *Lexer) stateFn {
l.emit(XItemAbbrAbsLocPath)
return stepState
}
func relLocPathState(l *Lexer) stateFn {
l.emit(XItemRelLocPath)
return stepState
}
func abbrRelLocPathState(l *Lexer) stateFn {
l.emit(XItemAbbrRelLocPath)
return stepState
}
func stepState(l *Lexer) stateFn {
l.skipWS(true)
r := l.next()
for isElemChar(r) {
r = l.next()
}
l.backup()
tok := l.input[l.start:l.pos]
state, err := parseSeparators(l, tok)
if err != nil {
return l.errorf(err.Error())
}
return getNextPathState(l, state)
}
func parseSeparators(l *Lexer, tok string) (XItemType, error) {
l.skipWS(false)
state := XItemType(XItemQName)
r := l.peek()
if string(r) == ":" && string(l.peekAt(2)) == ":" {
var err error
if state, err = getAxis(l, tok); err != nil {
return state, fmt.Errorf(err.Error())
}
} else if string(r) == ":" {
state = XItemNCName
l.emitVal(state, tok)
l.skip(1)
l.skipWS(true)
} else if string(r) == "@" {
state = XItemAbbrAxis
l.emitVal(state, tok)
l.skip(1)
l.skipWS(true)
} else if string(r) == "(" {
var err error
if state, err = getNT(l, tok); err != nil {
return state, fmt.Errorf(err.Error())
}
} else if len(tok) > 0 {
l.emitVal(state, tok)
}
return state, nil
}
func getAxis(l *Lexer, tok string) (XItemType, error) {
var state XItemType
for i := range xconst.AxisNames {
if tok == xconst.AxisNames[i] {
state = XItemAxis
}
}
if state != XItemAxis {
return state, fmt.Errorf("Invalid Axis specifier, %s", tok)
}
l.emitVal(state, tok)
l.skip(2)
l.skipWS(true)
return state, nil
}
func getNT(l *Lexer, tok string) (XItemType, error) {
isNT := false
for _, i := range xconst.NodeTypes {
if tok == i {
isNT = true
break
}
}
if isNT {
return procNT(l, tok)
}
return XItemError, fmt.Errorf("Invalid node-type " + tok)
}
func procNT(l *Lexer, tok string) (XItemType, error) {
state := XItemType(XItemNodeType)
l.emitVal(state, tok)
l.skip(1)
l.skipWS(true)
n := l.peek()
if tok == xconst.NodeTypeProcInst && (string(n) == `"` || string(n) == `'`) {
if err := getStrLit(l, XItemProcLit); err != nil {
return state, fmt.Errorf(err.Error())
}
l.skipWS(true)
n = l.next()
}
if string(n) != ")" {
return state, fmt.Errorf("Missing ) at end of NodeType declaration.")
}
l.skip(1)
return state, nil
}
func procFunc(l *Lexer, tok string) error {
state := XItemType(XItemFunction)
l.emitVal(state, tok)
l.skip(1)
l.skipWS(true)
if string(l.peek()) != ")" {
l.emit(XItemArgument)
for {
for state := startState; state != nil; {
state = state(l)
}
l.skipWS(true)
if string(l.peek()) == "," {
l.emit(XItemArgument)
l.skip(1)
} else if string(l.peek()) == ")" {
l.emit(XItemEndFunction)
l.skip(1)
break
} else if l.peek() == eof {
return fmt.Errorf("Missing ) at end of function declaration.")
}
}
} else {
l.emit(XItemEndFunction)
l.skip(1)
}
return nil
}
func getNextPathState(l *Lexer, state XItemType) stateFn {
isMultiPart := state == XItemAxis || state == XItemAbbrAxis || state == XItemNCName
l.skipWS(true)
for string(l.peek()) == "[" {
if err := getPred(l); err != nil {
return l.errorf(err.Error())
}
}
if string(l.peek()) == "/" && !isMultiPart {
l.skip(1)
if string(l.peek()) == "/" {
l.skip(1)
return abbrRelLocPathState
}
l.skipWS(true)
return relLocPathState
} else if isMultiPart && isElemChar(l.peek()) {
return stepState
}
if isMultiPart {
return l.errorf("Step is not complete")
}
l.emit(XItemEndPath)
return findOperatorState
}
func getPred(l *Lexer) error {
l.emit(XItemPredicate)
l.skip(1)
l.skipWS(true)
if string(l.peek()) == "]" {
return fmt.Errorf("Missing content in predicate.")
}
for state := startState; state != nil; {
state = state(l)
}
l.skipWS(true)
if string(l.peek()) != "]" {
return fmt.Errorf("Missing ] at end of predicate.")
}
l.skip(1)
l.emit(XItemEndPredicate)
l.skipWS(true)
return nil
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/lexer/xmlchars.go 0000664 0000000 0000000 00000016132 13161151513 0027612 0 ustar 00root root 0000000 0000000 package lexer
import "unicode"
//first and second was copied from src/encoding/xml/xml.go
var first = &unicode.RangeTable{
R16: []unicode.Range16{
{0x003A, 0x003A, 1},
{0x0041, 0x005A, 1},
{0x005F, 0x005F, 1},
{0x0061, 0x007A, 1},
{0x00C0, 0x00D6, 1},
{0x00D8, 0x00F6, 1},
{0x00F8, 0x00FF, 1},
{0x0100, 0x0131, 1},
{0x0134, 0x013E, 1},
{0x0141, 0x0148, 1},
{0x014A, 0x017E, 1},
{0x0180, 0x01C3, 1},
{0x01CD, 0x01F0, 1},
{0x01F4, 0x01F5, 1},
{0x01FA, 0x0217, 1},
{0x0250, 0x02A8, 1},
{0x02BB, 0x02C1, 1},
{0x0386, 0x0386, 1},
{0x0388, 0x038A, 1},
{0x038C, 0x038C, 1},
{0x038E, 0x03A1, 1},
{0x03A3, 0x03CE, 1},
{0x03D0, 0x03D6, 1},
{0x03DA, 0x03E0, 2},
{0x03E2, 0x03F3, 1},
{0x0401, 0x040C, 1},
{0x040E, 0x044F, 1},
{0x0451, 0x045C, 1},
{0x045E, 0x0481, 1},
{0x0490, 0x04C4, 1},
{0x04C7, 0x04C8, 1},
{0x04CB, 0x04CC, 1},
{0x04D0, 0x04EB, 1},
{0x04EE, 0x04F5, 1},
{0x04F8, 0x04F9, 1},
{0x0531, 0x0556, 1},
{0x0559, 0x0559, 1},
{0x0561, 0x0586, 1},
{0x05D0, 0x05EA, 1},
{0x05F0, 0x05F2, 1},
{0x0621, 0x063A, 1},
{0x0641, 0x064A, 1},
{0x0671, 0x06B7, 1},
{0x06BA, 0x06BE, 1},
{0x06C0, 0x06CE, 1},
{0x06D0, 0x06D3, 1},
{0x06D5, 0x06D5, 1},
{0x06E5, 0x06E6, 1},
{0x0905, 0x0939, 1},
{0x093D, 0x093D, 1},
{0x0958, 0x0961, 1},
{0x0985, 0x098C, 1},
{0x098F, 0x0990, 1},
{0x0993, 0x09A8, 1},
{0x09AA, 0x09B0, 1},
{0x09B2, 0x09B2, 1},
{0x09B6, 0x09B9, 1},
{0x09DC, 0x09DD, 1},
{0x09DF, 0x09E1, 1},
{0x09F0, 0x09F1, 1},
{0x0A05, 0x0A0A, 1},
{0x0A0F, 0x0A10, 1},
{0x0A13, 0x0A28, 1},
{0x0A2A, 0x0A30, 1},
{0x0A32, 0x0A33, 1},
{0x0A35, 0x0A36, 1},
{0x0A38, 0x0A39, 1},
{0x0A59, 0x0A5C, 1},
{0x0A5E, 0x0A5E, 1},
{0x0A72, 0x0A74, 1},
{0x0A85, 0x0A8B, 1},
{0x0A8D, 0x0A8D, 1},
{0x0A8F, 0x0A91, 1},
{0x0A93, 0x0AA8, 1},
{0x0AAA, 0x0AB0, 1},
{0x0AB2, 0x0AB3, 1},
{0x0AB5, 0x0AB9, 1},
{0x0ABD, 0x0AE0, 0x23},
{0x0B05, 0x0B0C, 1},
{0x0B0F, 0x0B10, 1},
{0x0B13, 0x0B28, 1},
{0x0B2A, 0x0B30, 1},
{0x0B32, 0x0B33, 1},
{0x0B36, 0x0B39, 1},
{0x0B3D, 0x0B3D, 1},
{0x0B5C, 0x0B5D, 1},
{0x0B5F, 0x0B61, 1},
{0x0B85, 0x0B8A, 1},
{0x0B8E, 0x0B90, 1},
{0x0B92, 0x0B95, 1},
{0x0B99, 0x0B9A, 1},
{0x0B9C, 0x0B9C, 1},
{0x0B9E, 0x0B9F, 1},
{0x0BA3, 0x0BA4, 1},
{0x0BA8, 0x0BAA, 1},
{0x0BAE, 0x0BB5, 1},
{0x0BB7, 0x0BB9, 1},
{0x0C05, 0x0C0C, 1},
{0x0C0E, 0x0C10, 1},
{0x0C12, 0x0C28, 1},
{0x0C2A, 0x0C33, 1},
{0x0C35, 0x0C39, 1},
{0x0C60, 0x0C61, 1},
{0x0C85, 0x0C8C, 1},
{0x0C8E, 0x0C90, 1},
{0x0C92, 0x0CA8, 1},
{0x0CAA, 0x0CB3, 1},
{0x0CB5, 0x0CB9, 1},
{0x0CDE, 0x0CDE, 1},
{0x0CE0, 0x0CE1, 1},
{0x0D05, 0x0D0C, 1},
{0x0D0E, 0x0D10, 1},
{0x0D12, 0x0D28, 1},
{0x0D2A, 0x0D39, 1},
{0x0D60, 0x0D61, 1},
{0x0E01, 0x0E2E, 1},
{0x0E30, 0x0E30, 1},
{0x0E32, 0x0E33, 1},
{0x0E40, 0x0E45, 1},
{0x0E81, 0x0E82, 1},
{0x0E84, 0x0E84, 1},
{0x0E87, 0x0E88, 1},
{0x0E8A, 0x0E8D, 3},
{0x0E94, 0x0E97, 1},
{0x0E99, 0x0E9F, 1},
{0x0EA1, 0x0EA3, 1},
{0x0EA5, 0x0EA7, 2},
{0x0EAA, 0x0EAB, 1},
{0x0EAD, 0x0EAE, 1},
{0x0EB0, 0x0EB0, 1},
{0x0EB2, 0x0EB3, 1},
{0x0EBD, 0x0EBD, 1},
{0x0EC0, 0x0EC4, 1},
{0x0F40, 0x0F47, 1},
{0x0F49, 0x0F69, 1},
{0x10A0, 0x10C5, 1},
{0x10D0, 0x10F6, 1},
{0x1100, 0x1100, 1},
{0x1102, 0x1103, 1},
{0x1105, 0x1107, 1},
{0x1109, 0x1109, 1},
{0x110B, 0x110C, 1},
{0x110E, 0x1112, 1},
{0x113C, 0x1140, 2},
{0x114C, 0x1150, 2},
{0x1154, 0x1155, 1},
{0x1159, 0x1159, 1},
{0x115F, 0x1161, 1},
{0x1163, 0x1169, 2},
{0x116D, 0x116E, 1},
{0x1172, 0x1173, 1},
{0x1175, 0x119E, 0x119E - 0x1175},
{0x11A8, 0x11AB, 0x11AB - 0x11A8},
{0x11AE, 0x11AF, 1},
{0x11B7, 0x11B8, 1},
{0x11BA, 0x11BA, 1},
{0x11BC, 0x11C2, 1},
{0x11EB, 0x11F0, 0x11F0 - 0x11EB},
{0x11F9, 0x11F9, 1},
{0x1E00, 0x1E9B, 1},
{0x1EA0, 0x1EF9, 1},
{0x1F00, 0x1F15, 1},
{0x1F18, 0x1F1D, 1},
{0x1F20, 0x1F45, 1},
{0x1F48, 0x1F4D, 1},
{0x1F50, 0x1F57, 1},
{0x1F59, 0x1F5B, 0x1F5B - 0x1F59},
{0x1F5D, 0x1F5D, 1},
{0x1F5F, 0x1F7D, 1},
{0x1F80, 0x1FB4, 1},
{0x1FB6, 0x1FBC, 1},
{0x1FBE, 0x1FBE, 1},
{0x1FC2, 0x1FC4, 1},
{0x1FC6, 0x1FCC, 1},
{0x1FD0, 0x1FD3, 1},
{0x1FD6, 0x1FDB, 1},
{0x1FE0, 0x1FEC, 1},
{0x1FF2, 0x1FF4, 1},
{0x1FF6, 0x1FFC, 1},
{0x2126, 0x2126, 1},
{0x212A, 0x212B, 1},
{0x212E, 0x212E, 1},
{0x2180, 0x2182, 1},
{0x3007, 0x3007, 1},
{0x3021, 0x3029, 1},
{0x3041, 0x3094, 1},
{0x30A1, 0x30FA, 1},
{0x3105, 0x312C, 1},
{0x4E00, 0x9FA5, 1},
{0xAC00, 0xD7A3, 1},
},
}
var second = &unicode.RangeTable{
R16: []unicode.Range16{
{0x002D, 0x002E, 1},
{0x0030, 0x0039, 1},
{0x00B7, 0x00B7, 1},
{0x02D0, 0x02D1, 1},
{0x0300, 0x0345, 1},
{0x0360, 0x0361, 1},
{0x0387, 0x0387, 1},
{0x0483, 0x0486, 1},
{0x0591, 0x05A1, 1},
{0x05A3, 0x05B9, 1},
{0x05BB, 0x05BD, 1},
{0x05BF, 0x05BF, 1},
{0x05C1, 0x05C2, 1},
{0x05C4, 0x0640, 0x0640 - 0x05C4},
{0x064B, 0x0652, 1},
{0x0660, 0x0669, 1},
{0x0670, 0x0670, 1},
{0x06D6, 0x06DC, 1},
{0x06DD, 0x06DF, 1},
{0x06E0, 0x06E4, 1},
{0x06E7, 0x06E8, 1},
{0x06EA, 0x06ED, 1},
{0x06F0, 0x06F9, 1},
{0x0901, 0x0903, 1},
{0x093C, 0x093C, 1},
{0x093E, 0x094C, 1},
{0x094D, 0x094D, 1},
{0x0951, 0x0954, 1},
{0x0962, 0x0963, 1},
{0x0966, 0x096F, 1},
{0x0981, 0x0983, 1},
{0x09BC, 0x09BC, 1},
{0x09BE, 0x09BF, 1},
{0x09C0, 0x09C4, 1},
{0x09C7, 0x09C8, 1},
{0x09CB, 0x09CD, 1},
{0x09D7, 0x09D7, 1},
{0x09E2, 0x09E3, 1},
{0x09E6, 0x09EF, 1},
{0x0A02, 0x0A3C, 0x3A},
{0x0A3E, 0x0A3F, 1},
{0x0A40, 0x0A42, 1},
{0x0A47, 0x0A48, 1},
{0x0A4B, 0x0A4D, 1},
{0x0A66, 0x0A6F, 1},
{0x0A70, 0x0A71, 1},
{0x0A81, 0x0A83, 1},
{0x0ABC, 0x0ABC, 1},
{0x0ABE, 0x0AC5, 1},
{0x0AC7, 0x0AC9, 1},
{0x0ACB, 0x0ACD, 1},
{0x0AE6, 0x0AEF, 1},
{0x0B01, 0x0B03, 1},
{0x0B3C, 0x0B3C, 1},
{0x0B3E, 0x0B43, 1},
{0x0B47, 0x0B48, 1},
{0x0B4B, 0x0B4D, 1},
{0x0B56, 0x0B57, 1},
{0x0B66, 0x0B6F, 1},
{0x0B82, 0x0B83, 1},
{0x0BBE, 0x0BC2, 1},
{0x0BC6, 0x0BC8, 1},
{0x0BCA, 0x0BCD, 1},
{0x0BD7, 0x0BD7, 1},
{0x0BE7, 0x0BEF, 1},
{0x0C01, 0x0C03, 1},
{0x0C3E, 0x0C44, 1},
{0x0C46, 0x0C48, 1},
{0x0C4A, 0x0C4D, 1},
{0x0C55, 0x0C56, 1},
{0x0C66, 0x0C6F, 1},
{0x0C82, 0x0C83, 1},
{0x0CBE, 0x0CC4, 1},
{0x0CC6, 0x0CC8, 1},
{0x0CCA, 0x0CCD, 1},
{0x0CD5, 0x0CD6, 1},
{0x0CE6, 0x0CEF, 1},
{0x0D02, 0x0D03, 1},
{0x0D3E, 0x0D43, 1},
{0x0D46, 0x0D48, 1},
{0x0D4A, 0x0D4D, 1},
{0x0D57, 0x0D57, 1},
{0x0D66, 0x0D6F, 1},
{0x0E31, 0x0E31, 1},
{0x0E34, 0x0E3A, 1},
{0x0E46, 0x0E46, 1},
{0x0E47, 0x0E4E, 1},
{0x0E50, 0x0E59, 1},
{0x0EB1, 0x0EB1, 1},
{0x0EB4, 0x0EB9, 1},
{0x0EBB, 0x0EBC, 1},
{0x0EC6, 0x0EC6, 1},
{0x0EC8, 0x0ECD, 1},
{0x0ED0, 0x0ED9, 1},
{0x0F18, 0x0F19, 1},
{0x0F20, 0x0F29, 1},
{0x0F35, 0x0F39, 2},
{0x0F3E, 0x0F3F, 1},
{0x0F71, 0x0F84, 1},
{0x0F86, 0x0F8B, 1},
{0x0F90, 0x0F95, 1},
{0x0F97, 0x0F97, 1},
{0x0F99, 0x0FAD, 1},
{0x0FB1, 0x0FB7, 1},
{0x0FB9, 0x0FB9, 1},
{0x20D0, 0x20DC, 1},
{0x20E1, 0x3005, 0x3005 - 0x20E1},
{0x302A, 0x302F, 1},
{0x3031, 0x3035, 1},
{0x3099, 0x309A, 1},
{0x309D, 0x309E, 1},
{0x30FC, 0x30FE, 1},
},
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/marshal.go 0000664 0000000 0000000 00000003662 13161151513 0026305 0 ustar 00root root 0000000 0000000 package goxpath
import (
"bytes"
"encoding/xml"
"io"
"github.com/ChrisTrenkamp/goxpath/tree"
)
//Marshal prints the result tree, r, in XML form to w.
func Marshal(n tree.Node, w io.Writer) error {
return marshal(n, w)
}
//MarshalStr is like Marhal, but returns a string.
func MarshalStr(n tree.Node) (string, error) {
ret := bytes.NewBufferString("")
err := marshal(n, ret)
return ret.String(), err
}
func marshal(n tree.Node, w io.Writer) error {
e := xml.NewEncoder(w)
err := encTok(n, e)
if err != nil {
return err
}
return e.Flush()
}
func encTok(n tree.Node, e *xml.Encoder) error {
switch n.GetNodeType() {
case tree.NtAttr:
return encAttr(n.GetToken().(xml.Attr), e)
case tree.NtElem:
return encEle(n.(tree.Elem), e)
case tree.NtNs:
return encNS(n.GetToken().(xml.Attr), e)
case tree.NtRoot:
for _, i := range n.(tree.Elem).GetChildren() {
err := encTok(i, e)
if err != nil {
return err
}
}
return nil
}
//case tree.NtChd, tree.NtComm, tree.NtPi:
return e.EncodeToken(n.GetToken())
}
func encAttr(a xml.Attr, e *xml.Encoder) error {
str := a.Name.Local + `="` + a.Value + `"`
if a.Name.Space != "" {
str += ` xmlns="` + a.Name.Space + `"`
}
pi := xml.ProcInst{
Target: "attribute",
Inst: ([]byte)(str),
}
return e.EncodeToken(pi)
}
func encNS(ns xml.Attr, e *xml.Encoder) error {
pi := xml.ProcInst{
Target: "namespace",
Inst: ([]byte)(ns.Value),
}
return e.EncodeToken(pi)
}
func encEle(n tree.Elem, e *xml.Encoder) error {
ele := xml.StartElement{
Name: n.GetToken().(xml.StartElement).Name,
}
attrs := n.GetAttrs()
ele.Attr = make([]xml.Attr, len(attrs))
for i := range attrs {
ele.Attr[i] = attrs[i].GetToken().(xml.Attr)
}
err := e.EncodeToken(ele)
if err != nil {
return err
}
if x, ok := n.(tree.Elem); ok {
for _, i := range x.GetChildren() {
err := encTok(i, e)
if err != nil {
return err
}
}
}
return e.EncodeToken(ele.End())
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/misc_test.go 0000664 0000000 0000000 00000010056 13161151513 0026643 0 ustar 00root root 0000000 0000000 package goxpath
import (
"bytes"
"encoding/xml"
"testing"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
)
func TestISO_8859_1(t *testing.T) {
p := `/test`
x := `testpathtest2`
exp := "testpathtest2"
execVal(p, x, exp, nil, t)
}
func TestNodePos(t *testing.T) {
ns := map[string]string{"test": "http://test", "test2": "http://test2", "test3": "http://test3"}
x := `text`
testPos := func(path string, pos int) {
res := MustParse(path).MustExec(xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns }).(tree.NodeSet)
if len(res) != 1 {
t.Errorf("Result length not 1: %s", path)
return
}
exPos := res[0].(tree.Node).Pos()
if exPos != pos {
t.Errorf("Node position not correct. Recieved %d, expected %d", exPos, pos)
}
}
testPos("/", 0)
testPos("/*", 1)
testPos("/*/namespace::*[1]", 2)
testPos("/*/namespace::*[2]", 3)
testPos("/*/attribute::*[1]", 4)
testPos("//*:p2", 5)
testPos("//*:p2/namespace::*[1]", 6)
testPos("//*:p2/namespace::*[2]", 7)
testPos("//*:p2/namespace::*[3]", 8)
testPos("//*:p2/attribute::*[1]", 9)
testPos("//text()", 10)
}
func TestNSSort(t *testing.T) {
testNS := func(n tree.Node, url string) {
if n.(tree.NS).Value != url {
t.Errorf("Unexpected namespace %s. Expecting %s", n.(tree.NS).Value, url)
}
}
ns := map[string]string{"test": "http://test", "test2": "http://test2", "test3": "http://test3"}
x := ``
res := MustParse("/*:p1/namespace::*").MustExec(xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns }).(tree.NodeSet)
testNS(res[0], ns["test"])
testNS(res[1], ns["test2"])
testNS(res[2], ns["test3"])
testNS(res[3], "http://www.w3.org/XML/1998/namespace")
}
func TestFindNodeByPos(t *testing.T) {
x := `text`
nt := xmltree.MustParseXML(bytes.NewBufferString(x))
if tree.FindNodeByPos(nt, 5).GetNodeType() != tree.NtElem {
t.Error("Node 5 not element")
}
if tree.FindNodeByPos(nt, 14).GetNodeType() != tree.NtChd {
t.Error("Node 14 not char data")
}
if tree.FindNodeByPos(nt, 4).GetNodeType() != tree.NtAttr {
t.Error("Node 4 not attribute")
}
if tree.FindNodeByPos(nt, 3).GetNodeType() != tree.NtNs {
t.Error("Node 3 not namespace")
}
if tree.FindNodeByPos(nt, 19) != nil {
t.Error("Invalid node returned")
}
}
func TestFindAttr(t *testing.T) {
x := ``
nt := xmltree.MustParseXML(bytes.NewBufferString(x))
res, _ := ParseExec("/p1", nt)
node := res.(tree.NodeSet)[0].(tree.Elem)
if val, ok := tree.GetAttributeVal(node, "attr1", ""); !ok || val != "foo" {
t.Error("attr1 not foo")
}
if val, ok := tree.GetAttributeVal(node, "attr2", "http://test"); !ok || val != "bar" {
t.Error("attr2 not bar")
}
if val, ok := tree.GetAttributeVal(node, "attr3", ""); ok || val != "" {
t.Error("attr3 is set")
}
if val := tree.GetAttrValOrEmpty(node, "attr3", ""); val != "" {
t.Error("attr3 is set")
}
if val := tree.GetAttrValOrEmpty(node, "attr1", ""); val != "foo" {
t.Error("attr1 not foo")
}
}
func TestVariable(t *testing.T) {
x := xmltree.MustParseXML(bytes.NewBufferString(xml.Header + "foobar"))
xp := MustParse(`/p1/p2`)
res := xp.MustExec(x)
opt := func(o *Opts) {
o.Vars["prev"] = res
}
xp = MustParse(`$prev = 'foo'`)
if res, err := xp.ExecBool(x, opt); err != nil || !res {
t.Error("Incorrect result", res, err)
}
if _, err := xp.ExecBool(x); err == nil {
t.Error("Error not nil")
}
if _, err := Parse(`$ = 'foo'`); err == nil {
t.Error("Parse error not nil")
}
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/parser/ 0000775 0000000 0000000 00000000000 13161151513 0025614 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/parser/ast.go 0000664 0000000 0000000 00000004244 13161151513 0026736 0 ustar 00root root 0000000 0000000 package parser
import "github.com/ChrisTrenkamp/goxpath/lexer"
//NodeType enumerations
const (
Empty lexer.XItemType = ""
)
//Node builds an AST tree for operating on XPath expressions
type Node struct {
Val lexer.XItem
Left *Node
Right *Node
Parent *Node
next *Node
}
var beginPathType = map[lexer.XItemType]bool{
lexer.XItemAbsLocPath: true,
lexer.XItemAbbrAbsLocPath: true,
lexer.XItemAbbrRelLocPath: true,
lexer.XItemRelLocPath: true,
lexer.XItemFunction: true,
}
func (n *Node) add(i lexer.XItem) {
if n.Val.Typ == Empty {
n.Val = i
} else if n.Left == nil {
n.Left = &Node{Val: n.Val, Parent: n}
n.Val = i
} else if beginPathType[n.Val.Typ] {
next := &Node{Val: n.Val, Left: n.Left, Parent: n}
n.Left = next
n.Val = i
} else if n.Right == nil {
n.Right = &Node{Val: i, Parent: n}
} else {
next := &Node{Val: n.Val, Left: n.Left, Right: n.Right, Parent: n}
n.Left, n.Right = next, nil
n.Val = i
}
n.next = n
}
func (n *Node) push(i lexer.XItem) {
if n.Left == nil {
n.Left = &Node{Val: i, Parent: n}
n.next = n.Left
} else if n.Right == nil {
n.Right = &Node{Val: i, Parent: n}
n.next = n.Right
} else {
next := &Node{Val: i, Left: n.Right, Parent: n}
n.Right = next
n.next = n.Right
}
}
func (n *Node) pushNotEmpty(i lexer.XItem) {
if n.Val.Typ == Empty {
n.add(i)
} else {
n.push(i)
}
}
/*
func (n *Node) prettyPrint(depth, width int) {
nodes := []*Node{}
n.getLine(depth, &nodes)
fmt.Printf("%*s", (width-depth)*2, "")
toggle := true
if len(nodes) > 1 {
for _, i := range nodes {
if i != nil {
if toggle {
fmt.Print("/ ")
} else {
fmt.Print("\\ ")
}
}
toggle = !toggle
}
fmt.Println()
fmt.Printf("%*s", (width-depth)*2, "")
}
for _, i := range nodes {
if i != nil {
fmt.Print(i.Val.Val, " ")
}
}
fmt.Println()
}
func (n *Node) getLine(depth int, ret *[]*Node) {
if depth <= 0 && n != nil {
*ret = append(*ret, n)
return
}
if n.Left != nil {
n.Left.getLine(depth-1, ret)
} else if depth-1 <= 0 {
*ret = append(*ret, nil)
}
if n.Right != nil {
n.Right.getLine(depth-1, ret)
} else if depth-1 <= 0 {
*ret = append(*ret, nil)
}
}
*/
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/parser/parser.go 0000664 0000000 0000000 00000007017 13161151513 0027444 0 ustar 00root root 0000000 0000000 package parser
import (
"fmt"
"github.com/ChrisTrenkamp/goxpath/lexer"
)
type stateType int
const (
defState stateType = iota
xpathState
funcState
paramState
predState
parenState
)
type parseStack struct {
stack []*Node
stateTypes []stateType
cur *Node
}
func (p *parseStack) push(t stateType) {
p.stack = append(p.stack, p.cur)
p.stateTypes = append(p.stateTypes, t)
}
func (p *parseStack) pop() {
stackPos := len(p.stack) - 1
p.cur = p.stack[stackPos]
p.stack = p.stack[:stackPos]
p.stateTypes = p.stateTypes[:stackPos]
}
func (p *parseStack) curState() stateType {
if len(p.stateTypes) == 0 {
return defState
}
return p.stateTypes[len(p.stateTypes)-1]
}
type lexFn func(*parseStack, lexer.XItem)
var parseMap = map[lexer.XItemType]lexFn{
lexer.XItemAbsLocPath: xiXPath,
lexer.XItemAbbrAbsLocPath: xiXPath,
lexer.XItemAbbrRelLocPath: xiXPath,
lexer.XItemRelLocPath: xiXPath,
lexer.XItemEndPath: xiEndPath,
lexer.XItemAxis: xiXPath,
lexer.XItemAbbrAxis: xiXPath,
lexer.XItemNCName: xiXPath,
lexer.XItemQName: xiXPath,
lexer.XItemNodeType: xiXPath,
lexer.XItemProcLit: xiXPath,
lexer.XItemFunction: xiFunc,
lexer.XItemArgument: xiFuncArg,
lexer.XItemEndFunction: xiEndFunc,
lexer.XItemPredicate: xiPred,
lexer.XItemEndPredicate: xiEndPred,
lexer.XItemStrLit: xiValue,
lexer.XItemNumLit: xiValue,
lexer.XItemOperator: xiOp,
lexer.XItemVariable: xiValue,
}
var opPrecedence = map[string]int{
"|": 1,
"*": 2,
"div": 2,
"mod": 2,
"+": 3,
"-": 3,
"=": 4,
"!=": 4,
"<": 4,
"<=": 4,
">": 4,
">=": 4,
"and": 5,
"or": 6,
}
//Parse creates an AST tree for XPath expressions.
func Parse(xp string) (*Node, error) {
var err error
c := lexer.Lex(xp)
n := &Node{}
p := &parseStack{cur: n}
for next := range c {
if next.Typ != lexer.XItemError {
parseMap[next.Typ](p, next)
} else if err == nil {
err = fmt.Errorf(next.Val)
}
}
return n, err
}
func xiXPath(p *parseStack, i lexer.XItem) {
if p.curState() == xpathState {
p.cur.push(i)
p.cur = p.cur.next
return
}
if p.cur.Val.Typ == lexer.XItemFunction {
p.cur.Right = &Node{Val: i, Parent: p.cur}
p.cur.next = p.cur.Right
p.push(xpathState)
p.cur = p.cur.next
return
}
p.cur.pushNotEmpty(i)
p.push(xpathState)
p.cur = p.cur.next
}
func xiEndPath(p *parseStack, i lexer.XItem) {
p.pop()
}
func xiFunc(p *parseStack, i lexer.XItem) {
p.cur.push(i)
p.cur = p.cur.next
p.push(funcState)
}
func xiFuncArg(p *parseStack, i lexer.XItem) {
if p.curState() != funcState {
p.pop()
}
p.cur.push(i)
p.cur = p.cur.next
p.push(paramState)
p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
p.cur = p.cur.next
}
func xiEndFunc(p *parseStack, i lexer.XItem) {
if p.curState() == paramState {
p.pop()
}
p.pop()
}
func xiPred(p *parseStack, i lexer.XItem) {
p.cur.push(i)
p.cur = p.cur.next
p.push(predState)
p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
p.cur = p.cur.next
}
func xiEndPred(p *parseStack, i lexer.XItem) {
p.pop()
}
func xiValue(p *parseStack, i lexer.XItem) {
p.cur.add(i)
}
func xiOp(p *parseStack, i lexer.XItem) {
if i.Val == "(" {
p.cur.push(lexer.XItem{Typ: Empty, Val: ""})
p.push(parenState)
p.cur = p.cur.next
return
}
if i.Val == ")" {
p.pop()
return
}
if p.cur.Val.Typ == lexer.XItemOperator {
if opPrecedence[p.cur.Val.Val] <= opPrecedence[i.Val] {
p.cur.add(i)
} else {
p.cur.push(i)
}
} else {
p.cur.add(i)
}
p.cur = p.cur.next
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/parser/pathexpr/ 0000775 0000000 0000000 00000000000 13161151513 0027447 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/parser/pathexpr/pathexpr.go 0000664 0000000 0000000 00000000337 13161151513 0031634 0 ustar 00root root 0000000 0000000 package pathexpr
import "encoding/xml"
//PathExpr represents XPath step's. xmltree.XMLTree uses it to find nodes.
type PathExpr struct {
Name xml.Name
Axis string
NodeType string
NS map[string]string
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/path_test.go 0000664 0000000 0000000 00000043233 13161151513 0026647 0 ustar 00root root 0000000 0000000 package goxpath
import (
"bytes"
"encoding/xml"
"runtime/debug"
"testing"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
)
func execPath(xp, x string, exp []string, ns map[string]string, t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("Panicked: from XPath expr: '" + xp)
t.Error(r)
t.Error(string(debug.Stack()))
}
}()
res := MustParse(xp).MustExec(xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns }).(tree.NodeSet)
if len(res) != len(exp) {
t.Error("Result length not valid in XPath expression '"+xp+"':", len(res), ", expecting", len(exp))
for i := range res {
t.Error(MarshalStr(res[i].(tree.Node)))
}
return
}
for i := range res {
r, err := MarshalStr(res[i].(tree.Node))
if err != nil {
t.Error(err.Error())
return
}
valid := false
for j := range exp {
if r == exp[j] {
valid = true
}
}
if !valid {
t.Error("Incorrect result in XPath expression '" + xp + "':" + r)
t.Error("Expecting one of:")
for j := range exp {
t.Error(exp[j])
}
return
}
}
}
func TestRoot(t *testing.T) {
p := `/`
x := ``
exp := []string{""}
execPath(p, x, exp, nil, t)
}
func TestRoot2(t *testing.T) {
x := xmltree.MustParseXML(bytes.NewBufferString(``))
x = MustParse("/test/path").MustExec(x).(tree.NodeSet)[0]
for _, i := range []string{"/", "//test"} {
res, err := MarshalStr(MustParse(i).MustExec(x).(tree.NodeSet)[0])
if err != nil {
t.Error(err)
}
if res != "" {
t.Error("Incorrect result", res)
}
}
}
func TestAbsPath(t *testing.T) {
p := ` / test / path`
x := ``
exp := []string{""}
execPath(p, x, exp, nil, t)
}
func TestNonAbsPath(t *testing.T) {
p := ` test `
x := ``
exp := []string{""}
execPath(p, x, exp, nil, t)
}
func TestRelPath(t *testing.T) {
p := ` // path `
x := ``
exp := []string{""}
execPath(p, x, exp, nil, t)
}
func TestRelPath2(t *testing.T) {
p := ` // path `
x := `test`
exp := []string{"test", `test`}
execPath(p, x, exp, nil, t)
}
func TestRelNonRootPath(t *testing.T) {
p := ` / test // path `
x := ``
exp := []string{""}
execPath(p, x, exp, nil, t)
}
func TestParent(t *testing.T) {
p := `/test/path/ parent:: test`
x := ``
exp := []string{""}
execPath(p, x, exp, nil, t)
}
func TestAncestor(t *testing.T) {
p := `/p1/p2/p3/p1/ancestor::p1`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestAncestorOrSelf(t *testing.T) {
p := `/p1/p2/p3/p1/ancestor-or-self::p1`
x := ``
exp := []string{``, ``}
execPath(p, x, exp, nil, t)
}
func TestDescendant(t *testing.T) {
p := `/p1/descendant::p1`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestDescendantOrSelf(t *testing.T) {
p := `/p1/descendant-or-self::p1`
x := ``
exp := []string{``, ``}
execPath(p, x, exp, nil, t)
}
func TestAttribute(t *testing.T) {
p := `/p1/attribute::test`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestAttributeSelf(t *testing.T) {
p := `/p1/attribute::test/self::node()`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestAttributeAbbr(t *testing.T) {
p := `/p1/ @ test`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestAttributeNoChild(t *testing.T) {
p := `/p1/attribute::test/p2`
x := ``
exp := []string{}
execPath(p, x, exp, nil, t)
}
func TestAttributeParent(t *testing.T) {
p := `/p1/attribute::test/ ..`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNodeTypeNode(t *testing.T) {
p := `/p1/child:: node( ) `
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNodeTypeNodeAbbr(t *testing.T) {
p := `/p1/ .`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNodeTypeParent(t *testing.T) {
p := `/p1/p2/parent::node()`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNodeTypeParentAbbr(t *testing.T) {
p := `/p1/p2/..`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestFollowing(t *testing.T) {
p := `//p3/following::node()`
x := ``
exp := []string{``, ``, ``}
execPath(p, x, exp, nil, t)
}
func TestPreceding(t *testing.T) {
p := `//p6/preceding::node()`
x := ``
exp := []string{``, ``, ``}
execPath(p, x, exp, nil, t)
}
func TestPrecedingSibling(t *testing.T) {
p := `//p4/preceding-sibling::node()`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestPrecedingSibling2(t *testing.T) {
p := `/preceding-sibling::node()`
x := ``
exp := []string{}
execPath(p, x, exp, nil, t)
}
func TestFollowingSibling(t *testing.T) {
p := `//p2/following-sibling::node()`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestFollowingSibling2(t *testing.T) {
p := `/following-sibling::node()`
x := ``
exp := []string{}
execPath(p, x, exp, nil, t)
}
func TestComment(t *testing.T) {
p := `// comment()`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestCommentInvPath(t *testing.T) {
p := `//comment()/ self :: processing-instruction ( ) `
x := ``
exp := []string{}
execPath(p, x, exp, nil, t)
}
func TestCommentParent(t *testing.T) {
p := `//comment()/..`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestText(t *testing.T) {
p := `//text()`
x := `text`
exp := []string{`text`}
execPath(p, x, exp, nil, t)
}
func TestTextParent(t *testing.T) {
p := `//text()/..`
x := `text`
exp := []string{`text`}
execPath(p, x, exp, nil, t)
}
func TestProcInst(t *testing.T) {
p := `//processing-instruction()`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestProcInstParent(t *testing.T) {
p := `//processing-instruction()/..`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestProcInstInvPath(t *testing.T) {
p := `//processing-instruction()/self::comment()`
x := ``
exp := []string{}
execPath(p, x, exp, nil, t)
}
func TestProcInst2(t *testing.T) {
p := `//processing-instruction( 'proc2' ) `
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNamespace(t *testing.T) {
p := `/*:p1/*:p2/namespace::*`
x := ``
exp := []string{``, ``, ``}
execPath(p, x, exp, nil, t)
}
func TestRootNamespace(t *testing.T) {
p := `/namespace::*`
x := ``
exp := []string{}
execPath(p, x, exp, nil, t)
}
func TestNamespaceXml(t *testing.T) {
p := `/*:p1/ * : p2 /namespace::xml`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNamespaceNodeType(t *testing.T) {
p := `/*:p1/*:p2/namespace::foo/ self :: node ( ) `
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNamespaceNodeWildcard(t *testing.T) {
p := `/*:p1/*:p2/namespace::*:foo`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
p = `/*:p1/*:p2/namespace::invalid:foo`
exp = []string{}
execPath(p, x, exp, nil, t)
}
func TestNamespaceChild(t *testing.T) {
p := `/*:p1/namespace::*/ * : p2`
x := ``
exp := []string{}
execPath(p, x, exp, nil, t)
}
func TestNamespaceParent(t *testing.T) {
p := `/*:p1/*:p2/namespace::foo/..`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNamespace2(t *testing.T) {
p := `//namespace::test`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNamespace3(t *testing.T) {
p := `/p1/p2/p3/namespace :: foo`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestNamespace4(t *testing.T) {
p := `/test:p1/p2/p3/namespace::*`
x := ``
exp := []string{``}
execPath(p, x, exp, map[string]string{"test": "http://test"}, t)
}
func TestNodeNamespace(t *testing.T) {
p := `/ test : p1 `
x := ``
exp := []string{``}
execPath(p, x, exp, map[string]string{"test": "http://test"}, t)
}
func TestNodeNamespace2(t *testing.T) {
p := `/test:p1/test2:p2`
x := ``
exp := []string{``}
execPath(p, x, exp, map[string]string{"test": "http://test", "test2": "http://test2"}, t)
}
func TestNodeNamespace3(t *testing.T) {
p := `/test:p1/foo:p2`
x := ``
exp := []string{``}
execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
}
func TestAttrNamespace(t *testing.T) {
p := `/test:p1/@foo:test`
x := ``
exp := []string{``}
execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
p = `/test:p1/@test:test`
exp = []string{}
execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
}
func TestWildcard(t *testing.T) {
p := `/p1/*`
x := ``
exp := []string{``, ``, ``}
execPath(p, x, exp, nil, t)
}
func TestWildcardNS(t *testing.T) {
p := `//*:p1`
x := ``
exp := []string{``, ``}
execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
}
func TestWildcardLocal(t *testing.T) {
p := `//foo:*`
x := ``
exp := []string{``, ``}
execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
}
func TestWildcardNSAttr(t *testing.T) {
p := `/p1/@*:attr`
x := ``
exp := []string{``}
execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
}
func TestWildcardLocalAttr(t *testing.T) {
p := `/p1/@foo:*`
x := ``
exp := []string{``, ``}
execPath(p, x, exp, map[string]string{"test": "http://test", "foo": "http://foo.bar"}, t)
}
func TestPredicate1(t *testing.T) {
p := `/p1/p2 [ p3 ] `
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestPredicate2(t *testing.T) {
p := `/p1/p2 [ 2 ] `
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
p = `/p1/p2 [ position() = 2 ] `
execPath(p, x, exp, nil, t)
}
func TestPredicate3(t *testing.T) {
p := `/p1/p2 [ last() ] /p3`
x := ``
exp := []string{``}
execPath(p, x, exp, nil, t)
}
func TestPredicate4(t *testing.T) {
p := `/p1/p2[not(@test)]`
x := ``
exp := []string{``, ``}
execPath(p, x, exp, nil, t)
}
func TestPredicate5(t *testing.T) {
p := `/p1/p2[.//p1]`
x := `lkj`
exp := []string{`lkj`}
execPath(p, x, exp, nil, t)
}
func TestPredicate6(t *testing.T) {
p := `/p1/p2[//p1]`
x := `lkj`
exp := []string{``, ``, `lkj`}
execPath(p, x, exp, nil, t)
}
func TestPredicate7(t *testing.T) {
p := `/p3[//p1]`
x := `lkj`
exp := []string{}
execPath(p, x, exp, nil, t)
}
func TestPredicate8(t *testing.T) {
p := `/test[@attr1='a' and @attr2='b']/a`
x := `aaaabbb`
exp := []string{`aaaa`}
execPath(p, x, exp, nil, t)
}
func TestPredicate9(t *testing.T) {
p := `/root/text[@attr="2"][2]`
x := xml.Header + `
This is some text 1.1.
This is some text 1.2.
This is some text. 2.1
This is some text. 2.2
This is some text. 2.3
`
exp := []string{`This is some text. 2.2`}
execPath(p, x, exp, nil, t)
}
func TestPredicate10(t *testing.T) {
p := `/root/text[@attr="2"][last()]`
x := xml.Header + `
This is some text 1.1.
This is some text 1.2.
This is some text. 2.1
This is some text. 2.2
This is some text. 2.3
`
exp := []string{`This is some text. 2.3`}
execPath(p, x, exp, nil, t)
}
func TestUnion(t *testing.T) {
p := `/test/test2 | /test/test3`
x := `foobarhamneggs`
exp := []string{"foobar", "hamneggs"}
execPath(p, x, exp, nil, t)
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/ 0000775 0000000 0000000 00000000000 13161151513 0025257 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/interfaces.go 0000664 0000000 0000000 00000000536 13161151513 0027735 0 ustar 00root root 0000000 0000000 package tree
import "fmt"
//Result is used for all data types.
type Result interface {
fmt.Stringer
}
//IsBool is used for the XPath boolean function. It turns the data type to a bool.
type IsBool interface {
Bool() Bool
}
//IsNum is used for the XPath number function. It turns the data type to a number.
type IsNum interface {
Num() Num
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/tree.go 0000664 0000000 0000000 00000011335 13161151513 0026550 0 ustar 00root root 0000000 0000000 package tree
import (
"encoding/xml"
"sort"
)
//XMLSpace is the W3C XML namespace
const XMLSpace = "http://www.w3.org/XML/1998/namespace"
//NodePos is a helper for representing the node's document order
type NodePos int
//Pos returns the node's document order position
func (n NodePos) Pos() int {
return int(n)
}
//NodeType is a safer way to determine a node's type than type assertions.
type NodeType int
//GetNodeType returns the node's type.
func (t NodeType) GetNodeType() NodeType {
return t
}
//These are all the possible node types
const (
NtAttr NodeType = iota
NtChd
NtComm
NtElem
NtNs
NtRoot
NtPi
)
//Node is a XPath result that is a node except elements
type Node interface {
//ResValue prints the node's string value
ResValue() string
//Pos returns the node's position in the document order
Pos() int
//GetToken returns the xml.Token representation of the node
GetToken() xml.Token
//GetParent returns the parent node, which will always be an XML element
GetParent() Elem
//GetNodeType returns the node's type
GetNodeType() NodeType
}
//Elem is a XPath result that is an element node
type Elem interface {
Node
//GetChildren returns the elements children.
GetChildren() []Node
//GetAttrs returns the attributes of the element
GetAttrs() []Node
}
//NSElem is a node that keeps track of namespaces.
type NSElem interface {
Elem
GetNS() map[xml.Name]string
}
//NSBuilder is a helper-struct for satisfying the NSElem interface
type NSBuilder struct {
NS map[xml.Name]string
}
//GetNS returns the namespaces found on the current element. It should not be
//confused with BuildNS, which actually resolves the namespace nodes.
func (ns NSBuilder) GetNS() map[xml.Name]string {
return ns.NS
}
type nsValueSort []NS
func (ns nsValueSort) Len() int { return len(ns) }
func (ns nsValueSort) Swap(i, j int) {
ns[i], ns[j] = ns[j], ns[i]
}
func (ns nsValueSort) Less(i, j int) bool {
return ns[i].Value < ns[j].Value
}
//BuildNS resolves all the namespace nodes of the element and returns them
func BuildNS(t Elem) (ret []NS) {
vals := make(map[xml.Name]string)
if nselem, ok := t.(NSElem); ok {
buildNS(nselem, vals)
ret = make([]NS, 0, len(vals))
i := 1
for k, v := range vals {
if !(k.Local == "xmlns" && k.Space == "" && v == "") {
ret = append(ret, NS{
Attr: xml.Attr{Name: k, Value: v},
Parent: t,
NodeType: NtNs,
})
i++
}
}
sort.Sort(nsValueSort(ret))
for i := range ret {
ret[i].NodePos = NodePos(t.Pos() + i + 1)
}
}
return ret
}
func buildNS(x NSElem, ret map[xml.Name]string) {
if x.GetNodeType() == NtRoot {
return
}
if nselem, ok := x.GetParent().(NSElem); ok {
buildNS(nselem, ret)
}
for k, v := range x.GetNS() {
ret[k] = v
}
}
//NS is a namespace node.
type NS struct {
xml.Attr
Parent Elem
NodePos
NodeType
}
//GetToken returns the xml.Token representation of the namespace.
func (ns NS) GetToken() xml.Token {
return ns.Attr
}
//GetParent returns the parent node of the namespace.
func (ns NS) GetParent() Elem {
return ns.Parent
}
//ResValue returns the string value of the namespace
func (ns NS) ResValue() string {
return ns.Attr.Value
}
//GetAttribute is a convenience function for getting the specified attribute from an element.
//false is returned if the attribute is not found.
func GetAttribute(n Elem, local, space string) (xml.Attr, bool) {
attrs := n.GetAttrs()
for _, i := range attrs {
attr := i.GetToken().(xml.Attr)
if local == attr.Name.Local && space == attr.Name.Space {
return attr, true
}
}
return xml.Attr{}, false
}
//GetAttributeVal is like GetAttribute, except it returns the attribute's value.
func GetAttributeVal(n Elem, local, space string) (string, bool) {
attr, ok := GetAttribute(n, local, space)
return attr.Value, ok
}
//GetAttrValOrEmpty is like GetAttributeVal, except it returns an empty string if
//the attribute is not found instead of false.
func GetAttrValOrEmpty(n Elem, local, space string) string {
val, ok := GetAttributeVal(n, local, space)
if !ok {
return ""
}
return val
}
//FindNodeByPos finds a node from the given position. Returns nil if the node
//is not found.
func FindNodeByPos(n Node, pos int) Node {
if n.Pos() == pos {
return n
}
if elem, ok := n.(Elem); ok {
chldrn := elem.GetChildren()
for i := 1; i < len(chldrn); i++ {
if chldrn[i-1].Pos() <= pos && chldrn[i].Pos() > pos {
return FindNodeByPos(chldrn[i-1], pos)
}
}
if len(chldrn) > 0 {
if chldrn[len(chldrn)-1].Pos() <= pos {
return FindNodeByPos(chldrn[len(chldrn)-1], pos)
}
}
attrs := elem.GetAttrs()
for _, i := range attrs {
if i.Pos() == pos {
return i
}
}
ns := BuildNS(elem)
for _, i := range ns {
if i.Pos() == pos {
return i
}
}
}
return nil
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xfn.go 0000664 0000000 0000000 00000002151 13161151513 0026400 0 ustar 00root root 0000000 0000000 package tree
import (
"fmt"
)
//Ctx represents the current context position, size, node, and the current filtered result
type Ctx struct {
NodeSet
Pos int
Size int
}
//Fn is a XPath function, written in Go
type Fn func(c Ctx, args ...Result) (Result, error)
//LastArgOpt sets whether the last argument in a function is optional, variadic, or neither
type LastArgOpt int
//LastArgOpt options
const (
None LastArgOpt = iota
Optional
Variadic
)
//Wrap interfaces XPath function calls with Go
type Wrap struct {
Fn Fn
//NArgs represents the number of arguments to the XPath function. -1 represents a single optional argument
NArgs int
LastArgOpt LastArgOpt
}
//Call checks the arguments and calls Fn if they are valid
func (w Wrap) Call(c Ctx, args ...Result) (Result, error) {
switch w.LastArgOpt {
case Optional:
if len(args) == w.NArgs || len(args) == w.NArgs-1 {
return w.Fn(c, args...)
}
case Variadic:
if len(args) >= w.NArgs-1 {
return w.Fn(c, args...)
}
default:
if len(args) == w.NArgs {
return w.Fn(c, args...)
}
}
return nil, fmt.Errorf("Invalid number of arguments")
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmlstruct/ 0000775 0000000 0000000 00000000000 13161151513 0027324 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmlstruct/xmlele.go 0000664 0000000 0000000 00000007035 13161151513 0031146 0 ustar 00root root 0000000 0000000 package xmlstruct
import (
"encoding/xml"
"fmt"
"reflect"
"strings"
"github.com/ChrisTrenkamp/goxpath/tree"
)
type XMLEle struct {
Val reflect.Value
pos int
prnt tree.Elem
prntTag string
}
func (x *XMLEle) ResValue() string {
if x.Val.Kind() != reflect.Struct {
return fmt.Sprintf("%v", x.Val.Interface())
}
ret := ""
for _, i := range x.GetChildren() {
switch i.GetNodeType() {
case tree.NtChd, tree.NtElem, tree.NtRoot:
ret += i.ResValue()
}
}
return ret
}
func (x *XMLEle) Pos() int {
return x.pos
}
func (x *XMLEle) GetToken() xml.Token {
ret := xml.StartElement{}
if x.prntTag != "" {
ret.Name = getTagInfo(x.prntTag).name
} else {
ret.Name = getXMLName(x.Val)
}
return ret
}
func (x *XMLEle) GetParent() tree.Elem {
return x.prnt
}
func (x *XMLEle) GetNodeType() tree.NodeType {
return tree.NtElem
}
func (x *XMLEle) GetChildren() []tree.Node {
n, _ := getChildren(x, x.Val, x.pos, false)
return n
}
func (x *XMLEle) GetAttrs() []tree.Node {
n, _ := getChildren(x, x.Val, x.pos, true)
return n
}
type tagInfo struct {
name xml.Name
attr bool
cdata bool
comment bool
}
func getTagInfo(tag string) (ret tagInfo) {
spl := strings.Split(tag, ",")
name := strings.Split(spl[0], " ")
if len(spl) >= 2 {
for i := 1; i < len(spl); i++ {
if spl[i] == "chardata" || spl[i] == "cdata" {
ret.cdata = true
} else if spl[i] == "attr" {
ret.attr = true
} else if spl[i] == "comment" {
ret.comment = true
}
}
}
if len(name) == 2 {
ret.name.Space = name[1]
}
ret.name.Local = name[0]
return
}
func getXMLName(val reflect.Value) xml.Name {
n := val.FieldByName("XMLName")
zero := reflect.Value{}
if zero != n {
field, _ := val.Type().FieldByName("XMLName")
tagInfo := getTagInfo(field.Tag.Get("xml"))
if tagInfo.name.Local != "" {
return tagInfo.name
}
if name, ok := n.Interface().(xml.Name); ok {
return name
}
}
return xml.Name{Local: val.Type().Name()}
}
func getChildren(x *XMLEle, val reflect.Value, pos int, getAttrs bool) ([]tree.Node, int) {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() == reflect.Interface {
val = reflect.ValueOf(val.Interface())
}
if val.Kind() != reflect.Struct {
if getAttrs {
return []tree.Node{}, x.pos + 1
}
return []tree.Node{&XMLNode{
Val: x.Val,
pos: x.pos + 1,
prnt: x,
nodeType: tree.NtChd,
prntTag: "",
}}, x.pos + 2
}
ret := make([]tree.Node, 0, val.NumField())
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
name := val.Type().Field(i).Name
if val.Type().Field(i).Anonymous {
nodes, newPos := getChildren(x, field, pos, getAttrs)
ret = append(ret, nodes...)
pos = newPos
continue
}
tag := val.Type().Field(i).Tag.Get("xml")
if tag == "-" || name == "XMLName" {
continue
}
tagInfo := getTagInfo(tag)
pos++
if tagInfo.attr || tagInfo.cdata || tagInfo.comment {
if !getAttrs && tagInfo.attr || getAttrs && !tagInfo.attr {
continue
}
child := &XMLNode{
Val: field,
pos: pos,
prnt: x,
prntTag: tag,
}
if tagInfo.attr {
child.nodeType = tree.NtAttr
} else if tagInfo.cdata {
child.nodeType = tree.NtChd
} else {
child.nodeType = tree.NtComm
}
if tagInfo.name.Local == "" {
child.prntTag = name
}
ret = append(ret, child)
continue
}
if getAttrs {
continue
}
child := &XMLEle{Val: field, pos: pos}
if tag == "" {
tag = name
}
child.prntTag = tag
ret = append(ret, child)
}
return ret, pos + 1
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmlstruct/xmlnode.go 0000664 0000000 0000000 00000001524 13161151513 0031323 0 ustar 00root root 0000000 0000000 package xmlstruct
import (
"encoding/xml"
"fmt"
"reflect"
"github.com/ChrisTrenkamp/goxpath/tree"
)
type XMLNode struct {
Val reflect.Value
pos int
prnt tree.Elem
nodeType tree.NodeType
prntTag string
}
func (x *XMLNode) ResValue() string {
if x.Val.Kind() == reflect.Ptr {
return fmt.Sprintf("%v", x.Val.Elem().Interface())
}
return fmt.Sprintf("%v", x.Val.Interface())
}
func (x *XMLNode) Pos() int {
return x.pos
}
func (x *XMLNode) GetToken() xml.Token {
switch x.nodeType {
case tree.NtAttr:
return xml.Attr{Name: getTagInfo(x.prntTag).name, Value: x.ResValue()}
case tree.NtChd:
return xml.CharData(x.ResValue())
}
//case tree.NtComm:
return xml.Comment(x.ResValue())
}
func (x *XMLNode) GetParent() tree.Elem {
return x.prnt
}
func (x *XMLNode) GetNodeType() tree.NodeType {
return x.nodeType
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmlstruct/xmlroot.go 0000664 0000000 0000000 00000001066 13161151513 0031362 0 ustar 00root root 0000000 0000000 package xmlstruct
import (
"encoding/xml"
"github.com/ChrisTrenkamp/goxpath/tree"
)
type XMLRoot struct {
Ele *XMLEle
}
func (x *XMLRoot) ResValue() string {
return x.Ele.ResValue()
}
func (x *XMLRoot) Pos() int {
return 0
}
func (x *XMLRoot) GetToken() xml.Token {
return xml.StartElement{}
}
func (x *XMLRoot) GetParent() tree.Elem {
return x
}
func (x *XMLRoot) GetNodeType() tree.NodeType {
return tree.NtRoot
}
func (x *XMLRoot) GetChildren() []tree.Node {
return []tree.Node{x.Ele}
}
func (x *XMLRoot) GetAttrs() []tree.Node {
return nil
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmlstruct/xmlstruct.go 0000664 0000000 0000000 00000001076 13161151513 0031724 0 ustar 00root root 0000000 0000000 package xmlstruct
import (
"fmt"
"reflect"
)
func ParseStruct(i interface{}) (*XMLRoot, error) {
ret := &XMLRoot{}
val := reflect.ValueOf(i)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return ret, fmt.Errorf("Interface is not a struct")
}
if getXMLName(val).Local == "" {
return nil, fmt.Errorf("Invalid XML struct")
}
ret.Ele = &XMLEle{Val: val, pos: 1, prnt: ret}
return ret, nil
}
func MustParseStruct(i interface{}) *XMLRoot {
ret, err := ParseStruct(i)
if err != nil {
panic(err)
}
return ret
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmlstruct/xmlstruct_test.go 0000664 0000000 0000000 00000001543 13161151513 0032762 0 ustar 00root root 0000000 0000000 package xmlstruct
import (
"encoding/xml"
"testing"
"github.com/ChrisTrenkamp/goxpath"
)
func TestSingleFields(t *testing.T) {
str := struct {
XMLName xml.Name `xml:"struct"`
Elem string `xml:"elem"`
Attr string `xml:"attr,attr"`
Attr2 string `xml:",attr"`
Comm string `xml:",comment"`
CD string `xml:",chardata"`
Test interface{}
}{
Elem: "foo",
Attr: "bar",
Attr2: "baz",
Comm: "steak",
CD: "eggs",
Test: struct {
Elem2 string `xml:"elem2"`
Attr3 string `xml:",attr"`
}{
Elem2: "elem2",
Attr3: "attr3",
},
}
x := MustParseStruct(&str)
x1, err := xml.Marshal(str)
str1 := string(x1)
if err != nil {
t.Error(err)
}
str2, err := goxpath.MarshalStr(x)
if err != nil {
t.Error(err)
}
if str1 != str2 {
t.Error("Strings not equal")
t.Error(str1)
t.Error(str2)
}
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmltree/ 0000775 0000000 0000000 00000000000 13161151513 0026737 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmltree/xmlbuilder/ 0000775 0000000 0000000 00000000000 13161151513 0031106 5 ustar 00root root 0000000 0000000 xmlbuilder.go 0000664 0000000 0000000 00000001015 13161151513 0033522 0 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmltree/xmlbuilder package xmlbuilder
import (
"encoding/xml"
"github.com/ChrisTrenkamp/goxpath/tree"
)
//BuilderOpts supplies all the information needed to create an XML node.
type BuilderOpts struct {
Dec *xml.Decoder
Tok xml.Token
NodeType tree.NodeType
NS map[xml.Name]string
Attrs []*xml.Attr
NodePos int
AttrStartPos int
}
//XMLBuilder is an interface for creating XML structures.
type XMLBuilder interface {
tree.Node
CreateNode(*BuilderOpts) XMLBuilder
EndElem() XMLBuilder
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmltree/xmlele/ 0000775 0000000 0000000 00000000000 13161151513 0030225 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmltree/xmlele/xmlele.go 0000664 0000000 0000000 00000005127 13161151513 0032047 0 ustar 00root root 0000000 0000000 package xmlele
import (
"encoding/xml"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlbuilder"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlnode"
)
//XMLEle is an implementation of XPRes for XML elements
type XMLEle struct {
xml.StartElement
tree.NSBuilder
Attrs []tree.Node
Children []tree.Node
Parent tree.Elem
tree.NodePos
tree.NodeType
}
//Root is the default root node builder for xmltree.ParseXML
func Root() xmlbuilder.XMLBuilder {
return &XMLEle{NodeType: tree.NtRoot}
}
//CreateNode is an implementation of xmlbuilder.XMLBuilder. It appends the node
//specified in opts and returns the child if it is an element. Otherwise, it returns x.
func (x *XMLEle) CreateNode(opts *xmlbuilder.BuilderOpts) xmlbuilder.XMLBuilder {
if opts.NodeType == tree.NtElem {
ele := &XMLEle{
StartElement: opts.Tok.(xml.StartElement),
NSBuilder: tree.NSBuilder{NS: opts.NS},
Attrs: make([]tree.Node, len(opts.Attrs)),
Parent: x,
NodePos: tree.NodePos(opts.NodePos),
NodeType: opts.NodeType,
}
for i := range opts.Attrs {
ele.Attrs[i] = xmlnode.XMLNode{
Token: opts.Attrs[i],
NodePos: tree.NodePos(opts.AttrStartPos + i),
NodeType: tree.NtAttr,
Parent: ele,
}
}
x.Children = append(x.Children, ele)
return ele
}
node := xmlnode.XMLNode{
Token: opts.Tok,
NodePos: tree.NodePos(opts.NodePos),
NodeType: opts.NodeType,
Parent: x,
}
x.Children = append(x.Children, node)
return x
}
//EndElem is an implementation of xmlbuilder.XMLBuilder. It returns x's parent.
func (x *XMLEle) EndElem() xmlbuilder.XMLBuilder {
return x.Parent.(*XMLEle)
}
//GetToken returns the xml.Token representation of the node
func (x *XMLEle) GetToken() xml.Token {
return x.StartElement
}
//GetParent returns the parent node, or itself if it's the root
func (x *XMLEle) GetParent() tree.Elem {
return x.Parent
}
//GetChildren returns all child nodes of the element
func (x *XMLEle) GetChildren() []tree.Node {
ret := make([]tree.Node, len(x.Children))
for i := range x.Children {
ret[i] = x.Children[i]
}
return ret
}
//GetAttrs returns all attributes of the element
func (x *XMLEle) GetAttrs() []tree.Node {
ret := make([]tree.Node, len(x.Attrs))
for i := range x.Attrs {
ret[i] = x.Attrs[i]
}
return ret
}
//ResValue returns the string value of the element and children
func (x *XMLEle) ResValue() string {
ret := ""
for i := range x.Children {
switch x.Children[i].GetNodeType() {
case tree.NtChd, tree.NtElem, tree.NtRoot:
ret += x.Children[i].ResValue()
}
}
return ret
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmltree/xmlnode/ 0000775 0000000 0000000 00000000000 13161151513 0030405 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmltree/xmlnode/xmlnode.go 0000664 0000000 0000000 00000001614 13161151513 0032404 0 ustar 00root root 0000000 0000000 package xmlnode
import (
"encoding/xml"
"github.com/ChrisTrenkamp/goxpath/tree"
)
//XMLNode will represent an attribute, character data, comment, or processing instruction node
type XMLNode struct {
xml.Token
tree.NodePos
tree.NodeType
Parent tree.Elem
}
//GetToken returns the xml.Token representation of the node
func (a XMLNode) GetToken() xml.Token {
if a.NodeType == tree.NtAttr {
ret := a.Token.(*xml.Attr)
return *ret
}
return a.Token
}
//GetParent returns the parent node
func (a XMLNode) GetParent() tree.Elem {
return a.Parent
}
//ResValue returns the string value of the attribute
func (a XMLNode) ResValue() string {
switch a.NodeType {
case tree.NtAttr:
return a.Token.(*xml.Attr).Value
case tree.NtChd:
return string(a.Token.(xml.CharData))
case tree.NtComm:
return string(a.Token.(xml.Comment))
}
//case tree.NtPi:
return string(a.Token.(xml.ProcInst).Inst)
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xmltree/xmltree.go 0000664 0000000 0000000 00000007152 13161151513 0030753 0 ustar 00root root 0000000 0000000 package xmltree
import (
"encoding/xml"
"io"
"golang.org/x/net/html/charset"
"github.com/ChrisTrenkamp/goxpath/tree"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlbuilder"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree/xmlele"
)
//ParseOptions is a set of methods and function pointers that alter
//the way the XML decoder works and the Node types that are created.
//Options that are not set will default to what is set in internal/defoverride.go
type ParseOptions struct {
Strict bool
XMLRoot func() xmlbuilder.XMLBuilder
}
//DirectiveParser is an optional interface extended from XMLBuilder that handles
//XML directives.
type DirectiveParser interface {
xmlbuilder.XMLBuilder
Directive(xml.Directive, *xml.Decoder)
}
//ParseSettings is a function for setting the ParseOptions you want when
//parsing an XML tree.
type ParseSettings func(s *ParseOptions)
//MustParseXML is like ParseXML, but panics instead of returning an error.
func MustParseXML(r io.Reader, op ...ParseSettings) tree.Node {
ret, err := ParseXML(r, op...)
if err != nil {
panic(err)
}
return ret
}
//ParseXML creates an XMLTree structure from an io.Reader.
func ParseXML(r io.Reader, op ...ParseSettings) (tree.Node, error) {
ov := ParseOptions{
Strict: true,
XMLRoot: xmlele.Root,
}
for _, i := range op {
i(&ov)
}
dec := xml.NewDecoder(r)
dec.CharsetReader = charset.NewReaderLabel
dec.Strict = ov.Strict
ordrPos := 1
xmlTree := ov.XMLRoot()
t, err := dec.Token()
if err != nil {
return nil, err
}
if head, ok := t.(xml.ProcInst); ok && head.Target == "xml" {
t, err = dec.Token()
}
opts := xmlbuilder.BuilderOpts{
Dec: dec,
}
for err == nil {
switch xt := t.(type) {
case xml.StartElement:
setEle(&opts, xmlTree, xt, &ordrPos)
xmlTree = xmlTree.CreateNode(&opts)
case xml.CharData:
setNode(&opts, xmlTree, xt, tree.NtChd, &ordrPos)
xmlTree = xmlTree.CreateNode(&opts)
case xml.Comment:
setNode(&opts, xmlTree, xt, tree.NtComm, &ordrPos)
xmlTree = xmlTree.CreateNode(&opts)
case xml.ProcInst:
setNode(&opts, xmlTree, xt, tree.NtPi, &ordrPos)
xmlTree = xmlTree.CreateNode(&opts)
case xml.EndElement:
xmlTree = xmlTree.EndElem()
case xml.Directive:
if dp, ok := xmlTree.(DirectiveParser); ok {
dp.Directive(xt.Copy(), dec)
}
}
t, err = dec.Token()
}
if err == io.EOF {
err = nil
}
return xmlTree, err
}
func setEle(opts *xmlbuilder.BuilderOpts, xmlTree xmlbuilder.XMLBuilder, ele xml.StartElement, ordrPos *int) {
opts.NodePos = *ordrPos
opts.Tok = ele
opts.Attrs = opts.Attrs[0:0:cap(opts.Attrs)]
opts.NS = make(map[xml.Name]string)
opts.NodeType = tree.NtElem
for i := range ele.Attr {
attr := ele.Attr[i].Name
val := ele.Attr[i].Value
if (attr.Local == "xmlns" && attr.Space == "") || attr.Space == "xmlns" {
opts.NS[attr] = val
} else {
opts.Attrs = append(opts.Attrs, &ele.Attr[i])
}
}
if nstree, ok := xmlTree.(tree.NSElem); ok {
ns := make(map[xml.Name]string)
for _, i := range tree.BuildNS(nstree) {
ns[i.Name] = i.Value
}
for k, v := range opts.NS {
ns[k] = v
}
if ns[xml.Name{Local: "xmlns"}] == "" {
delete(ns, xml.Name{Local: "xmlns"})
}
for k, v := range ns {
opts.NS[k] = v
}
if xmlTree.GetNodeType() == tree.NtRoot {
opts.NS[xml.Name{Space: "xmlns", Local: "xml"}] = tree.XMLSpace
}
}
opts.AttrStartPos = len(opts.NS) + len(opts.Attrs) + *ordrPos
*ordrPos = opts.AttrStartPos + 1
}
func setNode(opts *xmlbuilder.BuilderOpts, xmlTree xmlbuilder.XMLBuilder, tok xml.Token, nt tree.NodeType, ordrPos *int) {
opts.Tok = xml.CopyToken(tok)
opts.NodeType = nt
opts.NodePos = *ordrPos
*ordrPos++
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/tree/xtypes.go 0000664 0000000 0000000 00000003721 13161151513 0027145 0 ustar 00root root 0000000 0000000 package tree
import (
"fmt"
"math"
"strconv"
"strings"
)
//Boolean strings
const (
True = "true"
False = "false"
)
//Bool is a boolean XPath type
type Bool bool
//ResValue satisfies the Res interface for Bool
func (b Bool) String() string {
if b {
return True
}
return False
}
//Bool satisfies the HasBool interface for Bool's
func (b Bool) Bool() Bool {
return b
}
//Num satisfies the HasNum interface for Bool's
func (b Bool) Num() Num {
if b {
return Num(1)
}
return Num(0)
}
//Num is a number XPath type
type Num float64
//ResValue satisfies the Res interface for Num
func (n Num) String() string {
if math.IsInf(float64(n), 0) {
if math.IsInf(float64(n), 1) {
return "Infinity"
}
return "-Infinity"
}
return fmt.Sprintf("%g", float64(n))
}
//Bool satisfies the HasBool interface for Num's
func (n Num) Bool() Bool {
return n != 0
}
//Num satisfies the HasNum interface for Num's
func (n Num) Num() Num {
return n
}
//String is string XPath type
type String string
//ResValue satisfies the Res interface for String
func (s String) String() string {
return string(s)
}
//Bool satisfies the HasBool interface for String's
func (s String) Bool() Bool {
return Bool(len(s) > 0)
}
//Num satisfies the HasNum interface for String's
func (s String) Num() Num {
num, err := strconv.ParseFloat(strings.TrimSpace(string(s)), 64)
if err != nil {
return Num(math.NaN())
}
return Num(num)
}
//NodeSet is a node-set XPath type
type NodeSet []Node
//GetNodeNum converts the node to a string-value and to a number
func GetNodeNum(n Node) Num {
return String(n.ResValue()).Num()
}
//String satisfies the Res interface for NodeSet
func (n NodeSet) String() string {
if len(n) == 0 {
return ""
}
return n[0].ResValue()
}
//Bool satisfies the HasBool interface for node-set's
func (n NodeSet) Bool() Bool {
return Bool(len(n) > 0)
}
//Num satisfies the HasNum interface for NodeSet's
func (n NodeSet) Num() Num {
return String(n.String()).Num()
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/value_test.go 0000664 0000000 0000000 00000003007 13161151513 0027022 0 ustar 00root root 0000000 0000000 package goxpath
import (
"bytes"
"runtime/debug"
"testing"
"github.com/ChrisTrenkamp/goxpath/tree/xmltree"
)
func execVal(xp, x string, exp string, ns map[string]string, t *testing.T) {
defer func() {
if r := recover(); r != nil {
t.Error("Panicked: from XPath expr: '" + xp)
t.Error(r)
t.Error(string(debug.Stack()))
}
}()
res := MustParse(xp).MustExec(xmltree.MustParseXML(bytes.NewBufferString(x)), func(o *Opts) { o.NS = ns })
if res.String() != exp {
t.Error("Incorrect result:'" + res.String() + "' from XPath expr: '" + xp + "'. Expecting: '" + exp + "'")
return
}
}
func TestNodeVal(t *testing.T) {
p := `/test`
x := `testpathtest2`
exp := "testpathtest2"
execVal(p, x, exp, nil, t)
}
func TestAttrVal(t *testing.T) {
p := `/p1/@test`
x := ``
exp := "foo"
execVal(p, x, exp, nil, t)
}
func TestCommentVal(t *testing.T) {
p := `//comment()`
x := ``
exp := ` comment `
execVal(p, x, exp, nil, t)
}
func TestProcInstVal(t *testing.T) {
p := `//processing-instruction()`
x := ``
exp := `test`
execVal(p, x, exp, nil, t)
}
func TestNodeNamespaceVal(t *testing.T) {
p := `/test:p1/namespace::test`
x := ``
exp := `http://test`
execVal(p, x, exp, nil, t)
}
golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/xconst/ 0000775 0000000 0000000 00000000000 13161151513 0025636 5 ustar 00root root 0000000 0000000 golang-github-christrenkamp-goxpath-1.0~alpha3+git20170922.c385f95/xconst/xconst.go 0000664 0000000 0000000 00000003662 13161151513 0027512 0 ustar 00root root 0000000 0000000 package xconst
const (
//AxisAncestor represents the "ancestor" axis
AxisAncestor = "ancestor"
//AxisAncestorOrSelf represents the "ancestor-or-self" axis
AxisAncestorOrSelf = "ancestor-or-self"
//AxisAttribute represents the "attribute" axis
AxisAttribute = "attribute"
//AxisChild represents the "child" axis
AxisChild = "child"
//AxisDescendent represents the "descendant" axis
AxisDescendent = "descendant"
//AxisDescendentOrSelf represents the "descendant-or-self" axis
AxisDescendentOrSelf = "descendant-or-self"
//AxisFollowing represents the "following" axis
AxisFollowing = "following"
//AxisFollowingSibling represents the "following-sibling" axis
AxisFollowingSibling = "following-sibling"
//AxisNamespace represents the "namespace" axis
AxisNamespace = "namespace"
//AxisParent represents the "parent" axis
AxisParent = "parent"
//AxisPreceding represents the "preceding" axis
AxisPreceding = "preceding"
//AxisPrecedingSibling represents the "preceding-sibling" axis
AxisPrecedingSibling = "preceding-sibling"
//AxisSelf represents the "self" axis
AxisSelf = "self"
)
//AxisNames is all the possible Axis identifiers wrapped in an array for convenience
var AxisNames = []string{
AxisAncestor,
AxisAncestorOrSelf,
AxisAttribute,
AxisChild,
AxisDescendent,
AxisDescendentOrSelf,
AxisFollowing,
AxisFollowingSibling,
AxisNamespace,
AxisParent,
AxisPreceding,
AxisPrecedingSibling,
AxisSelf,
}
const (
//NodeTypeComment represents the "comment" node test
NodeTypeComment = "comment"
//NodeTypeText represents the "text" node test
NodeTypeText = "text"
//NodeTypeProcInst represents the "processing-instruction" node test
NodeTypeProcInst = "processing-instruction"
//NodeTypeNode represents the "node" node test
NodeTypeNode = "node"
)
//NodeTypes is all the possible node tests wrapped in an array for convenience
var NodeTypes = []string{
NodeTypeComment,
NodeTypeText,
NodeTypeProcInst,
NodeTypeNode,
}