pax_global_header00006660000000000000000000000064147331003030014504gustar00rootroot0000000000000052 comment=9dfa07330e761e0299fb8f90be5d9baaa4660e0c golang-github-antchfx-xpath-1.3.3/000077500000000000000000000000001473310030300170125ustar00rootroot00000000000000golang-github-antchfx-xpath-1.3.3/.github/000077500000000000000000000000001473310030300203525ustar00rootroot00000000000000golang-github-antchfx-xpath-1.3.3/.github/workflows/000077500000000000000000000000001473310030300224075ustar00rootroot00000000000000golang-github-antchfx-xpath-1.3.3/.github/workflows/coverage.yml000066400000000000000000000011601473310030300247230ustar00rootroot00000000000000name: goveralls on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - name: Set up Go uses: actions/setup-go@v5 with: go-version: "1.16" - name: Check out code uses: actions/checkout@v4 - name: Run coverage run: | go test -covermode atomic -coverprofile=coverage.out ./... - name: Install goveralls run: go install github.com/mattn/goveralls@latest - name: Send coverage env: COVERALLS_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: goveralls -coverprofile=coverage.out -service=github golang-github-antchfx-xpath-1.3.3/.github/workflows/testing.yml000066400000000000000000000005241473310030300246100ustar00rootroot00000000000000name: Testing on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - name: Install checkout uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: "stable" - name: Run tests run: | go version go test golang-github-antchfx-xpath-1.3.3/.gitignore000066400000000000000000000004621473310030300210040ustar00rootroot00000000000000# vscode .vscode debug *.test ./build # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.profgolang-github-antchfx-xpath-1.3.3/LICENSE000066400000000000000000000017761473310030300200320ustar00rootroot00000000000000Permission 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-antchfx-xpath-1.3.3/README.md000066400000000000000000000137151473310030300203000ustar00rootroot00000000000000# XPath [![GoDoc](https://godoc.org/github.com/antchfx/xpath?status.svg)](https://godoc.org/github.com/antchfx/xpath) [![Coverage Status](https://coveralls.io/repos/github/antchfx/xpath/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xpath?branch=master) [![Build Status](https://github.com/antchfx/xpath/actions/workflows/testing.yml/badge.svg)](https://github.com/antchfx/xpath/actions/workflows/testing.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/antchfx/xpath)](https://goreportcard.com/report/github.com/antchfx/xpath) XPath is Go package provides selecting nodes from XML, HTML or other documents using XPath expression. # Implementation - [htmlquery](https://github.com/antchfx/htmlquery) - an XPath query package for HTML document - [xmlquery](https://github.com/antchfx/xmlquery) - an XPath query package for XML document. - [jsonquery](https://github.com/antchfx/jsonquery) - an XPath query package for JSON document # Supported Features #### The basic XPath patterns. > The basic XPath patterns cover 90% of the cases that most stylesheets will need. - `node` : Selects all child elements with nodeName of node. - `*` : Selects all child elements. - `@attr` : Selects the attribute attr. - `@*` : Selects all attributes. - `node()` : Matches an org.w3c.dom.Node. - `text()` : Matches a org.w3c.dom.Text node. - `comment()` : Matches a comment. - `.` : Selects the current node. - `..` : Selects the parent of current node. - `/` : Selects the document node. - `a[expr]` : Select only those nodes matching a which also satisfy the expression expr. - `a[n]` : Selects the nth matching node matching a When a filter's expression is a number, XPath selects based on position. - `a/b` : For each node matching a, add the nodes matching b to the result. - `a//b` : For each node matching a, add the descendant nodes matching b to the result. - `//b` : Returns elements in the entire document matching b. - `a|b` : All nodes matching a or b, union operation(not boolean or). - `(a, b, c)` : Evaluates each of its operands and concatenates the resulting sequences, in order, into a single result sequence - `(a/b)` : Selects all matches nodes as grouping set. #### Node Axes - `child::*` : The child axis selects children of the current node. - `child::node()`: Selects all the children of the context node. - `child::text()`: Selects all text node children of the context node. - `descendant::*` : The descendant axis selects descendants of the current node. It is equivalent to '//'. - `descendant-or-self::*` : Selects descendants including the current node. - `attribute::*` : Selects attributes of the current element. It is equivalent to @\* - `following-sibling::*` : Selects nodes after the current node. - `preceding-sibling::*` : Selects nodes before the current node. - `following::*` : Selects the first matching node following in document order, excluding descendants. - `preceding::*` : Selects the first matching node preceding in document order, excluding ancestors. - `parent::*` : Selects the parent if it matches. The '..' pattern from the core is equivalent to 'parent::node()'. - `ancestor::*` : Selects matching ancestors. - `ancestor-or-self::*` : Selects ancestors including the current node. - `self::*` : Selects the current node. '.' is equivalent to 'self::node()'. #### Expressions The gxpath supported three types: number, boolean, string. - `path` : Selects nodes based on the path. - `a = b` : Standard comparisons. - `a = b` : True if a equals b. - `a != b` : True if a is not equal to b. - `a < b` : True if a is less than b. - `a <= b` : True if a is less than or equal to b. - `a > b` : True if a is greater than b. - `a >= b` : True if a is greater than or equal to b. - `a + b` : Arithmetic expressions. - `- a` Unary minus - `a + b` : Addition - `a - b` : Subtraction - `a * b` : Multiplication - `a div b` : Division - `a mod b` : Modulus (division remainder) - `a or b` : Boolean `or` operation. - `a and b` : Boolean `and` operation. - `(expr)` : Parenthesized expressions. - `fun(arg1, ..., argn)` : Function calls: | Function | Supported | | ----------------------- | --------- | | `boolean()` | ✓ | | `ceiling()` | ✓ | | `choose()` | ✗ | | `concat()` | ✓ | | `contains()` | ✓ | | `count()` | ✓ | | `current()` | ✗ | | `document()` | ✗ | | `element-available()` | ✗ | | `ends-with()` | ✓ | | `false()` | ✓ | | `floor()` | ✓ | | `format-number()` | ✗ | | `function-available()` | ✗ | | `generate-id()` | ✗ | | `id()` | ✗ | | `key()` | ✗ | | `lang()` | ✗ | | `last()` | ✓ | | `local-name()` | ✓ | | `lower-case()`[^1] | ✓ | | `matches()` | ✓ | | `name()` | ✓ | | `namespace-uri()` | ✓ | | `normalize-space()` | ✓ | | `not()` | ✓ | | `number()` | ✓ | | `position()` | ✓ | | `replace()` | ✓ | | `reverse()` | ✓ | | `round()` | ✓ | | `starts-with()` | ✓ | | `string()` | ✓ | | `string-join()`[^1] | ✓ | | `string-length()` | ✓ | | `substring()` | ✓ | | `substring-after()` | ✓ | | `substring-before()` | ✓ | | `sum()` | ✓ | | `system-property()` | ✗ | | `translate()` | ✓ | | `true()` | ✓ | | `unparsed-entity-url()` | ✗ | [^1]: XPath-2.0 expression golang-github-antchfx-xpath-1.3.3/assert_test.go000066400000000000000000000016211473310030300217010ustar00rootroot00000000000000package xpath import ( "reflect" "testing" ) func assertEqual(tb testing.TB, v1, v2 interface{}) { if !reflect.DeepEqual(v1, v2) { tb.Fatalf("'%+v' and '%+v' are not equal", v1, v2) } } func assertNoErr(tb testing.TB, err error) { if err != nil { tb.Fatalf("expected no err, but got: %s", err.Error()) } } func assertErr(tb testing.TB, err error) { if err == nil { tb.Fatal("expected err, but got nil") } } func assertTrue(tb testing.TB, v bool) { if !v { tb.Fatal("expected true, but got false") } } func assertFalse(tb testing.TB, v bool) { if v { tb.Fatal("expected false, but got true") } } func assertNil(tb testing.TB, v interface{}) { if v != nil && !reflect.ValueOf(v).IsNil() { tb.Fatalf("expected nil, but got: %+v", v) } } func assertPanic(t *testing.T, f func()) { defer func() { if r := recover(); r == nil { t.Errorf("The code did not panic") } }() f() } golang-github-antchfx-xpath-1.3.3/build.go000066400000000000000000000503601473310030300204440ustar00rootroot00000000000000package xpath import ( "errors" "fmt" ) type flag int var flagsEnum = struct { None flag SmartDesc flag PosFilter flag Filter flag Condition flag }{ None: 0, SmartDesc: 1, PosFilter: 2, Filter: 4, Condition: 8, } type builderProp int var builderProps = struct { None builderProp PosFilter builderProp HasPosition builderProp HasLast builderProp NonFlat builderProp }{ None: 0, PosFilter: 1, HasPosition: 2, HasLast: 4, NonFlat: 8, } // builder provides building an XPath expressions. type builder struct { parseDepth int firstInput query } // axisPredicate creates a predicate to predicating for this axis node. func axisPredicate(root *axisNode) func(NodeNavigator) bool { nametest := root.LocalName != "" || root.Prefix != "" predicate := func(n NodeNavigator) bool { if root.typeTest == n.NodeType() || root.typeTest == allNode { if nametest { type namespaceURL interface { NamespaceURL() string } if ns, ok := n.(namespaceURL); ok && root.hasNamespaceURI { return root.LocalName == n.LocalName() && root.namespaceURI == ns.NamespaceURL() } if root.LocalName == n.LocalName() && root.Prefix == n.Prefix() { return true } } else { return true } } return false } return predicate } // processAxis processes a query for the XPath axis node. func (b *builder) processAxis(root *axisNode, flags flag, props *builderProp) (query, error) { var ( err error qyInput query qyOutput query ) b.firstInput = nil predicate := axisPredicate(root) if root.Input == nil { qyInput = &contextQuery{} *props = builderProps.None } else { inputFlags := flagsEnum.None if (flags & flagsEnum.Filter) == 0 { if root.AxisType == "child" && (root.Input.Type() == nodeAxis) { if input := root.Input.(*axisNode); input.AxisType == "descendant-or-self" { var qyGrandInput query if input.Input != nil { qyGrandInput, err = b.processNode(input.Input, flagsEnum.SmartDesc, props) if err != nil { return nil, err } } else { qyGrandInput = &contextQuery{} } qyOutput = &descendantQuery{name: root.LocalName, Input: qyGrandInput, Predicate: predicate, Self: false} *props |= builderProps.NonFlat return qyOutput, nil } } if root.AxisType == "descendant" || root.AxisType == "descendant-or-self" { inputFlags |= flagsEnum.SmartDesc } } qyInput, err = b.processNode(root.Input, inputFlags, props) if err != nil { return nil, err } } switch root.AxisType { case "ancestor": qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} *props |= builderProps.NonFlat case "ancestor-or-self": qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true} *props |= builderProps.NonFlat case "attribute": qyOutput = &attributeQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} case "child": if (*props & builderProps.NonFlat) == 0 { qyOutput = &childQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} } else { qyOutput = &cachedChildQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} } case "descendant": if (flags & flagsEnum.SmartDesc) != flagsEnum.None { qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: false, Predicate: predicate} } else { qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} } *props |= builderProps.NonFlat case "descendant-or-self": if (flags & flagsEnum.SmartDesc) != flagsEnum.None { qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: true, Predicate: predicate} } else { qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true} } *props |= builderProps.NonFlat case "following": qyOutput = &followingQuery{Input: qyInput, Predicate: predicate} *props |= builderProps.NonFlat case "following-sibling": qyOutput = &followingQuery{Input: qyInput, Predicate: predicate, Sibling: true} case "parent": qyOutput = &parentQuery{Input: qyInput, Predicate: predicate} case "preceding": qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate} *props |= builderProps.NonFlat case "preceding-sibling": qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate, Sibling: true} case "self": qyOutput = &selfQuery{Input: qyInput, Predicate: predicate} case "namespace": // haha,what will you do someting?? default: err = fmt.Errorf("unknown axe type: %s", root.AxisType) return nil, err } return qyOutput, nil } func canBeNumber(q query) bool { if q.ValueType() != xpathResultType.Any { return q.ValueType() == xpathResultType.Number } return true } // processFilterNode builds query for the XPath filter predicate. func (b *builder) processFilter(root *filterNode, flags flag, props *builderProp) (query, error) { first := (flags & flagsEnum.Filter) == 0 qyInput, err := b.processNode(root.Input, (flags | flagsEnum.Filter), props) if err != nil { return nil, err } firstInput := b.firstInput var propsCond builderProp cond, err := b.processNode(root.Condition, flags, &propsCond) if err != nil { return nil, err } // Checking whether is number if canBeNumber(cond) || ((propsCond & (builderProps.HasPosition | builderProps.HasLast)) != 0) { propsCond |= builderProps.HasPosition flags |= flagsEnum.PosFilter } if root.Input.Type() != nodeFilter { *props &= ^builderProps.PosFilter } if (propsCond & builderProps.HasPosition) != 0 { *props |= builderProps.PosFilter } if (propsCond & builderProps.HasPosition) != builderProps.None { if (propsCond & builderProps.HasLast) != 0 { // https://github.com/antchfx/xpath/issues/76 // https://github.com/antchfx/xpath/issues/78 if qyFunc, ok := cond.(*functionQuery); ok { switch qyFunc.Input.(type) { case *filterQuery: cond = &lastFuncQuery{Input: qyFunc.Input} } } } } merge := (qyInput.Properties() & queryProps.Merge) != 0 if first && firstInput != nil { if merge && ((*props & builderProps.PosFilter) != 0) { var ( rootQuery = &contextQuery{} parent query ) switch axisQuery := firstInput.(type) { case *ancestorQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *attributeQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *childQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *cachedChildQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *descendantQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *followingQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *precedingQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *parentQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *selfQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *groupQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *descendantOverDescendantQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } } b.firstInput = nil child := &filterQuery{Input: qyInput, Predicate: cond, NoPosition: false} if parent != nil { return &mergeQuery{Input: parent, Child: child}, nil } return child, nil } b.firstInput = nil } resultQuery := &filterQuery{ Input: qyInput, Predicate: cond, NoPosition: (propsCond & builderProps.HasPosition) == 0, } return resultQuery, nil } // processFunctionNode processes query for the XPath function node. func (b *builder) processFunction(root *functionNode, props *builderProp) (query, error) { // Reset builder props *props = builderProps.None var qyOutput query switch root.FuncName { case "lower-case": arg, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: lowerCaseFunc(arg)} case "starts-with": arg1, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: startwithFunc(arg1, arg2)} case "ends-with": arg1, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: endwithFunc(arg1, arg2)} case "contains": arg1, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: containsFunc(arg1, arg2)} case "matches": //matches(string , pattern) if len(root.Args) != 2 { return nil, errors.New("xpath: matches function must have two parameters") } var ( arg1, arg2 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } // Issue #92, testing the regular expression before. if q, ok := arg2.(*constantQuery); ok { if _, err = getRegexp(q.Val.(string)); err != nil { return nil, fmt.Errorf("matches() got error. %v", err) } } qyOutput = &functionQuery{Func: matchesFunc(arg1, arg2)} case "substring": //substring( string , start [, length] ) if len(root.Args) < 2 { return nil, errors.New("xpath: substring function must have at least two parameter") } var ( arg1, arg2, arg3 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } if len(root.Args) == 3 { if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil { return nil, err } } qyOutput = &functionQuery{Func: substringFunc(arg1, arg2, arg3)} case "substring-before", "substring-after": //substring-xxxx( haystack, needle ) if len(root.Args) != 2 { return nil, errors.New("xpath: substring-before function must have two parameters") } var ( arg1, arg2 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } qyOutput = &functionQuery{ Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"), } case "string-length": // string-length( [string] ) if len(root.Args) < 1 { return nil, errors.New("xpath: string-length function must have at least one parameter") } arg1, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: stringLengthFunc(arg1)} case "normalize-space": var arg node if len(root.Args) > 0 { arg = root.Args[0] } else { arg = newAxisNode("self", allNode, "", "", "", nil) } arg1, err := b.processNode(arg, flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: normalizespaceFunc(arg1)} case "replace": //replace( string , string, string ) if len(root.Args) != 3 { return nil, errors.New("xpath: replace function must have three parameters") } var ( arg1, arg2, arg3 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil { return nil, err } qyOutput = &functionQuery{Func: replaceFunc(arg1, arg2, arg3)} case "translate": //translate( string , string, string ) if len(root.Args) != 3 { return nil, errors.New("xpath: translate function must have three parameters") } var ( arg1, arg2, arg3 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil { return nil, err } qyOutput = &functionQuery{Func: translateFunc(arg1, arg2, arg3)} case "not": if len(root.Args) == 0 { return nil, errors.New("xpath: not function must have at least one parameter") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: notFunc(argQuery)} case "name", "local-name", "namespace-uri": if len(root.Args) > 1 { return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName) } var ( arg query err error ) if len(root.Args) == 1 { arg, err = b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } } switch root.FuncName { case "name": qyOutput = &functionQuery{Func: nameFunc(arg)} case "local-name": qyOutput = &functionQuery{Func: localNameFunc(arg)} case "namespace-uri": qyOutput = &functionQuery{Func: namespaceFunc(arg)} } case "true", "false": val := root.FuncName == "true" qyOutput = &functionQuery{ Func: func(_ query, _ iterator) interface{} { return val }, } case "last": qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc()} *props |= builderProps.HasLast case "position": qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc()} *props |= builderProps.HasPosition case "boolean", "number", "string": var inp query if len(root.Args) > 1 { return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName) } if len(root.Args) == 1 { argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } inp = argQuery } switch root.FuncName { case "boolean": qyOutput = &functionQuery{Func: booleanFunc(inp)} case "string": qyOutput = &functionQuery{Func: stringFunc(inp)} case "number": qyOutput = &functionQuery{Func: numberFunc(inp)} } case "count": if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: countFunc(argQuery)} case "sum": if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: sum(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: sumFunc(argQuery)} case "ceiling", "floor", "round": if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: ceiling(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } switch root.FuncName { case "ceiling": qyOutput = &functionQuery{Func: ceilingFunc(argQuery)} case "floor": qyOutput = &functionQuery{Func: floorFunc(argQuery)} case "round": qyOutput = &functionQuery{Func: roundFunc(argQuery)} } case "concat": if len(root.Args) < 2 { return nil, fmt.Errorf("xpath: concat() must have at least two arguments") } var args []query for _, v := range root.Args { q, err := b.processNode(v, flagsEnum.None, props) if err != nil { return nil, err } args = append(args, q) } qyOutput = &functionQuery{Func: concatFunc(args...)} case "reverse": if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: reverse(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &transformFunctionQuery{Input: argQuery, Func: reverseFunc} case "string-join": if len(root.Args) != 2 { return nil, fmt.Errorf("xpath: string-join(node-sets, separator) function requires node-set and argument") } input, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } arg1, err := b.processNode(root.Args[1], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: stringJoinFunc(input, arg1)} default: return nil, fmt.Errorf("not yet support this function %s()", root.FuncName) } return qyOutput, nil } func (b *builder) processOperator(root *operatorNode, props *builderProp) (query, error) { var ( leftProp builderProp rightProp builderProp ) left, err := b.processNode(root.Left, flagsEnum.None, &leftProp) if err != nil { return nil, err } right, err := b.processNode(root.Right, flagsEnum.None, &rightProp) if err != nil { return nil, err } *props = leftProp | rightProp var qyOutput query switch root.Op { case "+", "-", "*", "div", "mod": // Numeric operator var exprFunc func(iterator, interface{}, interface{}) interface{} switch root.Op { case "+": exprFunc = plusFunc case "-": exprFunc = minusFunc case "*": exprFunc = mulFunc case "div": exprFunc = divFunc case "mod": exprFunc = modFunc } qyOutput = &numericQuery{Left: left, Right: right, Do: exprFunc} case "=", ">", ">=", "<", "<=", "!=": var exprFunc func(iterator, interface{}, interface{}) interface{} switch root.Op { case "=": exprFunc = eqFunc case ">": exprFunc = gtFunc case ">=": exprFunc = geFunc case "<": exprFunc = ltFunc case "<=": exprFunc = leFunc case "!=": exprFunc = neFunc } qyOutput = &logicalQuery{Left: left, Right: right, Do: exprFunc} case "or", "and": isOr := false if root.Op == "or" { isOr = true } qyOutput = &booleanQuery{Left: left, Right: right, IsOr: isOr} case "|": *props |= builderProps.NonFlat qyOutput = &unionQuery{Left: left, Right: right} } return qyOutput, nil } func (b *builder) processNode(root node, flags flag, props *builderProp) (q query, err error) { if b.parseDepth = b.parseDepth + 1; b.parseDepth > 1024 { err = errors.New("the xpath expressions is too complex") return } *props = builderProps.None switch root.Type() { case nodeConstantOperand: n := root.(*operandNode) q = &constantQuery{Val: n.Val} case nodeRoot: q = &absoluteQuery{} case nodeAxis: q, err = b.processAxis(root.(*axisNode), flags, props) b.firstInput = q case nodeFilter: q, err = b.processFilter(root.(*filterNode), flags, props) b.firstInput = q case nodeFunction: q, err = b.processFunction(root.(*functionNode), props) case nodeOperator: q, err = b.processOperator(root.(*operatorNode), props) case nodeGroup: q, err = b.processNode(root.(*groupNode).Input, flagsEnum.None, props) if err != nil { return } q = &groupQuery{Input: q} if b.firstInput == nil { b.firstInput = q } } b.parseDepth-- return } // build builds a specified XPath expressions expr. func build(expr string, namespaces map[string]string) (q query, err error) { defer func() { if e := recover(); e != nil { switch x := e.(type) { case string: err = errors.New(x) case error: err = x default: err = errors.New("unknown panic") } } }() root := parse(expr, namespaces) b := &builder{} props := builderProps.None return b.processNode(root, flagsEnum.None, &props) } golang-github-antchfx-xpath-1.3.3/cache.go000066400000000000000000000045461473310030300204150ustar00rootroot00000000000000package xpath import ( "regexp" "sync" ) type loadFunc func(key interface{}) (interface{}, error) const ( defaultCap = 65536 ) // The reason we're building a simple capacity-resetting loading cache (when capacity reached) instead of using // something like github.com/hashicorp/golang-lru is primarily due to (not wanting to create) external dependency. // Currently this library has 0 external dep (other than go sdk), and supports go 1.6, 1.9, and 1.10 (and later). // Creating external lib dependencies (plus their transitive dependencies) would make things hard if not impossible. // We expect under most circumstances, the defaultCap is big enough for any long running services that use this // library if their xpath regexp cardinality is low. However, in extreme cases when the capacity is reached, we // simply reset the cache, taking a small subsequent perf hit (next to nothing considering amortization) in trade // of more complex and less performant LRU type of construct. type loadingCache struct { sync.RWMutex cap int load loadFunc m map[interface{}]interface{} reset int } // NewLoadingCache creates a new instance of a loading cache with capacity. Capacity must be >= 0, or // it will panic. Capacity == 0 means the cache growth is unbounded. func NewLoadingCache(load loadFunc, capacity int) *loadingCache { if capacity < 0 { panic("capacity must be >= 0") } return &loadingCache{cap: capacity, load: load, m: make(map[interface{}]interface{})} } func (c *loadingCache) get(key interface{}) (interface{}, error) { c.RLock() v, found := c.m[key] c.RUnlock() if found { return v, nil } v, err := c.load(key) if err != nil { return nil, err } c.Lock() if c.cap > 0 && len(c.m) >= c.cap { c.m = map[interface{}]interface{}{key: v} c.reset++ } else { c.m[key] = v } c.Unlock() return v, nil } var ( // RegexpCache is a loading cache for string -> *regexp.Regexp mapping. It is exported so that in rare cases // client can customize load func and/or capacity. RegexpCache = defaultRegexpCache() ) func defaultRegexpCache() *loadingCache { return NewLoadingCache( func(key interface{}) (interface{}, error) { return regexp.Compile(key.(string)) }, defaultCap) } func getRegexp(pattern string) (*regexp.Regexp, error) { exp, err := RegexpCache.get(pattern) if err != nil { return nil, err } return exp.(*regexp.Regexp), nil } golang-github-antchfx-xpath-1.3.3/cache_test.go000066400000000000000000000102051473310030300214410ustar00rootroot00000000000000package xpath import ( "errors" "fmt" "math/rand" "strconv" "testing" ) func TestLoadingCache(t *testing.T) { c := NewLoadingCache( func(key interface{}) (interface{}, error) { switch v := key.(type) { case int: return strconv.Itoa(v), nil default: return nil, errors.New("invalid type") } }, 2) // cap = 2 assertEqual(t, 0, len(c.m)) v, err := c.get(1) assertNoErr(t, err) assertEqual(t, "1", v) assertEqual(t, 1, len(c.m)) v, err = c.get(1) assertNoErr(t, err) assertEqual(t, "1", v) assertEqual(t, 1, len(c.m)) v, err = c.get(2) assertNoErr(t, err) assertEqual(t, "2", v) assertEqual(t, 2, len(c.m)) // over capacity, m is reset v, err = c.get(3) assertNoErr(t, err) assertEqual(t, "3", v) assertEqual(t, 1, len(c.m)) // Invalid capacity assertPanic(t, func() { NewLoadingCache(func(key interface{}) (interface{}, error) { return key, nil }, -1) }) // Loading failure c = NewLoadingCache( func(key interface{}) (interface{}, error) { if key.(int)%2 == 0 { return key, nil } else { return nil, fmt.Errorf("artificial error: %d", key.(int)) } }, 0) v, err = c.get(12) assertNoErr(t, err) assertEqual(t, 12, v) _, err = c.get(21) assertErr(t, err) assertEqual(t, "artificial error: 21", err.Error()) } const ( benchLoadingCacheRandSeed = 12345 benchLoadingCacheConcurrency = 5 benchLoadingCacheKeyRange = 2000 benchLoadingCacheCap = 1000 ) func BenchmarkLoadingCacheCapped_SingleThread(b *testing.B) { rand.Seed(benchLoadingCacheRandSeed) c := NewLoadingCache( func(key interface{}) (interface{}, error) { return key, nil }, benchLoadingCacheCap) for i := 0; i < b.N; i++ { k := rand.Intn(benchLoadingCacheKeyRange) v, _ := c.get(k) if k != v { b.FailNow() } } b.Logf("N=%d, reset=%d", b.N, c.reset) } func BenchmarkLoadingCacheCapped_MultiThread(b *testing.B) { rand.Seed(benchLoadingCacheRandSeed) c := NewLoadingCache( func(key interface{}) (interface{}, error) { return key, nil }, benchLoadingCacheCap) errCh := make(chan error, benchLoadingCacheConcurrency) defer close(errCh) for i := 0; i < benchLoadingCacheConcurrency; i++ { go func() { for j := 0; j < b.N; j++ { k := rand.Intn(benchLoadingCacheKeyRange) v, _ := c.get(k) if k != v { errCh <- fmt.Errorf("key:%q != value:%q", k, v) return } } errCh <- nil return }() } for i := 0; i < benchLoadingCacheConcurrency; i++ { if err := <-errCh; err != nil { b.Fatal(err) } } b.Logf("N=%d, concurrency=%d, reset=%d", b.N, benchLoadingCacheConcurrency, c.reset) } func BenchmarkLoadingCacheNoCap_SingleThread(b *testing.B) { rand.Seed(benchLoadingCacheRandSeed) c := NewLoadingCache( func(key interface{}) (interface{}, error) { return key, nil }, 0) // 0 => no cap for i := 0; i < b.N; i++ { k := rand.Intn(benchLoadingCacheKeyRange) v, _ := c.get(k) if k != v { b.FailNow() } } b.Logf("N=%d, reset=%d", b.N, c.reset) } func BenchmarkLoadingCacheNoCap_MultiThread(b *testing.B) { rand.Seed(benchLoadingCacheRandSeed) c := NewLoadingCache( func(key interface{}) (interface{}, error) { return key, nil }, 0) // 0 => no cap errCh := make(chan error, benchLoadingCacheConcurrency) defer close(errCh) for i := 0; i < benchLoadingCacheConcurrency; i++ { go func() { for j := 0; j < b.N; j++ { k := rand.Intn(benchLoadingCacheKeyRange) v, _ := c.get(k) if k != v { errCh <- fmt.Errorf("key:%q != value:%q", k, v) return } } errCh <- nil return }() } for i := 0; i < benchLoadingCacheConcurrency; i++ { if err := <-errCh; err != nil { b.Fatal(err) } } b.Logf("N=%d, concurrency=%d, reset=%d", b.N, benchLoadingCacheConcurrency, c.reset) } func TestGetRegexp(t *testing.T) { RegexpCache = defaultRegexpCache() assertEqual(t, 0, len(RegexpCache.m)) assertEqual(t, defaultCap, RegexpCache.cap) exp, err := getRegexp("^[0-9]{3,5}$") assertNoErr(t, err) assertTrue(t, exp.MatchString("3141")) assertFalse(t, exp.MatchString("3")) exp, err = getRegexp("[invalid") assertErr(t, err) assertEqual(t, "error parsing regexp: missing closing ]: `[invalid`", err.Error()) assertNil(t, exp) } golang-github-antchfx-xpath-1.3.3/doc_test.go000066400000000000000000000013131473310030300211430ustar00rootroot00000000000000package xpath_test import ( "fmt" "github.com/antchfx/xpath" ) // XPath package example. func Example() { expr, err := xpath.Compile("count(//book)") if err != nil { panic(err) } var root xpath.NodeNavigator // using Evaluate() method val := expr.Evaluate(root) // it returns float64 type fmt.Println(val.(float64)) // using Evaluate() method expr = xpath.MustCompile("//book") val = expr.Evaluate(root) // it returns NodeIterator type. iter := val.(*xpath.NodeIterator) for iter.MoveNext() { fmt.Println(iter.Current().Value()) } // using Select() method iter = expr.Select(root) // it always returns NodeIterator object. for iter.MoveNext() { fmt.Println(iter.Current().Value()) } } golang-github-antchfx-xpath-1.3.3/func.go000066400000000000000000000377011473310030300203040ustar00rootroot00000000000000package xpath import ( "errors" "fmt" "math" "strconv" "strings" "sync" "unicode" ) // Defined an interface of stringBuilder that compatible with // strings.Builder(go 1.10) and bytes.Buffer(< go 1.10) type stringBuilder interface { WriteRune(r rune) (n int, err error) WriteString(s string) (int, error) Reset() Grow(n int) String() string } var builderPool = sync.Pool{New: func() interface{} { return newStringBuilder() }} // The XPath function list. func predicate(q query) func(NodeNavigator) bool { type Predicater interface { Test(NodeNavigator) bool } if p, ok := q.(Predicater); ok { return p.Test } return func(NodeNavigator) bool { return true } } // positionFunc is a XPath Node Set functions position(). func positionFunc() func(query, iterator) interface{} { return func(q query, t iterator) interface{} { var ( count = 1 node = t.Current().Copy() ) test := predicate(q) for node.MoveToPrevious() { if test(node) { count++ } } return float64(count) } } // lastFunc is a XPath Node Set functions last(). func lastFunc() func(query, iterator) interface{} { return func(q query, t iterator) interface{} { var ( count = 0 node = t.Current().Copy() ) test := predicate(q) node.MoveToFirst() for { if test(node) { count++ } if !node.MoveToNext() { break } } return float64(count) } } // countFunc is a XPath Node Set functions count(node-set). func countFunc(arg query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var count = 0 q := functionArgs(arg) test := predicate(q) switch typ := q.Evaluate(t).(type) { case query: for node := typ.Select(t); node != nil; node = typ.Select(t) { if test(node) { count++ } } } return float64(count) } } // sumFunc is a XPath Node Set functions sum(node-set). func sumFunc(arg query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var sum float64 switch typ := functionArgs(arg).Evaluate(t).(type) { case query: for node := typ.Select(t); node != nil; node = typ.Select(t) { if v, err := strconv.ParseFloat(node.Value(), 64); err == nil { sum += v } } case float64: sum = typ case string: v, err := strconv.ParseFloat(typ, 64) if err != nil { panic(errors.New("sum() function argument type must be a node-set or number")) } sum = v } return sum } } func asNumber(t iterator, o interface{}) float64 { switch typ := o.(type) { case query: node := typ.Select(t) if node == nil { return math.NaN() } if v, err := strconv.ParseFloat(node.Value(), 64); err == nil { return v } case float64: return typ case string: v, err := strconv.ParseFloat(typ, 64) if err == nil { return v } } return math.NaN() } // ceilingFunc is a XPath Node Set functions ceiling(node-set). func ceilingFunc(arg query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { val := asNumber(t, functionArgs(arg).Evaluate(t)) // if math.IsNaN(val) { // panic(errors.New("ceiling() function argument type must be a valid number")) // } return math.Ceil(val) } } // floorFunc is a XPath Node Set functions floor(node-set). func floorFunc(arg query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { val := asNumber(t, functionArgs(arg).Evaluate(t)) return math.Floor(val) } } // roundFunc is a XPath Node Set functions round(node-set). func roundFunc(arg query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { val := asNumber(t, functionArgs(arg).Evaluate(t)) //return math.Round(val) return round(val) } } // nameFunc is a XPath functions name([node-set]). func nameFunc(arg query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var v NodeNavigator if arg == nil { v = t.Current() } else { v = arg.Clone().Select(t) if v == nil { return "" } } ns := v.Prefix() if ns == "" { return v.LocalName() } return ns + ":" + v.LocalName() } } // localNameFunc is a XPath functions local-name([node-set]). func localNameFunc(arg query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var v NodeNavigator if arg == nil { v = t.Current() } else { v = arg.Clone().Select(t) if v == nil { return "" } } return v.LocalName() } } // namespaceFunc is a XPath functions namespace-uri([node-set]). func namespaceFunc(arg query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var v NodeNavigator if arg == nil { v = t.Current() } else { // Get the first node in the node-set if specified. v = arg.Clone().Select(t) if v == nil { return "" } } // fix about namespace-uri() bug: https://github.com/antchfx/xmlquery/issues/22 // TODO: In the next version, add NamespaceURL() to the NodeNavigator interface. type namespaceURL interface { NamespaceURL() string } if f, ok := v.(namespaceURL); ok { return f.NamespaceURL() } return v.Prefix() } } func asBool(t iterator, v interface{}) bool { switch v := v.(type) { case nil: return false case *NodeIterator: return v.MoveNext() case bool: return v case float64: return v != 0 case string: return v != "" case query: return v.Select(t) != nil default: panic(fmt.Errorf("unexpected type: %T", v)) } } func asString(t iterator, v interface{}) string { switch v := v.(type) { case nil: return "" case bool: if v { return "true" } return "false" case float64: return strconv.FormatFloat(v, 'g', -1, 64) case string: return v case query: node := v.Select(t) if node == nil { return "" } return node.Value() default: panic(fmt.Errorf("unexpected type: %T", v)) } } // booleanFunc is a XPath functions boolean([node-set]). func booleanFunc(arg1 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { v := functionArgs(arg1).Evaluate(t) return asBool(t, v) } } // numberFunc is a XPath functions number([node-set]). func numberFunc(arg1 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { v := functionArgs(arg1).Evaluate(t) return asNumber(t, v) } } // stringFunc is a XPath functions string([node-set]). func stringFunc(arg1 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { v := functionArgs(arg1).Evaluate(t) return asString(t, v) } } // startwithFunc is a XPath functions starts-with(string, string). func startwithFunc(arg1, arg2 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var ( m, n string ok bool ) switch typ := functionArgs(arg1).Evaluate(t).(type) { case string: m = typ case query: node := typ.Select(t) if node == nil { return false } m = node.Value() default: panic(errors.New("starts-with() function argument type must be string")) } n, ok = functionArgs(arg2).Evaluate(t).(string) if !ok { panic(errors.New("starts-with() function argument type must be string")) } return strings.HasPrefix(m, n) } } // endwithFunc is a XPath functions ends-with(string, string). func endwithFunc(arg1, arg2 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var ( m, n string ok bool ) switch typ := functionArgs(arg1).Evaluate(t).(type) { case string: m = typ case query: node := typ.Select(t) if node == nil { return false } m = node.Value() default: panic(errors.New("ends-with() function argument type must be string")) } n, ok = functionArgs(arg2).Evaluate(t).(string) if !ok { panic(errors.New("ends-with() function argument type must be string")) } return strings.HasSuffix(m, n) } } // containsFunc is a XPath functions contains(string or @attr, string). func containsFunc(arg1, arg2 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var ( m, n string ok bool ) switch typ := functionArgs(arg1).Evaluate(t).(type) { case string: m = typ case query: node := typ.Select(t) if node == nil { return false } m = node.Value() default: panic(errors.New("contains() function argument type must be string")) } n, ok = functionArgs(arg2).Evaluate(t).(string) if !ok { panic(errors.New("contains() function argument type must be string")) } return strings.Contains(m, n) } } // matchesFunc is an XPath function that tests a given string against a regexp pattern. // Note: does not support https://www.w3.org/TR/xpath-functions-31/#func-matches 3rd optional `flags` argument; if // needed, directly put flags in the regexp pattern, such as `(?i)^pattern$` for `i` flag. func matchesFunc(arg1, arg2 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var s string switch typ := functionArgs(arg1).Evaluate(t).(type) { case string: s = typ case query: node := typ.Select(t) if node == nil { return "" } s = node.Value() } var pattern string var ok bool if pattern, ok = functionArgs(arg2).Evaluate(t).(string); !ok { panic(errors.New("matches() function second argument type must be string")) } re, err := getRegexp(pattern) if err != nil { panic(fmt.Errorf("matches() function second argument is not a valid regexp pattern, err: %s", err.Error())) } return re.MatchString(s) } } // normalizespaceFunc is XPath functions normalize-space(string?) func normalizespaceFunc(arg1 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var m string switch typ := functionArgs(arg1).Evaluate(t).(type) { case string: m = typ case query: node := typ.Select(t) if node == nil { return "" } m = node.Value() } var b = builderPool.Get().(stringBuilder) b.Grow(len(m)) runeStr := []rune(strings.TrimSpace(m)) l := len(runeStr) for i := range runeStr { r := runeStr[i] isSpace := unicode.IsSpace(r) if !(isSpace && (i+1 < l && unicode.IsSpace(runeStr[i+1]))) { if isSpace { r = ' ' } b.WriteRune(r) } } result := b.String() b.Reset() builderPool.Put(b) return result } } // substringFunc is XPath functions substring function returns a part of a given string. func substringFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var m string switch typ := functionArgs(arg1).Evaluate(t).(type) { case string: m = typ case query: node := typ.Select(t) if node == nil { return "" } m = node.Value() } var start, length float64 var ok bool if start, ok = functionArgs(arg2).Evaluate(t).(float64); !ok { panic(errors.New("substring() function first argument type must be int")) } else if start < 1 { panic(errors.New("substring() function first argument type must be >= 1")) } start-- if arg3 != nil { if length, ok = functionArgs(arg3).Evaluate(t).(float64); !ok { panic(errors.New("substring() function second argument type must be int")) } } if (len(m) - int(start)) < int(length) { panic(errors.New("substring() function start and length argument out of range")) } if length > 0 { return m[int(start):int(length+start)] } return m[int(start):] } } // substringIndFunc is XPath functions substring-before/substring-after function returns a part of a given string. func substringIndFunc(arg1, arg2 query, after bool) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var str string switch v := functionArgs(arg1).Evaluate(t).(type) { case string: str = v case query: node := v.Select(t) if node == nil { return "" } str = node.Value() } var word string switch v := functionArgs(arg2).Evaluate(t).(type) { case string: word = v case query: node := v.Select(t) if node == nil { return "" } word = node.Value() } if word == "" { return "" } i := strings.Index(str, word) if i < 0 { return "" } if after { return str[i+len(word):] } return str[:i] } } // stringLengthFunc is XPATH string-length( [string] ) function that returns a number // equal to the number of characters in a given string. func stringLengthFunc(arg1 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { switch v := functionArgs(arg1).Evaluate(t).(type) { case string: return float64(len(v)) case query: node := v.Select(t) if node == nil { break } return float64(len(node.Value())) } return float64(0) } } // translateFunc is XPath functions translate() function returns a replaced string. func translateFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { str := asString(t, functionArgs(arg1).Evaluate(t)) src := asString(t, functionArgs(arg2).Evaluate(t)) dst := asString(t, functionArgs(arg3).Evaluate(t)) replace := make([]string, 0, len(src)) for i, s := range src { d := "" if i < len(dst) { d = string(dst[i]) } replace = append(replace, string(s), d) } return strings.NewReplacer(replace...).Replace(str) } } // replaceFunc is XPath functions replace() function returns a replaced string. func replaceFunc(arg1, arg2, arg3 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { str := asString(t, functionArgs(arg1).Evaluate(t)) src := asString(t, functionArgs(arg2).Evaluate(t)) dst := asString(t, functionArgs(arg3).Evaluate(t)) return strings.Replace(str, src, dst, -1) } } // notFunc is XPATH functions not(expression) function operation. func notFunc(arg1 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { switch v := functionArgs(arg1).Evaluate(t).(type) { case bool: return !v case query: node := v.Select(t) return node == nil default: return false } } } // concatFunc is the concat function concatenates two or more // strings and returns the resulting string. // concat( string1 , string2 [, stringn]* ) func concatFunc(args ...query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { b := builderPool.Get().(stringBuilder) for _, v := range args { v = functionArgs(v) switch v := v.Evaluate(t).(type) { case string: b.WriteString(v) case query: node := v.Select(t) if node != nil { b.WriteString(node.Value()) } } } result := b.String() b.Reset() builderPool.Put(b) return result } } // https://github.com/antchfx/xpath/issues/43 func functionArgs(q query) query { if _, ok := q.(*functionQuery); ok { return q } return q.Clone() } func reverseFunc(q query, t iterator) func() NodeNavigator { var list []NodeNavigator for { node := q.Select(t) if node == nil { break } list = append(list, node.Copy()) } i := len(list) return func() NodeNavigator { if i <= 0 { return nil } i-- node := list[i] return node } } // string-join is a XPath Node Set functions string-join(node-set, separator). func stringJoinFunc(q, arg1 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { var separator string switch v := functionArgs(arg1).Evaluate(t).(type) { case string: separator = v case query: node := v.Select(t) if node != nil { separator = node.Value() } } q = functionArgs(q) test := predicate(q) var parts []string switch v := q.Evaluate(t).(type) { case string: return v case query: for node := v.Select(t); node != nil; node = v.Select(t) { if test(node) { parts = append(parts, node.Value()) } } } return strings.Join(parts, separator) } } // lower-case is XPATH function that converts a string to lower case. func lowerCaseFunc(arg1 query) func(query, iterator) interface{} { return func(_ query, t iterator) interface{} { v := functionArgs(arg1).Evaluate(t) return strings.ToLower(asString(t, v)) } } golang-github-antchfx-xpath-1.3.3/func_go110.go000066400000000000000000000002771473310030300212110ustar00rootroot00000000000000// +build go1.10 package xpath import ( "math" "strings" ) func round(f float64) int { return int(math.Round(f)) } func newStringBuilder() stringBuilder { return &strings.Builder{} } golang-github-antchfx-xpath-1.3.3/func_pre_go110.go000066400000000000000000000005661473310030300220600ustar00rootroot00000000000000// +build !go1.10 package xpath import ( "bytes" "math" ) // math.Round() is supported by Go 1.10+, // This method just compatible for version <1.10. // https://github.com/golang/go/issues/20100 func round(f float64) int { if math.Abs(f) < 0.5 { return 0 } return int(f + math.Copysign(0.5, f)) } func newStringBuilder() stringBuilder { return &bytes.Buffer{} } golang-github-antchfx-xpath-1.3.3/go.mod000066400000000000000000000000511473310030300201140ustar00rootroot00000000000000module github.com/antchfx/xpath go 1.14 golang-github-antchfx-xpath-1.3.3/operator.go000066400000000000000000000132601473310030300211760ustar00rootroot00000000000000package xpath import ( "strconv" ) // The XPath number operator function list. type logical func(iterator, string, interface{}, interface{}) bool var logicalFuncs = [][]logical{ {cmpBooleanBoolean, nil, nil, nil}, {nil, cmpNumericNumeric, cmpNumericString, cmpNumericNodeSet}, {nil, cmpStringNumeric, cmpStringString, cmpStringNodeSet}, {nil, cmpNodeSetNumeric, cmpNodeSetString, cmpNodeSetNodeSet}, } // number vs number func cmpNumberNumberF(op string, a, b float64) bool { switch op { case "=": return a == b case ">": return a > b case "<": return a < b case ">=": return a >= b case "<=": return a <= b case "!=": return a != b } return false } // string vs string func cmpStringStringF(op string, a, b string) bool { switch op { case "=": return a == b case ">": return a > b case "<": return a < b case ">=": return a >= b case "<=": return a <= b case "!=": return a != b } return false } func cmpBooleanBooleanF(op string, a, b bool) bool { switch op { case "or": return a || b case "and": return a && b } return false } func cmpNumericNumeric(t iterator, op string, m, n interface{}) bool { a := m.(float64) b := n.(float64) return cmpNumberNumberF(op, a, b) } func cmpNumericString(t iterator, op string, m, n interface{}) bool { a := m.(float64) b := n.(string) num, err := strconv.ParseFloat(b, 64) if err != nil { panic(err) } return cmpNumberNumberF(op, a, num) } func cmpNumericNodeSet(t iterator, op string, m, n interface{}) bool { a := m.(float64) b := n.(query) for { node := b.Select(t) if node == nil { break } num, err := strconv.ParseFloat(node.Value(), 64) if err != nil { panic(err) } if cmpNumberNumberF(op, a, num) { return true } } return false } func cmpNodeSetNumeric(t iterator, op string, m, n interface{}) bool { a := m.(query) b := n.(float64) for { node := a.Select(t) if node == nil { break } num, err := strconv.ParseFloat(node.Value(), 64) if err != nil { panic(err) } if cmpNumberNumberF(op, num, b) { return true } } return false } func cmpNodeSetString(t iterator, op string, m, n interface{}) bool { a := m.(query) b := n.(string) for { node := a.Select(t) if node == nil { break } if cmpStringStringF(op, b, node.Value()) { return true } } return false } func cmpNodeSetNodeSet(t iterator, op string, m, n interface{}) bool { a := m.(query) b := n.(query) for { x := a.Select(t) if x == nil { return false } y := b.Select(t) if y == nil { return false } for { if cmpStringStringF(op, x.Value(), y.Value()) { return true } if y = b.Select(t); y == nil { break } } // reset b.Evaluate(t) } } func cmpStringNumeric(t iterator, op string, m, n interface{}) bool { a := m.(string) b := n.(float64) num, err := strconv.ParseFloat(a, 64) if err != nil { panic(err) } return cmpNumberNumberF(op, b, num) } func cmpStringString(t iterator, op string, m, n interface{}) bool { a := m.(string) b := n.(string) return cmpStringStringF(op, a, b) } func cmpStringNodeSet(t iterator, op string, m, n interface{}) bool { a := m.(string) b := n.(query) for { node := b.Select(t) if node == nil { break } if cmpStringStringF(op, a, node.Value()) { return true } } return false } func cmpBooleanBoolean(t iterator, op string, m, n interface{}) bool { a := m.(bool) b := n.(bool) return cmpBooleanBooleanF(op, a, b) } // eqFunc is an `=` operator. func eqFunc(t iterator, m, n interface{}) interface{} { t1 := getXPathType(m) t2 := getXPathType(n) return logicalFuncs[t1][t2](t, "=", m, n) } // gtFunc is an `>` operator. func gtFunc(t iterator, m, n interface{}) interface{} { t1 := getXPathType(m) t2 := getXPathType(n) return logicalFuncs[t1][t2](t, ">", m, n) } // geFunc is an `>=` operator. func geFunc(t iterator, m, n interface{}) interface{} { t1 := getXPathType(m) t2 := getXPathType(n) return logicalFuncs[t1][t2](t, ">=", m, n) } // ltFunc is an `<` operator. func ltFunc(t iterator, m, n interface{}) interface{} { t1 := getXPathType(m) t2 := getXPathType(n) return logicalFuncs[t1][t2](t, "<", m, n) } // leFunc is an `<=` operator. func leFunc(t iterator, m, n interface{}) interface{} { t1 := getXPathType(m) t2 := getXPathType(n) return logicalFuncs[t1][t2](t, "<=", m, n) } // neFunc is an `!=` operator. func neFunc(t iterator, m, n interface{}) interface{} { t1 := getXPathType(m) t2 := getXPathType(n) return logicalFuncs[t1][t2](t, "!=", m, n) } // orFunc is an `or` operator. var orFunc = func(t iterator, m, n interface{}) interface{} { t1 := getXPathType(m) t2 := getXPathType(n) return logicalFuncs[t1][t2](t, "or", m, n) } func numericExpr(t iterator, m, n interface{}, cb func(float64, float64) float64) float64 { a := asNumber(t, m) b := asNumber(t, n) return cb(a, b) } // plusFunc is an `+` operator. var plusFunc = func(t iterator, m, n interface{}) interface{} { return numericExpr(t, m, n, func(a, b float64) float64 { return a + b }) } // minusFunc is an `-` operator. var minusFunc = func(t iterator, m, n interface{}) interface{} { return numericExpr(t, m, n, func(a, b float64) float64 { return a - b }) } // mulFunc is an `*` operator. var mulFunc = func(t iterator, m, n interface{}) interface{} { return numericExpr(t, m, n, func(a, b float64) float64 { return a * b }) } // divFunc is an `DIV` operator. var divFunc = func(t iterator, m, n interface{}) interface{} { return numericExpr(t, m, n, func(a, b float64) float64 { return a / b }) } // modFunc is an 'MOD' operator. var modFunc = func(t iterator, m, n interface{}) interface{} { return numericExpr(t, m, n, func(a, b float64) float64 { return float64(int(a) % int(b)) }) } golang-github-antchfx-xpath-1.3.3/parse.go000066400000000000000000000661011473310030300204570ustar00rootroot00000000000000package xpath import ( "bytes" "errors" "fmt" "strconv" "unicode" "unicode/utf8" ) // A XPath expression token type. type itemType int const ( itemComma itemType = iota // ',' itemSlash // '/' itemAt // '@' itemDot // '.' itemLParens // '(' itemRParens // ')' itemLBracket // '[' itemRBracket // ']' itemStar // '*' itemPlus // '+' itemMinus // '-' itemEq // '=' itemLt // '<' itemGt // '>' itemBang // '!' itemDollar // '$' itemApos // '\'' itemQuote // '"' itemUnion // '|' itemNe // '!=' itemLe // '<=' itemGe // '>=' itemAnd // '&&' itemOr // '||' itemDotDot // '..' itemSlashSlash // '//' itemName // XML Name itemString // Quoted string constant itemNumber // Number constant itemAxe // Axe (like child::) itemEOF // END ) // A node is an XPath node in the parse tree. type node interface { Type() nodeType } // nodeType identifies the type of a parse tree node. type nodeType int func (t nodeType) Type() nodeType { return t } const ( nodeRoot nodeType = iota nodeAxis nodeFilter nodeFunction nodeOperator nodeVariable nodeConstantOperand nodeGroup ) type parser struct { r *scanner d int namespaces map[string]string } // newOperatorNode returns new operator node OperatorNode. func newOperatorNode(op string, left, right node) node { return &operatorNode{nodeType: nodeOperator, Op: op, Left: left, Right: right} } // newOperand returns new constant operand node OperandNode. func newOperandNode(v interface{}) node { return &operandNode{nodeType: nodeConstantOperand, Val: v} } // newAxisNode returns new axis node AxisNode. func newAxisNode(axisType string, typeTest NodeType, localName, prefix, prop string, n node, opts ...func(p *axisNode)) node { a := axisNode{ nodeType: nodeAxis, typeTest: typeTest, LocalName: localName, Prefix: prefix, AxisType: axisType, Prop: prop, Input: n, } for _, o := range opts { o(&a) } return &a } // newVariableNode returns new variable node VariableNode. func newVariableNode(prefix, name string) node { return &variableNode{nodeType: nodeVariable, Name: name, Prefix: prefix} } // newFilterNode returns a new filter node FilterNode. func newFilterNode(n, m node) node { return &filterNode{nodeType: nodeFilter, Input: n, Condition: m} } func newGroupNode(n node) node { return &groupNode{nodeType: nodeGroup, Input: n} } // newRootNode returns a root node. func newRootNode(s string) node { return &rootNode{nodeType: nodeRoot, slash: s} } // newFunctionNode returns function call node. func newFunctionNode(name, prefix string, args []node) node { return &functionNode{nodeType: nodeFunction, Prefix: prefix, FuncName: name, Args: args} } // testOp reports whether current item name is an operand op. func testOp(r *scanner, op string) bool { return r.typ == itemName && r.prefix == "" && r.name == op } func isPrimaryExpr(r *scanner) bool { switch r.typ { case itemString, itemNumber, itemDollar, itemLParens: return true case itemName: return r.canBeFunc && !isNodeType(r) } return false } func isNodeType(r *scanner) bool { switch r.name { case "node", "text", "processing-instruction", "comment": return r.prefix == "" } return false } func isStep(item itemType) bool { switch item { case itemDot, itemDotDot, itemAt, itemAxe, itemStar, itemName: return true } return false } func checkItem(r *scanner, typ itemType) { if r.typ != typ { panic(fmt.Sprintf("%s has an invalid token", r.text)) } } // parseExpression parsing the expression with input node n. func (p *parser) parseExpression(n node) node { if p.d = p.d + 1; p.d > 200 { panic("the xpath query is too complex(depth > 200)") } n = p.parseOrExpr(n) p.d-- return n } // next scanning next item on forward. func (p *parser) next() bool { return p.r.nextItem() } func (p *parser) skipItem(typ itemType) { checkItem(p.r, typ) p.next() } // OrExpr ::= AndExpr | OrExpr 'or' AndExpr func (p *parser) parseOrExpr(n node) node { opnd := p.parseAndExpr(n) for { if !testOp(p.r, "or") { break } p.next() opnd = newOperatorNode("or", opnd, p.parseAndExpr(n)) } return opnd } // AndExpr ::= EqualityExpr | AndExpr 'and' EqualityExpr func (p *parser) parseAndExpr(n node) node { opnd := p.parseEqualityExpr(n) for { if !testOp(p.r, "and") { break } p.next() opnd = newOperatorNode("and", opnd, p.parseEqualityExpr(n)) } return opnd } // EqualityExpr ::= RelationalExpr | EqualityExpr '=' RelationalExpr | EqualityExpr '!=' RelationalExpr func (p *parser) parseEqualityExpr(n node) node { opnd := p.parseRelationalExpr(n) Loop: for { var op string switch p.r.typ { case itemEq: op = "=" case itemNe: op = "!=" default: break Loop } p.next() opnd = newOperatorNode(op, opnd, p.parseRelationalExpr(n)) } return opnd } // RelationalExpr ::= AdditiveExpr | RelationalExpr '<' AdditiveExpr | RelationalExpr '>' AdditiveExpr // // | RelationalExpr '<=' AdditiveExpr // | RelationalExpr '>=' AdditiveExpr func (p *parser) parseRelationalExpr(n node) node { opnd := p.parseAdditiveExpr(n) Loop: for { var op string switch p.r.typ { case itemLt: op = "<" case itemGt: op = ">" case itemLe: op = "<=" case itemGe: op = ">=" default: break Loop } p.next() opnd = newOperatorNode(op, opnd, p.parseAdditiveExpr(n)) } return opnd } // AdditiveExpr ::= MultiplicativeExpr | AdditiveExpr '+' MultiplicativeExpr | AdditiveExpr '-' MultiplicativeExpr func (p *parser) parseAdditiveExpr(n node) node { opnd := p.parseMultiplicativeExpr(n) Loop: for { var op string switch p.r.typ { case itemPlus: op = "+" case itemMinus: op = "-" default: break Loop } p.next() opnd = newOperatorNode(op, opnd, p.parseMultiplicativeExpr(n)) } return opnd } // MultiplicativeExpr ::= UnaryExpr | MultiplicativeExpr MultiplyOperator(*) UnaryExpr // // | MultiplicativeExpr 'div' UnaryExpr | MultiplicativeExpr 'mod' UnaryExpr func (p *parser) parseMultiplicativeExpr(n node) node { opnd := p.parseUnaryExpr(n) Loop: for { var op string if p.r.typ == itemStar { op = "*" } else if testOp(p.r, "div") || testOp(p.r, "mod") { op = p.r.name } else { break Loop } p.next() opnd = newOperatorNode(op, opnd, p.parseUnaryExpr(n)) } return opnd } // UnaryExpr ::= UnionExpr | '-' UnaryExpr func (p *parser) parseUnaryExpr(n node) node { minus := false // ignore '-' sequence for p.r.typ == itemMinus { p.next() minus = !minus } opnd := p.parseUnionExpr(n) if minus { opnd = newOperatorNode("*", opnd, newOperandNode(float64(-1))) } return opnd } // UnionExpr ::= PathExpr | UnionExpr '|' PathExpr func (p *parser) parseUnionExpr(n node) node { opnd := p.parsePathExpr(n) Loop: for { if p.r.typ != itemUnion { break Loop } p.next() opnd2 := p.parsePathExpr(n) // Checking the node type that must be is node set type? opnd = newOperatorNode("|", opnd, opnd2) } return opnd } // PathExpr ::= LocationPath | FilterExpr | FilterExpr '/' RelativeLocationPath | FilterExpr '//' RelativeLocationPath func (p *parser) parsePathExpr(n node) node { var opnd node if isPrimaryExpr(p.r) { opnd = p.parseFilterExpr(n) switch p.r.typ { case itemSlash: p.next() opnd = p.parseRelativeLocationPath(opnd) case itemSlashSlash: p.next() opnd = p.parseRelativeLocationPath(newAxisNode("descendant-or-self", allNode, "", "", "", opnd)) } } else { opnd = p.parseLocationPath(nil) } return opnd } // FilterExpr ::= PrimaryExpr | FilterExpr Predicate func (p *parser) parseFilterExpr(n node) node { opnd := p.parsePrimaryExpr(n) if p.r.typ == itemLBracket { opnd = newFilterNode(opnd, p.parsePredicate(opnd)) } return opnd } // Predicate ::= '[' PredicateExpr ']' func (p *parser) parsePredicate(n node) node { p.skipItem(itemLBracket) opnd := p.parseExpression(n) p.skipItem(itemRBracket) return opnd } // LocationPath ::= RelativeLocationPath | AbsoluteLocationPath func (p *parser) parseLocationPath(n node) (opnd node) { switch p.r.typ { case itemSlash: p.next() opnd = newRootNode("/") if isStep(p.r.typ) { opnd = p.parseRelativeLocationPath(opnd) // ?? child:: or self ?? } case itemSlashSlash: p.next() opnd = newRootNode("//") opnd = p.parseRelativeLocationPath(newAxisNode("descendant-or-self", allNode, "", "", "", opnd)) default: opnd = p.parseRelativeLocationPath(n) } return opnd } // RelativeLocationPath ::= Step | RelativeLocationPath '/' Step | AbbreviatedRelativeLocationPath func (p *parser) parseRelativeLocationPath(n node) node { opnd := n Loop: for { opnd = p.parseStep(opnd) switch p.r.typ { case itemSlashSlash: p.next() opnd = newAxisNode("descendant-or-self", allNode, "", "", "", opnd) case itemSlash: p.next() default: break Loop } } return opnd } // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep func (p *parser) parseStep(n node) (opnd node) { if p.r.typ == itemDot || p.r.typ == itemDotDot { if p.r.typ == itemDot { opnd = newAxisNode("self", allNode, "", "", "", n) } else { opnd = newAxisNode("parent", allNode, "", "", "", n) } p.next() if p.r.typ != itemLBracket { return opnd } } else { axisType := "child" // default axes value. switch p.r.typ { case itemAt: axisType = "attribute" p.next() case itemAxe: axisType = p.r.name p.next() case itemLParens: return p.parseSequence(n) } matchType := ElementNode if axisType == "attribute" { matchType = AttributeNode } opnd = p.parseNodeTest(n, axisType, matchType) } for p.r.typ == itemLBracket { opnd = newFilterNode(opnd, p.parsePredicate(opnd)) } return opnd } // Expr ::= '(' Step ("," Step)* ')' func (p *parser) parseSequence(n node) (opnd node) { p.skipItem(itemLParens) opnd = p.parseStep(n) for { if p.r.typ != itemComma { break } p.next() opnd2 := p.parseStep(n) opnd = newOperatorNode("|", opnd, opnd2) } p.skipItem(itemRParens) return opnd } // NodeTest ::= NameTest | nodeType '(' ')' | 'processing-instruction' '(' Literal ')' func (p *parser) parseNodeTest(n node, axeTyp string, matchType NodeType) (opnd node) { switch p.r.typ { case itemName: if p.r.canBeFunc && isNodeType(p.r) { var prop string switch p.r.name { case "comment", "text", "processing-instruction", "node": prop = p.r.name } var name string p.next() p.skipItem(itemLParens) if prop == "processing-instruction" && p.r.typ != itemRParens { checkItem(p.r, itemString) name = p.r.strval p.next() } p.skipItem(itemRParens) switch prop { case "comment": matchType = CommentNode case "text": matchType = TextNode case "processing-instruction": case "node": matchType = allNode default: matchType = RootNode } opnd = newAxisNode(axeTyp, matchType, name, "", prop, n) } else { prefix := p.r.prefix name := p.r.name p.next() if p.r.name == "*" { name = "" } opnd = newAxisNode(axeTyp, matchType, name, prefix, "", n, func(a *axisNode) { if prefix != "" && p.namespaces != nil { if ns, ok := p.namespaces[prefix]; ok { a.hasNamespaceURI = true a.namespaceURI = ns } else { panic(fmt.Sprintf("prefix %s not defined.", prefix)) } } }) } case itemStar: opnd = newAxisNode(axeTyp, matchType, "", "", "", n) p.next() default: panic("expression must evaluate to a node-set") } return opnd } // PrimaryExpr ::= VariableReference | '(' Expr ')' | Literal | Number | FunctionCall func (p *parser) parsePrimaryExpr(n node) (opnd node) { switch p.r.typ { case itemString: opnd = newOperandNode(p.r.strval) p.next() case itemNumber: opnd = newOperandNode(p.r.numval) p.next() case itemDollar: p.next() checkItem(p.r, itemName) opnd = newVariableNode(p.r.prefix, p.r.name) p.next() case itemLParens: p.next() opnd = p.parseExpression(n) if opnd.Type() != nodeConstantOperand { opnd = newGroupNode(opnd) } p.skipItem(itemRParens) case itemName: if p.r.canBeFunc && !isNodeType(p.r) { opnd = p.parseMethod(nil) } } return opnd } // FunctionCall ::= FunctionName '(' ( Argument ( ',' Argument )* )? ')' func (p *parser) parseMethod(n node) node { var args []node name := p.r.name prefix := p.r.prefix p.skipItem(itemName) p.skipItem(itemLParens) if p.r.typ != itemRParens { for { args = append(args, p.parseExpression(n)) if p.r.typ == itemRParens { break } p.skipItem(itemComma) } } p.skipItem(itemRParens) return newFunctionNode(name, prefix, args) } // Parse parsing the XPath express string expr and returns a tree node. func parse(expr string, namespaces map[string]string) node { r := &scanner{text: expr} r.nextChar() r.nextItem() p := &parser{r: r, namespaces: namespaces} return p.parseExpression(nil) } // rootNode holds a top-level node of tree. type rootNode struct { nodeType slash string } func (r *rootNode) String() string { return r.slash } // operatorNode holds two Nodes operator. type operatorNode struct { nodeType Op string Left, Right node } func (o *operatorNode) String() string { return fmt.Sprintf("%v%s%v", o.Left, o.Op, o.Right) } // axisNode holds a location step. type axisNode struct { nodeType Input node Prop string // node-test name.[comment|text|processing-instruction|node] AxisType string // name of the axis.[attribute|ancestor|child|....] LocalName string // local part name of node. Prefix string // prefix name of node. namespaceURI string // namespace URI of node hasNamespaceURI bool // if namespace URI is set (can be "") typeTest NodeType } func (a *axisNode) String() string { var b bytes.Buffer if a.AxisType != "" { b.Write([]byte(a.AxisType + "::")) } if a.Prefix != "" { b.Write([]byte(a.Prefix + ":")) } b.Write([]byte(a.LocalName)) if a.Prop != "" { b.Write([]byte("/" + a.Prop + "()")) } return b.String() } // operandNode holds a constant operand. type operandNode struct { nodeType Val interface{} } func (o *operandNode) String() string { return fmt.Sprintf("%v", o.Val) } // groupNode holds a set of node expression type groupNode struct { nodeType Input node } func (g *groupNode) String() string { return fmt.Sprintf("%s", g.Input) } // filterNode holds a condition filter. type filterNode struct { nodeType Input, Condition node } func (f *filterNode) String() string { return fmt.Sprintf("%s[%s]", f.Input, f.Condition) } // variableNode holds a variable. type variableNode struct { nodeType Name, Prefix string } func (v *variableNode) String() string { if v.Prefix == "" { return v.Name } return fmt.Sprintf("%s:%s", v.Prefix, v.Name) } // functionNode holds a function call. type functionNode struct { nodeType Args []node Prefix string FuncName string // function name } func (f *functionNode) String() string { var b bytes.Buffer // fun(arg1, ..., argn) b.Write([]byte(f.FuncName)) b.Write([]byte("(")) for i, arg := range f.Args { if i > 0 { b.Write([]byte(",")) } b.Write([]byte(fmt.Sprintf("%s", arg))) } b.Write([]byte(")")) return b.String() } type scanner struct { text, name, prefix string pos int curr rune currSize int typ itemType strval string // text value at current pos numval float64 // number value at current pos canBeFunc bool } func (s *scanner) nextChar() bool { if s.pos >= len(s.text) { s.curr = rune(0) s.currSize = 1 return false } r, size := rune(s.text[s.pos]), 1 if r >= 0x80 { // handle multi-byte runes r, size = utf8.DecodeRuneInString(s.text[s.pos:]) } s.curr = r s.currSize = size s.pos += size return true } func (s *scanner) nextItem() bool { s.skipSpace() switch s.curr { case 0: s.typ = itemEOF return false case ',', '@', '(', ')', '|', '*', '[', ']', '+', '-', '=', '#', '$': s.typ = asItemType(s.curr) s.nextChar() case '<': s.typ = itemLt s.nextChar() if s.curr == '=' { s.typ = itemLe s.nextChar() } case '>': s.typ = itemGt s.nextChar() if s.curr == '=' { s.typ = itemGe s.nextChar() } case '!': s.typ = itemBang s.nextChar() if s.curr == '=' { s.typ = itemNe s.nextChar() } case '.': s.typ = itemDot s.nextChar() if s.curr == '.' { s.typ = itemDotDot s.nextChar() } else if isDigit(s.curr) { s.typ = itemNumber s.numval = s.scanFraction() } case '/': s.typ = itemSlash s.nextChar() if s.curr == '/' { s.typ = itemSlashSlash s.nextChar() } case '"', '\'': s.typ = itemString s.strval = s.scanString() default: if isDigit(s.curr) { s.typ = itemNumber s.numval = s.scanNumber() } else if isName(s.curr) { s.typ = itemName s.name = s.scanName() s.prefix = "" // "foo:bar" is one itemem not three because it doesn't allow spaces in between // We should distinct it from "foo::" and need process "foo ::" as well if s.curr == ':' { s.nextChar() // can be "foo:bar" or "foo::" if s.curr == ':' { // "foo::" s.nextChar() s.typ = itemAxe } else { // "foo:*", "foo:bar" or "foo: " s.prefix = s.name if s.curr == '*' { s.nextChar() s.name = "*" } else if isName(s.curr) { s.name = s.scanName() } else { panic(fmt.Sprintf("%s has an invalid qualified name.", s.text)) } } } else { s.skipSpace() if s.curr == ':' { s.nextChar() // it can be "foo ::" or just "foo :" if s.curr == ':' { s.nextChar() s.typ = itemAxe } else { panic(fmt.Sprintf("%s has an invalid qualified name.", s.text)) } } } s.skipSpace() s.canBeFunc = s.curr == '(' } else { panic(fmt.Sprintf("%s has an invalid token.", s.text)) } } return true } func (s *scanner) skipSpace() { Loop: for { if !unicode.IsSpace(s.curr) || !s.nextChar() { break Loop } } } func (s *scanner) scanFraction() float64 { var ( i = s.pos - 2 c = 1 // '.' ) for isDigit(s.curr) { s.nextChar() c++ } v, err := strconv.ParseFloat(s.text[i:i+c], 64) if err != nil { panic(fmt.Errorf("xpath: scanFraction parse float got error: %v", err)) } return v } func (s *scanner) scanNumber() float64 { var ( c int i = s.pos - 1 ) for isDigit(s.curr) { s.nextChar() c++ } if s.curr == '.' { s.nextChar() c++ for isDigit(s.curr) { s.nextChar() c++ } } v, err := strconv.ParseFloat(s.text[i:i+c], 64) if err != nil { panic(fmt.Errorf("xpath: scanNumber parse float got error: %v", err)) } return v } func (s *scanner) scanString() string { var ( end = s.curr ) s.nextChar() i := s.pos - s.currSize c := s.currSize for s.curr != end { if !s.nextChar() { panic(errors.New("xpath: scanString got unclosed string")) } c += s.currSize } c -= 1 s.nextChar() return s.text[i : i+c] } func (s *scanner) scanName() string { var ( c = s.currSize - 1 i = s.pos - s.currSize ) // Detect current rune size for isName(s.curr) { if !s.nextChar() { c += s.currSize break } c += s.currSize } return s.text[i : i+c] } func isName(r rune) bool { return string(r) != ":" && string(r) != "/" && (unicode.Is(first, r) || unicode.Is(second, r) || string(r) == "*") } func isDigit(r rune) bool { return unicode.IsDigit(r) } func asItemType(r rune) itemType { switch r { case ',': return itemComma case '@': return itemAt case '(': return itemLParens case ')': return itemRParens case '|': return itemUnion case '*': return itemStar case '[': return itemLBracket case ']': return itemRBracket case '+': return itemPlus case '-': return itemMinus case '=': return itemEq case '$': return itemDollar } panic(fmt.Errorf("unknown item: %v", r)) } 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-antchfx-xpath-1.3.3/query.go000066400000000000000000000706751473310030300205250ustar00rootroot00000000000000package xpath import ( "bytes" "fmt" "hash/fnv" "reflect" ) // The return type of the XPath expression. type resultType int var xpathResultType = struct { Boolean resultType // A numeric value Number resultType String resultType // A node collection. NodeSet resultType // Any of the XPath node types. Any resultType }{ Boolean: 0, Number: 1, String: 2, NodeSet: 3, Any: 4, } type queryProp int var queryProps = struct { None queryProp Position queryProp Count queryProp Cached queryProp Reverse queryProp Merge queryProp }{ None: 0, Position: 1, Count: 2, Cached: 4, Reverse: 8, Merge: 16, } type iterator interface { Current() NodeNavigator } // An XPath query interface. type query interface { // Select traversing iterator returns a query matched node NodeNavigator. Select(iterator) NodeNavigator // Evaluate evaluates query and returns values of the current query. Evaluate(iterator) interface{} Clone() query // ValueType returns the value type of the current query. ValueType() resultType Properties() queryProp } // nopQuery is an empty query that always return nil for any query. type nopQuery struct{} func (nopQuery) Select(iterator) NodeNavigator { return nil } func (nopQuery) Evaluate(iterator) interface{} { return nil } func (nopQuery) Clone() query { return nopQuery{} } func (nopQuery) ValueType() resultType { return xpathResultType.NodeSet } func (nopQuery) Properties() queryProp { return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached } // contextQuery is returns current node on the iterator object query. type contextQuery struct { count int } func (c *contextQuery) Select(t iterator) NodeNavigator { if c.count > 0 { return nil } c.count++ return t.Current().Copy() } func (c *contextQuery) Evaluate(iterator) interface{} { c.count = 0 return c } func (c *contextQuery) Clone() query { return &contextQuery{} } func (c *contextQuery) ValueType() resultType { return xpathResultType.NodeSet } func (c *contextQuery) Properties() queryProp { return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached } type absoluteQuery struct { count int } func (a *absoluteQuery) Select(t iterator) (n NodeNavigator) { if a.count > 0 { return } a.count++ n = t.Current().Copy() n.MoveToRoot() return } func (a *absoluteQuery) Evaluate(t iterator) interface{} { a.count = 0 return a } func (a *absoluteQuery) Clone() query { return &absoluteQuery{} } func (a *absoluteQuery) ValueType() resultType { return xpathResultType.NodeSet } func (a *absoluteQuery) Properties() queryProp { return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached } // ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*) type ancestorQuery struct { name string iterator func() NodeNavigator table map[uint64]bool Self bool Input query Predicate func(NodeNavigator) bool } func (a *ancestorQuery) Select(t iterator) NodeNavigator { if a.table == nil { a.table = make(map[uint64]bool) } for { if a.iterator == nil { node := a.Input.Select(t) if node == nil { return nil } first := true node = node.Copy() a.iterator = func() NodeNavigator { if first { first = false if a.Self && a.Predicate(node) { return node } } for node.MoveToParent() { if a.Predicate(node) { return node } } return nil } } for node := a.iterator(); node != nil; node = a.iterator() { node_id := getHashCode(node.Copy()) if _, ok := a.table[node_id]; !ok { a.table[node_id] = true return node } } a.iterator = nil } } func (a *ancestorQuery) Evaluate(t iterator) interface{} { a.Input.Evaluate(t) a.iterator = nil return a } func (a *ancestorQuery) Test(n NodeNavigator) bool { return a.Predicate(n) } func (a *ancestorQuery) Clone() query { return &ancestorQuery{name: a.name, Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate} } func (a *ancestorQuery) ValueType() resultType { return xpathResultType.NodeSet } func (a *ancestorQuery) Properties() queryProp { return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge | queryProps.Reverse } // attributeQuery is an XPath attribute node query.(@*) type attributeQuery struct { name string iterator func() NodeNavigator Input query Predicate func(NodeNavigator) bool } func (a *attributeQuery) Select(t iterator) NodeNavigator { for { if a.iterator == nil { node := a.Input.Select(t) if node == nil { return nil } node = node.Copy() a.iterator = func() NodeNavigator { for { onAttr := node.MoveToNextAttribute() if !onAttr { return nil } if a.Predicate(node) { return node } } } } if node := a.iterator(); node != nil { return node } a.iterator = nil } } func (a *attributeQuery) Evaluate(t iterator) interface{} { a.Input.Evaluate(t) a.iterator = nil return a } func (a *attributeQuery) Test(n NodeNavigator) bool { return a.Predicate(n) } func (a *attributeQuery) Clone() query { return &attributeQuery{name: a.name, Input: a.Input.Clone(), Predicate: a.Predicate} } func (a *attributeQuery) ValueType() resultType { return xpathResultType.NodeSet } func (a *attributeQuery) Properties() queryProp { return queryProps.Merge } // childQuery is an XPath child node query.(child::*) type childQuery struct { name string posit int iterator func() NodeNavigator Input query Predicate func(NodeNavigator) bool } func (c *childQuery) Select(t iterator) NodeNavigator { for { if c.iterator == nil { c.posit = 0 node := c.Input.Select(t) if node == nil { return nil } node = node.Copy() first := true c.iterator = func() NodeNavigator { for { if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) { return nil } first = false if c.Predicate(node) { return node } } } } if node := c.iterator(); node != nil { c.posit++ return node } c.iterator = nil } } func (c *childQuery) Evaluate(t iterator) interface{} { c.Input.Evaluate(t) c.iterator = nil return c } func (c *childQuery) Test(n NodeNavigator) bool { return c.Predicate(n) } func (c *childQuery) Clone() query { return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate} } func (c *childQuery) ValueType() resultType { return xpathResultType.NodeSet } func (c *childQuery) Properties() queryProp { return queryProps.Merge } // position returns a position of current NodeNavigator. func (c *childQuery) position() int { return c.posit } type cachedChildQuery struct { name string posit int iterator func() NodeNavigator Input query Predicate func(NodeNavigator) bool } func (c *cachedChildQuery) Select(t iterator) NodeNavigator { for { if c.iterator == nil { c.posit = 0 node := c.Input.Select(t) if node == nil { return nil } node = node.Copy() first := true c.iterator = func() NodeNavigator { for { if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) { return nil } first = false if c.Predicate(node) { return node } } } } if node := c.iterator(); node != nil { c.posit++ return node } c.iterator = nil } } func (c *cachedChildQuery) Evaluate(t iterator) interface{} { c.Input.Evaluate(t) c.iterator = nil return c } func (c *cachedChildQuery) position() int { return c.posit } func (c *cachedChildQuery) Test(n NodeNavigator) bool { return c.Predicate(n) } func (c *cachedChildQuery) Clone() query { return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate} } func (c *cachedChildQuery) ValueType() resultType { return xpathResultType.NodeSet } func (c *cachedChildQuery) Properties() queryProp { return queryProps.Merge } // descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*) type descendantQuery struct { name string iterator func() NodeNavigator posit int level int Self bool Input query Predicate func(NodeNavigator) bool } func (d *descendantQuery) Select(t iterator) NodeNavigator { for { if d.iterator == nil { d.posit = 0 node := d.Input.Select(t) if node == nil { return nil } node = node.Copy() d.level = 0 first := true d.iterator = func() NodeNavigator { if first { first = false if d.Self && d.Predicate(node) { return node } } for { if node.MoveToChild() { d.level = d.level + 1 } else { for { if d.level == 0 { return nil } if node.MoveToNext() { break } node.MoveToParent() d.level = d.level - 1 } } if d.Predicate(node) { return node } } } } if node := d.iterator(); node != nil { d.posit++ return node } d.iterator = nil } } func (d *descendantQuery) Evaluate(t iterator) interface{} { d.Input.Evaluate(t) d.iterator = nil return d } func (d *descendantQuery) Test(n NodeNavigator) bool { return d.Predicate(n) } // position returns a position of current NodeNavigator. func (d *descendantQuery) position() int { return d.posit } func (d *descendantQuery) depth() int { return d.level } func (d *descendantQuery) Clone() query { return &descendantQuery{name: d.name, Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate} } func (d *descendantQuery) ValueType() resultType { return xpathResultType.NodeSet } func (d *descendantQuery) Properties() queryProp { return queryProps.Merge } // followingQuery is an XPath following node query.(following::*|following-sibling::*) type followingQuery struct { posit int iterator func() NodeNavigator Input query Sibling bool // The matching sibling node of current node. Predicate func(NodeNavigator) bool } func (f *followingQuery) Select(t iterator) NodeNavigator { for { if f.iterator == nil { f.posit = 0 node := f.Input.Select(t) if node == nil { return nil } node = node.Copy() if f.Sibling { f.iterator = func() NodeNavigator { for { if !node.MoveToNext() { return nil } if f.Predicate(node) { f.posit++ return node } } } } else { var q *descendantQuery // descendant query f.iterator = func() NodeNavigator { for { if q == nil { for !node.MoveToNext() { if !node.MoveToParent() { return nil } } q = &descendantQuery{ Self: true, Input: &contextQuery{}, Predicate: f.Predicate, } t.Current().MoveTo(node) } if node := q.Select(t); node != nil { f.posit = q.posit return node } q = nil } } } } if node := f.iterator(); node != nil { return node } f.iterator = nil } } func (f *followingQuery) Evaluate(t iterator) interface{} { f.Input.Evaluate(t) return f } func (f *followingQuery) Test(n NodeNavigator) bool { return f.Predicate(n) } func (f *followingQuery) Clone() query { return &followingQuery{Input: f.Input.Clone(), Sibling: f.Sibling, Predicate: f.Predicate} } func (f *followingQuery) ValueType() resultType { return xpathResultType.NodeSet } func (f *followingQuery) Properties() queryProp { return queryProps.Merge } func (f *followingQuery) position() int { return f.posit } // precedingQuery is an XPath preceding node query.(preceding::*) type precedingQuery struct { iterator func() NodeNavigator posit int Input query Sibling bool // The matching sibling node of current node. Predicate func(NodeNavigator) bool } func (p *precedingQuery) Select(t iterator) NodeNavigator { for { if p.iterator == nil { p.posit = 0 node := p.Input.Select(t) if node == nil { return nil } node = node.Copy() if p.Sibling { p.iterator = func() NodeNavigator { for { for !node.MoveToPrevious() { return nil } if p.Predicate(node) { p.posit++ return node } } } } else { var q query p.iterator = func() NodeNavigator { for { if q == nil { for !node.MoveToPrevious() { if !node.MoveToParent() { return nil } p.posit = 0 } q = &descendantQuery{ Self: true, Input: &contextQuery{}, Predicate: p.Predicate, } t.Current().MoveTo(node) } if node := q.Select(t); node != nil { p.posit++ return node } q = nil } } } } if node := p.iterator(); node != nil { return node } p.iterator = nil } } func (p *precedingQuery) Evaluate(t iterator) interface{} { p.Input.Evaluate(t) return p } func (p *precedingQuery) Test(n NodeNavigator) bool { return p.Predicate(n) } func (p *precedingQuery) Clone() query { return &precedingQuery{Input: p.Input.Clone(), Sibling: p.Sibling, Predicate: p.Predicate} } func (p *precedingQuery) ValueType() resultType { return xpathResultType.NodeSet } func (p *precedingQuery) Properties() queryProp { return queryProps.Merge | queryProps.Reverse } func (p *precedingQuery) position() int { return p.posit } // parentQuery is an XPath parent node query.(parent::*) type parentQuery struct { Input query Predicate func(NodeNavigator) bool } func (p *parentQuery) Select(t iterator) NodeNavigator { for { node := p.Input.Select(t) if node == nil { return nil } node = node.Copy() if node.MoveToParent() && p.Predicate(node) { return node } } } func (p *parentQuery) Evaluate(t iterator) interface{} { p.Input.Evaluate(t) return p } func (p *parentQuery) Clone() query { return &parentQuery{Input: p.Input.Clone(), Predicate: p.Predicate} } func (p *parentQuery) ValueType() resultType { return xpathResultType.NodeSet } func (p *parentQuery) Properties() queryProp { return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge } func (p *parentQuery) Test(n NodeNavigator) bool { return p.Predicate(n) } // selfQuery is an Self node query.(self::*) type selfQuery struct { Input query Predicate func(NodeNavigator) bool } func (s *selfQuery) Select(t iterator) NodeNavigator { for { node := s.Input.Select(t) if node == nil { return nil } if s.Predicate(node) { return node } } } func (s *selfQuery) Evaluate(t iterator) interface{} { s.Input.Evaluate(t) return s } func (s *selfQuery) Test(n NodeNavigator) bool { return s.Predicate(n) } func (s *selfQuery) Clone() query { return &selfQuery{Input: s.Input.Clone(), Predicate: s.Predicate} } func (s *selfQuery) ValueType() resultType { return xpathResultType.NodeSet } func (s *selfQuery) Properties() queryProp { return queryProps.Merge } // filterQuery is an XPath query for predicate filter. type filterQuery struct { Input query Predicate query NoPosition bool posit int positmap map[int]int } func (f *filterQuery) do(t iterator) bool { val := reflect.ValueOf(f.Predicate.Evaluate(t)) switch val.Kind() { case reflect.Bool: return val.Bool() case reflect.String: return len(val.String()) > 0 case reflect.Float64: pt := getNodePosition(f.Input) return int(val.Float()) == pt default: if f.Predicate != nil { return f.Predicate.Select(t) != nil } } return false } func (f *filterQuery) position() int { return f.posit } func (f *filterQuery) Select(t iterator) NodeNavigator { if f.positmap == nil { f.positmap = make(map[int]int) } for { node := f.Input.Select(t) if node == nil { return nil } node = node.Copy() t.Current().MoveTo(node) if f.do(t) { // fix https://github.com/antchfx/htmlquery/issues/26 // Calculate and keep the each of matching node's position in the same depth. level := getNodeDepth(f.Input) f.positmap[level]++ f.posit = f.positmap[level] return node } } } func (f *filterQuery) Evaluate(t iterator) interface{} { f.Input.Evaluate(t) return f } func (f *filterQuery) Clone() query { return &filterQuery{Input: f.Input.Clone(), Predicate: f.Predicate.Clone()} } func (f *filterQuery) ValueType() resultType { return xpathResultType.NodeSet } func (f *filterQuery) Properties() queryProp { return (queryProps.Position | f.Input.Properties()) & (queryProps.Reverse | queryProps.Merge) } // functionQuery is an XPath function that returns a computed value for // the Evaluate call of the current NodeNavigator node. Select call isn't // applicable for functionQuery. type functionQuery struct { Input query // Node Set Func func(query, iterator) interface{} // The xpath function. } func (f *functionQuery) Select(t iterator) NodeNavigator { return nil } // Evaluate call a specified function that will returns the // following value type: number,string,boolean. func (f *functionQuery) Evaluate(t iterator) interface{} { return f.Func(f.Input, t) } func (f *functionQuery) Clone() query { if f.Input == nil { return &functionQuery{Func: f.Func} } return &functionQuery{Input: f.Input.Clone(), Func: f.Func} } func (f *functionQuery) ValueType() resultType { return xpathResultType.Any } func (f *functionQuery) Properties() queryProp { return queryProps.Merge } // transformFunctionQuery diffs from functionQuery where the latter computes a scalar // value (number,string,boolean) for the current NodeNavigator node while the former // (transformFunctionQuery) performs a mapping or transform of the current NodeNavigator // and returns a new NodeNavigator. It is used for non-scalar XPath functions such as // reverse(), remove(), subsequence(), unordered(), etc. type transformFunctionQuery struct { Input query Func func(query, iterator) func() NodeNavigator iterator func() NodeNavigator } func (f *transformFunctionQuery) Select(t iterator) NodeNavigator { if f.iterator == nil { f.iterator = f.Func(f.Input, t) } return f.iterator() } func (f *transformFunctionQuery) Evaluate(t iterator) interface{} { f.Input.Evaluate(t) f.iterator = nil return f } func (f *transformFunctionQuery) Clone() query { return &transformFunctionQuery{Input: f.Input.Clone(), Func: f.Func} } func (f *transformFunctionQuery) ValueType() resultType { return xpathResultType.Any } func (f *transformFunctionQuery) Properties() queryProp { return queryProps.Merge } // constantQuery is an XPath constant operand. type constantQuery struct { Val interface{} } func (c *constantQuery) Select(t iterator) NodeNavigator { return nil } func (c *constantQuery) Evaluate(t iterator) interface{} { return c.Val } func (c *constantQuery) Clone() query { return c } func (c *constantQuery) ValueType() resultType { return getXPathType(c.Val) } func (c *constantQuery) Properties() queryProp { return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge } type groupQuery struct { posit int Input query } func (g *groupQuery) Select(t iterator) NodeNavigator { node := g.Input.Select(t) if node == nil { return nil } g.posit++ return node } func (g *groupQuery) Evaluate(t iterator) interface{} { return g.Input.Evaluate(t) } func (g *groupQuery) Clone() query { return &groupQuery{Input: g.Input.Clone()} } func (g *groupQuery) ValueType() resultType { return g.Input.ValueType() } func (g *groupQuery) Properties() queryProp { return queryProps.Position } func (g *groupQuery) position() int { return g.posit } // logicalQuery is an XPath logical expression. type logicalQuery struct { Left, Right query Do func(iterator, interface{}, interface{}) interface{} } func (l *logicalQuery) Select(t iterator) NodeNavigator { // When a XPath expr is logical expression. node := t.Current().Copy() val := l.Evaluate(t) switch val.(type) { case bool: if val.(bool) == true { return node } } return nil } func (l *logicalQuery) Evaluate(t iterator) interface{} { m := l.Left.Evaluate(t) n := l.Right.Evaluate(t) return l.Do(t, m, n) } func (l *logicalQuery) Clone() query { return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do} } func (l *logicalQuery) ValueType() resultType { return xpathResultType.Boolean } func (l *logicalQuery) Properties() queryProp { return queryProps.Merge } // numericQuery is an XPath numeric operator expression. type numericQuery struct { Left, Right query Do func(iterator, interface{}, interface{}) interface{} } func (n *numericQuery) Select(t iterator) NodeNavigator { return nil } func (n *numericQuery) Evaluate(t iterator) interface{} { m := n.Left.Evaluate(t) k := n.Right.Evaluate(t) return n.Do(t, m, k) } func (n *numericQuery) Clone() query { return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do} } func (n *numericQuery) ValueType() resultType { return xpathResultType.Number } func (n *numericQuery) Properties() queryProp { return queryProps.Merge } type booleanQuery struct { IsOr bool Left, Right query iterator func() NodeNavigator } func (b *booleanQuery) Select(t iterator) NodeNavigator { if b.iterator == nil { var list []NodeNavigator i := 0 root := t.Current().Copy() if b.IsOr { for { node := b.Left.Select(t) if node == nil { break } node = node.Copy() list = append(list, node) } t.Current().MoveTo(root) for { node := b.Right.Select(t) if node == nil { break } node = node.Copy() list = append(list, node) } } else { var m []NodeNavigator var n []NodeNavigator for { node := b.Left.Select(t) if node == nil { break } node = node.Copy() list = append(m, node) } t.Current().MoveTo(root) for { node := b.Right.Select(t) if node == nil { break } node = node.Copy() list = append(n, node) } for _, k := range m { for _, j := range n { if k == j { list = append(list, k) } } } } b.iterator = func() NodeNavigator { if i >= len(list) { return nil } node := list[i] i++ return node } } return b.iterator() } func (b *booleanQuery) Evaluate(t iterator) interface{} { n := t.Current().Copy() m := b.Left.Evaluate(t) left := asBool(t, m) if b.IsOr && left { return true } else if !b.IsOr && !left { return false } t.Current().MoveTo(n) m = b.Right.Evaluate(t) return asBool(t, m) } func (b *booleanQuery) Clone() query { return &booleanQuery{IsOr: b.IsOr, Left: b.Left.Clone(), Right: b.Right.Clone()} } func (b *booleanQuery) ValueType() resultType { return xpathResultType.Boolean } func (b *booleanQuery) Properties() queryProp { return queryProps.Merge } type unionQuery struct { Left, Right query iterator func() NodeNavigator } func (u *unionQuery) Select(t iterator) NodeNavigator { if u.iterator == nil { var list []NodeNavigator var m = make(map[uint64]bool) root := t.Current().Copy() for { node := u.Left.Select(t) if node == nil { break } code := getHashCode(node.Copy()) if _, ok := m[code]; !ok { m[code] = true list = append(list, node.Copy()) } } t.Current().MoveTo(root) for { node := u.Right.Select(t) if node == nil { break } code := getHashCode(node.Copy()) if _, ok := m[code]; !ok { m[code] = true list = append(list, node.Copy()) } } var i int u.iterator = func() NodeNavigator { if i >= len(list) { return nil } node := list[i] i++ return node } } return u.iterator() } func (u *unionQuery) Evaluate(t iterator) interface{} { u.iterator = nil u.Left.Evaluate(t) u.Right.Evaluate(t) return u } func (u *unionQuery) Clone() query { return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()} } func (u *unionQuery) ValueType() resultType { return xpathResultType.NodeSet } func (u *unionQuery) Properties() queryProp { return queryProps.Merge } type lastFuncQuery struct { buffer []NodeNavigator counted bool Input query } func (q *lastFuncQuery) Select(t iterator) NodeNavigator { return nil } func (q *lastFuncQuery) Evaluate(t iterator) interface{} { if !q.counted { for { node := q.Input.Select(t) if node == nil { break } q.buffer = append(q.buffer, node.Copy()) } q.counted = true } return float64(len(q.buffer)) } func (q *lastFuncQuery) Clone() query { return &lastFuncQuery{Input: q.Input.Clone()} } func (q *lastFuncQuery) ValueType() resultType { return xpathResultType.Number } func (q *lastFuncQuery) Properties() queryProp { return queryProps.Merge } type descendantOverDescendantQuery struct { name string level int posit int currentNode NodeNavigator Input query MatchSelf bool Predicate func(NodeNavigator) bool } func (d *descendantOverDescendantQuery) moveToFirstChild() bool { if d.currentNode.MoveToChild() { d.level++ return true } return false } func (d *descendantOverDescendantQuery) moveUpUntilNext() bool { for !d.currentNode.MoveToNext() { d.level-- if d.level == 0 { return false } d.currentNode.MoveToParent() } return true } func (d *descendantOverDescendantQuery) Select(t iterator) NodeNavigator { for { if d.level == 0 { node := d.Input.Select(t) if node == nil { return nil } d.currentNode = node.Copy() d.posit = 0 if d.MatchSelf && d.Predicate(d.currentNode) { d.posit = 1 return d.currentNode } d.moveToFirstChild() } else if !d.moveUpUntilNext() { continue } for ok := true; ok; ok = d.moveToFirstChild() { if d.Predicate(d.currentNode) { d.posit++ return d.currentNode } } } } func (d *descendantOverDescendantQuery) Evaluate(t iterator) interface{} { d.Input.Evaluate(t) return d } func (d *descendantOverDescendantQuery) Clone() query { return &descendantOverDescendantQuery{Input: d.Input.Clone(), Predicate: d.Predicate, MatchSelf: d.MatchSelf} } func (d *descendantOverDescendantQuery) ValueType() resultType { return xpathResultType.NodeSet } func (d *descendantOverDescendantQuery) Properties() queryProp { return queryProps.Merge } func (d *descendantOverDescendantQuery) position() int { return d.posit } type mergeQuery struct { Input query Child query iterator func() NodeNavigator } func (m *mergeQuery) Select(t iterator) NodeNavigator { for { if m.iterator == nil { root := m.Input.Select(t) if root == nil { return nil } m.Child.Evaluate(t) root = root.Copy() t.Current().MoveTo(root) var list []NodeNavigator for node := m.Child.Select(t); node != nil; node = m.Child.Select(t) { list = append(list, node.Copy()) } i := 0 m.iterator = func() NodeNavigator { if i >= len(list) { return nil } result := list[i] i++ return result } } if node := m.iterator(); node != nil { return node } m.iterator = nil } } func (m *mergeQuery) Evaluate(t iterator) interface{} { m.Input.Evaluate(t) return m } func (m *mergeQuery) Clone() query { return &mergeQuery{Input: m.Input.Clone(), Child: m.Child.Clone()} } func (m *mergeQuery) ValueType() resultType { return xpathResultType.NodeSet } func (m *mergeQuery) Properties() queryProp { return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge } func getHashCode(n NodeNavigator) uint64 { var sb bytes.Buffer switch n.NodeType() { case AttributeNode, TextNode, CommentNode: sb.WriteString(fmt.Sprintf("%s=%s", n.LocalName(), n.Value())) // https://github.com/antchfx/htmlquery/issues/25 d := 1 for n.MoveToPrevious() { d++ } sb.WriteString(fmt.Sprintf("-%d", d)) for n.MoveToParent() { d = 1 for n.MoveToPrevious() { d++ } sb.WriteString(fmt.Sprintf("-%d", d)) } case ElementNode: sb.WriteString(n.Prefix() + n.LocalName()) d := 1 for n.MoveToPrevious() { d++ } sb.WriteString(fmt.Sprintf("-%d", d)) for n.MoveToParent() { d = 1 for n.MoveToPrevious() { d++ } sb.WriteString(fmt.Sprintf("-%d", d)) } } h := fnv.New64a() h.Write(sb.Bytes()) return h.Sum64() } func getNodePosition(q query) int { type Position interface { position() int } if count, ok := q.(Position); ok { return count.position() } return 1 } func getNodeDepth(q query) int { type Depth interface { depth() int } if count, ok := q.(Depth); ok { return count.depth() } return 0 } func getXPathType(i interface{}) resultType { v := reflect.ValueOf(i) switch v.Kind() { case reflect.Float64: return xpathResultType.Number case reflect.String: return xpathResultType.String case reflect.Bool: return xpathResultType.Boolean default: if _, ok := i.(query); ok { return xpathResultType.NodeSet } } panic(fmt.Errorf("xpath unknown value type: %v", v.Kind())) } golang-github-antchfx-xpath-1.3.3/xpath.go000066400000000000000000000107041473310030300204670ustar00rootroot00000000000000package xpath import ( "errors" "fmt" ) // NodeType represents a type of XPath node. type NodeType int const ( // RootNode is a root node of the XML document or node tree. RootNode NodeType = iota // ElementNode is an element, such as . ElementNode // AttributeNode is an attribute, such as id='123'. AttributeNode // TextNode is the text content of a node. TextNode // CommentNode is a comment node, such as CommentNode // allNode is any types of node, used by xpath package only to predicate match. allNode ) // NodeNavigator provides cursor model for navigating XML data. type NodeNavigator interface { // NodeType returns the XPathNodeType of the current node. NodeType() NodeType // LocalName gets the Name of the current node. LocalName() string // Prefix returns namespace prefix associated with the current node. Prefix() string // Value gets the value of current node. Value() string // Copy does a deep copy of the NodeNavigator and all its components. Copy() NodeNavigator // MoveToRoot moves the NodeNavigator to the root node of the current node. MoveToRoot() // MoveToParent moves the NodeNavigator to the parent node of the current node. MoveToParent() bool // MoveToNextAttribute moves the NodeNavigator to the next attribute on current node. MoveToNextAttribute() bool // MoveToChild moves the NodeNavigator to the first child node of the current node. MoveToChild() bool // MoveToFirst moves the NodeNavigator to the first sibling node of the current node. MoveToFirst() bool // MoveToNext moves the NodeNavigator to the next sibling node of the current node. MoveToNext() bool // MoveToPrevious moves the NodeNavigator to the previous sibling node of the current node. MoveToPrevious() bool // MoveTo moves the NodeNavigator to the same position as the specified NodeNavigator. MoveTo(NodeNavigator) bool } // NodeIterator holds all matched Node object. type NodeIterator struct { node NodeNavigator query query } // Current returns current node which matched. func (t *NodeIterator) Current() NodeNavigator { return t.node } // MoveNext moves Navigator to the next match node. func (t *NodeIterator) MoveNext() bool { n := t.query.Select(t) if n == nil { return false } if !t.node.MoveTo(n) { t.node = n.Copy() } return true } // Select selects a node set using the specified XPath expression. // This method is deprecated, recommend using Expr.Select() method instead. func Select(root NodeNavigator, expr string) *NodeIterator { exp, err := Compile(expr) if err != nil { panic(err) } return exp.Select(root) } // Expr is an XPath expression for query. type Expr struct { s string q query } type iteratorFunc func() NodeNavigator func (f iteratorFunc) Current() NodeNavigator { return f() } // Evaluate returns the result of the expression. // The result type of the expression is one of the follow: bool,float64,string,NodeIterator). func (expr *Expr) Evaluate(root NodeNavigator) interface{} { val := expr.q.Evaluate(iteratorFunc(func() NodeNavigator { return root })) switch val.(type) { case query: return &NodeIterator{query: expr.q.Clone(), node: root} } return val } // Select selects a node set using the specified XPath expression. func (expr *Expr) Select(root NodeNavigator) *NodeIterator { return &NodeIterator{query: expr.q.Clone(), node: root} } // String returns XPath expression string. func (expr *Expr) String() string { return expr.s } // Compile compiles an XPath expression string. func Compile(expr string) (*Expr, error) { if expr == "" { return nil, errors.New("expr expression is nil") } qy, err := build(expr, nil) if err != nil { return nil, err } if qy == nil { return nil, fmt.Errorf(fmt.Sprintf("undeclared variable in XPath expression: %s", expr)) } return &Expr{s: expr, q: qy}, nil } // MustCompile compiles an XPath expression string and ignored error. func MustCompile(expr string) *Expr { exp, err := Compile(expr) if err != nil { return &Expr{s: expr, q: nopQuery{}} } return exp } // CompileWithNS compiles an XPath expression string, using given namespaces map. func CompileWithNS(expr string, namespaces map[string]string) (*Expr, error) { if expr == "" { return nil, errors.New("expr expression is nil") } qy, err := build(expr, namespaces) if err != nil { return nil, err } if qy == nil { return nil, fmt.Errorf(fmt.Sprintf("undeclared variable in XPath expression: %s", expr)) } return &Expr{s: expr, q: qy}, nil } golang-github-antchfx-xpath-1.3.3/xpath_axes_test.go000066400000000000000000000062541473310030300225530ustar00rootroot00000000000000package xpath import "testing" func Test_self(t *testing.T) { test_xpath_elements(t, employee_example, `//name/self::*`, 4, 9, 14) } func Test_child(t *testing.T) { test_xpath_elements(t, employee_example, `//child::employee/child::email`, 6, 11, 16) test_xpath_elements(t, employee_example, `/empinfo/child::*`, 3, 8, 13) test_xpath_elements(t, employee_example, `/empinfo/child::node()`, 3, 8, 13) test_xpath_values(t, employee_example, `//name/child::text()`, "Opal Kole", "Max Miller", "Beccaa Moss") } func Test_descendant(t *testing.T) { test_xpath_elements(t, employee_example, `//employee/descendant::*`, 4, 5, 6, 9, 10, 11, 14, 15, 16) test_xpath_count(t, employee_example, `//descendant::employee`, 3) } func Test_descendant_or_self(t *testing.T) { test_xpath_tags(t, employee_example.FirstChild, `self::*`, "empinfo") test_xpath_elements(t, employee_example, `//employee/descendant-or-self::*`, 3, 4, 5, 6, 8, 9, 10, 11, 13, 14, 15, 16) test_xpath_count(t, employee_example, `//descendant-or-self::employee`, 3) } func Test_ancestor(t *testing.T) { test_xpath_tags(t, employee_example, `//employee/ancestor::*`, "empinfo") test_xpath_tags(t, employee_example, `//employee/ancestor::empinfo`, "empinfo") // Test Panic //test_xpath_elements(t, employee_example, `//ancestor::name`, 4, 9, 14) } func Test_ancestor_or_self(t *testing.T) { // Expected the value is [2, 3, 8, 13], but got [3, 2, 8, 13] test_xpath_elements(t, employee_example, `//employee/ancestor-or-self::*`, 3, 2, 8, 13) test_xpath_elements(t, employee_example, `//name/ancestor-or-self::employee`, 3, 8, 13) } func Test_parent(t *testing.T) { test_xpath_elements(t, employee_example, `//name/parent::*`, 3, 8, 13) test_xpath_elements(t, employee_example, `//name/parent::employee`, 3, 8, 13) } func Test_attribute(t *testing.T) { test_xpath_values(t, employee_example, `//attribute::id`, "1", "2", "3") test_xpath_count(t, employee_example, `//attribute::*`, 9) // test failed //test_xpath_tags(t, employee_example, `//attribute::*[1]`, "id", "discipline", "id", "from", "discipline", "id", "discipline") // test failed(random): the return values is expected but the order of value is random. //test_xpath_tags(t, employee_example, `//attribute::*`, "id", "discipline", "experience", "id", "from", "discipline", "experience", "id", "discipline") } func Test_following(t *testing.T) { test_xpath_elements(t, employee_example, `//employee[@id=1]/following::*`, 8, 9, 10, 11, 13, 14, 15, 16) } func Test_following_sibling(t *testing.T) { test_xpath_elements(t, employee_example, `//employee[@id=1]/following-sibling::*`, 8, 13) test_xpath_elements(t, employee_example, `//employee[@id=1]/following-sibling::employee`, 8, 13) } func Test_preceding(t *testing.T) { //testXPath3(t, html, "//li[last()]/preceding-sibling::*[2]", selectNode(html, "//li[position()=2]")) //testXPath3(t, html, "//li/preceding::*[1]", selectNode(html, "//h1")) test_xpath_elements(t, employee_example, `//employee[@id=3]/preceding::*`, 8, 9, 10, 11, 3, 4, 5, 6) } func Test_preceding_sibling(t *testing.T) { test_xpath_elements(t, employee_example, `//employee[@id=3]/preceding-sibling::*`, 8, 3) } func Test_namespace(t *testing.T) { // TODO } golang-github-antchfx-xpath-1.3.3/xpath_expression_test.go000066400000000000000000000142021473310030300240020ustar00rootroot00000000000000package xpath import ( "testing" ) func Test_descendant_issue(t *testing.T) { // Issue #93 https://github.com/antchfx/xpath/issues/93 /*
span one
span two
*/ doc := createNode("", RootNode) div := doc.createChildNode("div", ElementNode) div.lines = 1 div.addAttribute("id", "wrapper") span := div.createChildNode("span", ElementNode) span.lines = 2 span.createChildNode("span one", TextNode) div = div.createChildNode("div", ElementNode) div.lines = 3 span = div.createChildNode("span", ElementNode) span.lines = 4 span.createChildNode("span two", TextNode) test_xpath_elements(t, doc, `//div[@id='wrapper']/descendant::span[1]`, 2) test_xpath_elements(t, doc, `//div[@id='wrapper']//descendant::span[1]`, 2, 4) } // https://github.com/antchfx/htmlquery/issues/52 func TestRelativePaths(t *testing.T) { test_xpath_elements(t, book_example, `//bookstore`, 2) test_xpath_elements(t, book_example, `//book`, 3, 9, 15, 25) test_xpath_elements(t, book_example, `//bookstore/book`, 3, 9, 15, 25) test_xpath_tags(t, book_example, `//book/..`, "bookstore") test_xpath_elements(t, book_example, `//book[@category="cooking"]/..`, 2) test_xpath_elements(t, book_example, `//book/year[text() = 2005]/../..`, 2) // bookstore test_xpath_elements(t, book_example, `//book/year/../following-sibling::*`, 9, 15, 25) test_xpath_count(t, book_example, `//bookstore/book/*`, 20) test_xpath_tags(t, html_example, "//title/../..", "html") test_xpath_elements(t, html_example, "//ul/../p", 19) } func TestAbsolutePaths(t *testing.T) { test_xpath_elements(t, book_example, `bookstore`, 2) test_xpath_elements(t, book_example, `bookstore/book`, 3, 9, 15, 25) test_xpath_elements(t, book_example, `(bookstore/book)`, 3, 9, 15, 25) test_xpath_elements(t, book_example, `bookstore/book[2]`, 9) test_xpath_elements(t, book_example, `bookstore/book[last()]`, 25) test_xpath_elements(t, book_example, `bookstore/book[last()]/title`, 26) test_xpath_values(t, book_example, `/bookstore/book[last()]/title/text()`, "Learning XML") test_xpath_values(t, book_example, `/bookstore/book[@category = "children"]/year`, "2005") test_xpath_elements(t, book_example, `bookstore/book/..`, 2) test_xpath_elements(t, book_example, `/bookstore/*`, 3, 9, 15, 25) test_xpath_elements(t, book_example, `/bookstore/*/title`, 4, 10, 16, 26) } func TestAttributes(t *testing.T) { test_xpath_tags(t, html_example.FirstChild, "@*", "lang") test_xpath_count(t, employee_example, `//@*`, 9) test_xpath_values(t, employee_example, `//@discipline`, "web", "DBA", "appdev") test_xpath_count(t, employee_example, `//employee/@id`, 3) } func TestExpressions(t *testing.T) { test_xpath_elements(t, book_example, `//book[@category = "cooking"] | //book[@category = "children"]`, 3, 9) test_xpath_elements(t, book_example, `//book[@category = "web"] and //book[price = "39.95"]`, 25) test_xpath_count(t, html_example, `//ul/*`, 3) test_xpath_count(t, html_example, `//ul/*/a`, 3) // Sequence // // table/tbody/tr/td/(para, .[not(para)], ..) } func TestSequence(t *testing.T) { // `//table/tbody/tr/td/(para, .[not(para)],..)` test_xpath_count(t, html_example, `//body/(h1, h2, p)`, 2) test_xpath_count(t, html_example, `//body/(h1, h2, p, ..)`, 3) } func TestLatinAttributesInXPath(t *testing.T) { doc := createNode("", RootNode) div := doc.createChildNode("div", ElementNode) div.addAttribute("language", "english") div.lines = 1 test_xpath_elements(t, doc, `//div[@language='english']`, 1) } func TestCyrillicAttributesInXPath(t *testing.T) { doc := createNode("", RootNode) div := doc.createChildNode("div", ElementNode) div.addAttribute("язык", "русский") div.lines = 1 test_xpath_elements(t, doc, `//div[@язык='русский']`, 1) } func TestGreekAttributesInXPath(t *testing.T) { doc := createNode("", RootNode) div := doc.createChildNode("div", ElementNode) div.addAttribute("γλώσσα", "ελληνικά") div.lines = 1 test_xpath_elements(t, doc, `//div[@γλώσσα='ελληνικά']`, 1) } func TestCyrillicAndGreekAttributesMixedInXPath(t *testing.T) { doc := createNode("", RootNode) div := doc.createChildNode("div", ElementNode) div.addAttribute("язык", "русский") div.addAttribute("γλώσσα", "ελληνικά") div.lines = 1 test_xpath_elements(t, doc, `//div[@язык='русский' and @γλώσσα='ελληνικά']`, 1) } func TestCyrillicAttributesInXPath_NoMatch(t *testing.T) { doc := createNode("", RootNode) div := doc.createChildNode("div", ElementNode) div.addAttribute("язык", "русский") div.lines = 1 test_xpath_elements(t, doc, `//div[@язык='английский']`) } func TestGreekAttributesInXPath_NoMatch(t *testing.T) { doc := createNode("", RootNode) div := doc.createChildNode("div", ElementNode) div.addAttribute("γλώσσα", "ελληνικά") div.lines = 1 test_xpath_elements(t, doc, `//div[@γλώσσα='αγγλικά']`) } func TestNonEnglishExpression(t *testing.T) { doc := createNode("", RootNode) n_1 := doc.createChildNode("Σειρά", ElementNode) n_1.lines = 1 n_2 := n_1.createChildNode("ελληνικά", ElementNode) n_2.lines = 2 n_2.createChildNode("hello", TextNode) test_xpath_elements(t, doc, "//Σειρά", 1) test_xpath_values(t, doc, "//Σειρά/ελληνικά", "hello") } func TestChineseCharactersExpression(t *testing.T) { doc := createNode("", RootNode) n := doc.createChildNode("中文", ElementNode) n.createChildNode("你好世界", TextNode) test_xpath_values(t, doc, "//中文", "你好世界") } func TestBUG_104(t *testing.T) { // BUG https://github.com/antchfx/xpath/issues/104 test_xpath_count(t, book_example, `//author[1]`, 4) test_xpath_values(t, book_example, `//author[1]/text()`, "Giada De Laurentiis", "J K. Rowling", "James McGovern", "Erik T. Ray") } func TestNonEnglishPredicateExpression(t *testing.T) { // https://github.com/antchfx/xpath/issues/106 doc := createNode("", RootNode) n := doc.createChildNode("h1", ElementNode) n.addAttribute("id", "断点") n.createChildNode("Hello,World!", TextNode) test_xpath_count(t, doc, "//h1[@id='断点']", 1) } golang-github-antchfx-xpath-1.3.3/xpath_function_test.go000066400000000000000000000315551473310030300234420ustar00rootroot00000000000000package xpath import ( "math" "testing" ) // Some test examples from http://zvon.org/comp/r/ref-XPath_2.html func Test_func_boolean(t *testing.T) { test_xpath_eval(t, empty_example, `true()`, true) test_xpath_eval(t, empty_example, `false()`, false) test_xpath_eval(t, empty_example, `boolean(0)`, false) test_xpath_eval(t, empty_example, `boolean(null)`, false) test_xpath_eval(t, empty_example, `boolean(1)`, true) test_xpath_eval(t, empty_example, `boolean(2)`, true) test_xpath_eval(t, empty_example, `boolean(true)`, false) test_xpath_eval(t, empty_example, `boolean(1 > 2)`, false) test_xpath_eval(t, book_example, `boolean(//*[@lang])`, true) test_xpath_eval(t, book_example, `boolean(//*[@x])`, false) } func Test_func_name(t *testing.T) { test_xpath_eval(t, html_example, `name(//html/@lang)`, "lang") test_xpath_eval(t, html_example, `name(html/head/title)`, "title") test_xpath_count(t, html_example, `//*[name() = "li"]`, 3) } func Test_func_not(t *testing.T) { //test_xpath_eval(t, empty_example, `not(0)`, true) //test_xpath_eval(t, empty_example, `not(1)`, false) test_xpath_elements(t, employee_example, `//employee[not(@id = "1")]`, 8, 13) test_xpath_elements(t, book_example, `//book[not(year = 2005)]`, 15, 25) test_xpath_count(t, book_example, `//book[not(title)]`, 0) } func Test_func_ceiling_floor(t *testing.T) { test_xpath_eval(t, empty_example, "ceiling(5.2)", float64(6)) test_xpath_eval(t, empty_example, "floor(5.2)", float64(5)) } func Test_func_concat(t *testing.T) { test_xpath_eval(t, empty_example, `concat("1", "2", "3")`, "123") //test_xpath_eval(t, empty_example, `concat("Ciao!", ())`, "Ciao!") test_xpath_eval(t, book_example, `concat(//book[1]/title, ", ", //book[1]/year)`, "Everyday Italian, 2005") result := concatFunc(testQuery("a"), testQuery("b"))(nil, nil).(string) assertEqual(t, result, "ab") } func Test_func_contains(t *testing.T) { test_xpath_eval(t, empty_example, `contains("tattoo", "t")`, true) test_xpath_eval(t, empty_example, `contains("tattoo", "T")`, false) test_xpath_eval(t, empty_example, `contains("tattoo", "ttt")`, false) //test_xpath_eval(t, empty_example, `contains("", ())`, true) test_xpath_elements(t, book_example, `//book[contains(title, "Potter")]`, 9) test_xpath_elements(t, book_example, `//book[contains(year, "2005")]`, 3, 9) assertPanic(t, func() { selectNode(html_example, "//*[contains(0, 0)]") }) } func Test_func_count(t *testing.T) { test_xpath_eval(t, book_example, `count(//book)`, float64(4)) test_xpath_eval(t, book_example, `count(//book[3]/author)`, float64(5)) } func Test_func_ends_with(t *testing.T) { test_xpath_eval(t, empty_example, `ends-with("tattoo", "tattoo")`, true) test_xpath_eval(t, empty_example, `ends-with("tattoo", "atto")`, false) test_xpath_elements(t, book_example, `//book[ends-with(@category,'ing')]`, 3) test_xpath_elements(t, book_example, `//book[ends-with(./price,'.99')]`, 9, 15) assertPanic(t, func() { selectNode(html_example, `//*[ends-with(0, 0)]`) }) // arg must be start with string assertPanic(t, func() { selectNode(html_example, `//*[ends-with(name(), 0)]`) }) } func Test_func_last(t *testing.T) { test_xpath_elements(t, book_example, `//bookstore[last()]`, 2) test_xpath_elements(t, book_example, `//bookstore/book[last()]`, 25) test_xpath_elements(t, book_example, `(//bookstore/book)[last()]`, 25) //https: //github.com/antchfx/xpath/issues/76 test_xpath_elements(t, book_example, `(//bookstore/book[year = 2005])[last()]`, 9) test_xpath_elements(t, book_example, `//bookstore/book[year = 2005][last()]`, 9) test_xpath_elements(t, html_example, `//ul/li[last()]`, 15) test_xpath_elements(t, html_example, `(//ul/li)[last()]`, 15) } func Test_func_local_name(t *testing.T) { test_xpath_eval(t, book_example, `local-name(bookstore)`, "bookstore") test_xpath_eval(t, mybook_example, `local-name(//mybook:book)`, "book") } func Test_func_starts_with(t *testing.T) { test_xpath_eval(t, employee_example, `starts-with("tattoo", "tat")`, true) test_xpath_eval(t, employee_example, `starts-with("tattoo", "att")`, false) test_xpath_elements(t, book_example, `//book[starts-with(title,'Everyday')]`, 3) assertPanic(t, func() { selectNode(html_example, `//*[starts-with(0, 0)]`) }) assertPanic(t, func() { selectNode(html_example, `//*[starts-with(name(), 0)]`) }) } func Test_func_string(t *testing.T) { test_xpath_eval(t, empty_example, `string(1.23)`, "1.23") test_xpath_eval(t, empty_example, `string(3)`, "3") test_xpath_eval(t, book_example, `string(//book/@category)`, "cooking") } func Test_func_string_join(t *testing.T) { //(t, empty_example, `string-join(('Now', 'is', 'the', 'time', '...'), '')`, "Now is the time ...") test_xpath_eval(t, empty_example, `string-join("some text", ";")`, "some text") test_xpath_eval(t, book_example, `string-join(//book/@category, ";")`, "cooking;children;web;web") } func Test_func_string_length(t *testing.T) { test_xpath_eval(t, empty_example, `string-length("Harp not on that string, madam; that is past.")`, float64(45)) test_xpath_eval(t, empty_example, `string-length(normalize-space(' abc '))`, float64(3)) test_xpath_eval(t, html_example, `string-length(//title/text())`, float64(len("My page"))) test_xpath_eval(t, html_example, `string-length(//html/@lang)`, float64(len("en"))) test_xpath_count(t, employee_example, `//employee[string-length(@id) > 0]`, 3) // = //employee[@id] } func Test_func_substring(t *testing.T) { test_xpath_eval(t, empty_example, `substring("motor car", 6)`, " car") test_xpath_eval(t, empty_example, `substring("metadata", 4, 3)`, "ada") //test_xpath_eval(t, empty_example, `substring("12345", 5, -3)`, "") // ?? it should be 1 ?? //test_xpath_eval(t, empty_example, `substring("12345", 1.5, 2.6)`, "234") //test_xpath_eval(t, empty_example, `substring("12345", 0, 3)`, "12") // panic?? //test_xpath_eval(t, empty_example, `substring("12345", 5, -3)`, "1") test_xpath_eval(t, html_example, `substring(//title/child::node(), 1)`, "My page") //assertPanic(t, func() { selectNode(empty_example, `substring("12345", 5, -3)`) }) // Should be supports a negative value //assertPanic(t, func() { selectNode(empty_example, `substring("12345", 5, "")`) }) } func Test_func_substring_after(t *testing.T) { test_xpath_eval(t, empty_example, `substring-after("tattoo", "tat")`, "too") test_xpath_eval(t, empty_example, `substring-after("tattoo", "tattoo")`, "") } func Test_func_substring_before(t *testing.T) { test_xpath_eval(t, empty_example, `substring-before("tattoo", "attoo")`, "t") test_xpath_eval(t, empty_example, `substring-before("tattoo", "tatto")`, "") } func Test_func_sum(t *testing.T) { test_xpath_eval(t, empty_example, `sum(1 + 2)`, float64(3)) test_xpath_eval(t, empty_example, `sum(1.1 + 2)`, float64(3.1)) test_xpath_eval(t, book_example, `sum(//book/price)`, float64(149.93)) test_xpath_elements(t, book_example, `//book[sum(./price) > 40]`, 15) assertPanic(t, func() { selectNode(html_example, `//title[sum('Hello') = 0]`) }) } func Test_func_translate(t *testing.T) { test_xpath_eval(t, empty_example, `translate("bar","abc","ABC")`, "BAr") test_xpath_eval(t, empty_example, `translate("--aaa--","abc-","ABC")`, "AAA") test_xpath_eval(t, empty_example, `translate("abcdabc", "abc", "AB")`, "ABdAB") test_xpath_eval(t, empty_example, `translate('The quick brown fox', 'brown', 'red')`, "The quick red fdx") } func Test_func_matches(t *testing.T) { test_xpath_eval(t, empty_example, `matches("abracadabra", "bra")`, true) test_xpath_eval(t, empty_example, `matches("abracadabra", "(?i)^A.*A$")`, true) test_xpath_eval(t, empty_example, `matches("abracadabra", "^a.*a$")`, true) test_xpath_eval(t, empty_example, `matches("abracadabra", "^bra")`, false) assertPanic(t, func() { selectNode(html_example, `//*[matches()]`) }) // arg len check failure assertPanic(t, func() { selectNode(html_example, "//*[matches(substring(), 0)]") }) // first arg processing failure assertPanic(t, func() { selectNode(html_example, "//*[matches(@href, substring())]") }) // second arg processing failure assertPanic(t, func() { selectNode(html_example, "//*[matches(@href, 0)]") }) // second arg not string assertPanic(t, func() { selectNode(html_example, "//*[matches(@href, '[invalid')]") }) // second arg invalid regexp // testing unexpected the regular expression. _, err := Compile(`//*[matches(., '^[\u0621-\u064AA-Za-z\-]+')]`) assertErr(t, err) _, err = Compile(`//*[matches(., '//*[matches(., '\w+`) assertErr(t, err) } func Test_func_number(t *testing.T) { test_xpath_eval(t, empty_example, `number(10)`, float64(10)) test_xpath_eval(t, empty_example, `number(1.11)`, float64(1.11)) test_xpath_eval(t, empty_example, `number("10") > 10`, false) test_xpath_eval(t, empty_example, `number("10") = 10`, true) test_xpath_eval(t, empty_example, `number("123") < 1000`, true) test_xpath_eval(t, empty_example, `number(//non-existent-node) = 0`, false) assertTrue(t, math.IsNaN(MustCompile(`number(//non-existent-node)`).Evaluate(createNavigator(empty_example)).(float64))) assertTrue(t, math.IsNaN(MustCompile(`number("123a")`).Evaluate(createNavigator(empty_example)).(float64))) } func Test_func_position(t *testing.T) { test_xpath_elements(t, book_example, `//book[position() = 1]`, 3) test_xpath_elements(t, book_example, `//book[(position() mod 2) = 0]`, 9, 25) test_xpath_elements(t, book_example, `//book[position() = last()]`, 25) test_xpath_elements(t, book_example, `//book/*[position() = 1]`, 4, 10, 16, 26) // Test Failed //test_xpath_elements(t, book_example, `(//book/title)[position() = 1]`, 3) } func Test_func_replace(t *testing.T) { test_xpath_eval(t, empty_example, `replace('aa-bb-cc','bb','ee')`, "aa-ee-cc") test_xpath_eval(t, empty_example, `replace("abracadabra", "bra", "*")`, "a*cada*") test_xpath_eval(t, empty_example, `replace("abracadabra", "a", "")`, "brcdbr") // The below xpath expressions is not supported yet // //test_xpath_eval(t, empty_example, `replace("abracadabra", "a.*a", "*")`, "*") //test_xpath_eval(t, empty_example, `replace("abracadabra", "a.*?a", "*")`, "*c*bra") //test_xpath_eval(t, empty_example, `replace("abracadabra", ".*?", "$1")`, "*c*bra") // error, because the pattern matches the zero-length string //test_xpath_eval(t, empty_example, `replace("AAAA", "A+", "b")`, "b") //test_xpath_eval(t, empty_example, `replace("AAAA", "A+?", "b")`, "bbb") //test_xpath_eval(t, empty_example, `replace("darted", "^(.*?)d(.*)$", "$1c$2")`, "carted") //test_xpath_eval(t, empty_example, `replace("abracadabra", "a(.)", "a$1$1")`, "abbraccaddabbra") } func Test_func_reverse(t *testing.T) { //test_xpath_eval(t, employee_example, `reverse(("hello"))`, "hello") // Not passed test_xpath_elements(t, employee_example, `reverse(//employee)`, 13, 8, 3) test_xpath_elements(t, employee_example, `//employee[reverse(.) = reverse(.)]`, 3, 8, 13) assertPanic(t, func() { selectNode(html_example, "reverse(concat())") }) // invalid node-sets argument. assertPanic(t, func() { selectNode(html_example, "reverse()") }) // missing node-sets argument. } func Test_func_round(t *testing.T) { test_xpath_eval(t, employee_example, `round(2.5)`, 3) // int test_xpath_eval(t, employee_example, `round(2.5)`, 3) test_xpath_eval(t, employee_example, `round(2.4999)`, 2) } func Test_func_namespace_uri(t *testing.T) { test_xpath_eval(t, mybook_example, `namespace-uri(//mybook:book)`, "http://www.contoso.com/books") test_xpath_elements(t, mybook_example, `//*[namespace-uri()='http://www.contoso.com/books']`, 3, 9) } func Test_func_normalize_space(t *testing.T) { const testStr = "\t \rloooooooonnnnnnngggggggg \r \n tes \u00a0 t strin \n\n \r g " const expectedStr = `loooooooonnnnnnngggggggg tes t strin g` test_xpath_eval(t, empty_example, `normalize-space("`+testStr+`")`, expectedStr) test_xpath_eval(t, empty_example, `normalize-space(' abc ')`, "abc") n := selectNode(employee_example, `//employee[@id="1"]/name`) test_xpath_eval(t, n, `normalize-space()`, "Opal Kole") test_xpath_eval(t, n, `normalize-space(.)`, "Opal Kole") test_xpath_eval(t, book_example, `normalize-space(//book/title)`, "Everyday Italian") test_xpath_eval(t, book_example, `normalize-space(//book[1]/title)`, "Everyday Italian") } func Test_func_lower_case(t *testing.T) { test_xpath_eval(t, empty_example, `lower-case("ABc!D")`, "abc!d") test_xpath_count(t, employee_example, `//name[@from="ca"]`, 0) test_xpath_elements(t, employee_example, `//name[lower-case(@from) = "ca"]`, 9) //test_xpath_eval(t, employee_example, `//employee/name/lower-case(text())`, "opal kole", "max miller", "beccaa moss") } func Benchmark_NormalizeSpaceFunc(b *testing.B) { b.ReportAllocs() const strForNormalization = "\t \rloooooooonnnnnnngggggggg \r \n tes \u00a0 t strin \n\n \r g " for i := 0; i < b.N; i++ { _ = normalizespaceFunc(testQuery(strForNormalization))(nil, nil) } } func Benchmark_ConcatFunc(b *testing.B) { b.ReportAllocs() for i := 0; i < b.N; i++ { _ = concatFunc(testQuery("a"), testQuery("b"))(nil, nil) } } golang-github-antchfx-xpath-1.3.3/xpath_predicate_test.go000066400000000000000000000057211473310030300235510ustar00rootroot00000000000000package xpath import ( "testing" ) func TestLogicals(t *testing.T) { test_xpath_elements(t, book_example, `//book[1 + 1]`, 9) test_xpath_elements(t, book_example, `//book[1 * 2]`, 9) test_xpath_elements(t, book_example, `//book[5 div 2]`, 9) // equal to `//book[2]` test_xpath_elements(t, book_example, `//book[3 div 2]`, 3) test_xpath_elements(t, book_example, `//book[3 - 2]`, 3) test_xpath_elements(t, book_example, `//book[price > 35]`, 15, 25) test_xpath_elements(t, book_example, `//book[price >= 30]`, 3, 15, 25) test_xpath_elements(t, book_example, `//book[price < 30]`, 9) test_xpath_elements(t, book_example, `//book[price <= 30]`, 3, 9) test_xpath_elements(t, book_example, `//book[count(author) > 1]`, 15) test_xpath_elements(t, book_example, `//book[position() mod 2 = 0]`, 9, 25) } func TestPositions(t *testing.T) { test_xpath_elements(t, employee_example, `/empinfo/employee[2]`, 8) test_xpath_elements(t, employee_example, `//employee[position() = 2]`, 8) test_xpath_elements(t, employee_example, `/empinfo/employee[2]/name`, 9) test_xpath_elements(t, employee_example, `//employee[position() > 1]`, 8, 13) test_xpath_elements(t, employee_example, `//employee[position() <= 2]`, 3, 8) test_xpath_elements(t, employee_example, `//employee[last()]`, 13) test_xpath_elements(t, employee_example, `//employee[position() = last()]`, 13) test_xpath_elements(t, book_example, `//book[@category = "web"][2]`, 25) test_xpath_elements(t, book_example, `(//book[@category = "web"])[2]`, 25) } func TestPredicates(t *testing.T) { test_xpath_elements(t, employee_example, `//employee[name]`, 3, 8, 13) test_xpath_elements(t, employee_example, `/empinfo/employee[@id]`, 3, 8, 13) test_xpath_elements(t, book_example, `//book[@category = "web"]`, 15, 25) test_xpath_elements(t, book_example, `//book[author = "J K. Rowling"]`, 9) test_xpath_elements(t, book_example, `//book[./author/text() = "J K. Rowling"]`, 9) test_xpath_elements(t, book_example, `//book[year = 2005]`, 3, 9) test_xpath_elements(t, book_example, `//year[text() = 2005]`, 6, 12) test_xpath_elements(t, employee_example, `/empinfo/employee[1][@id=1]`, 3) test_xpath_elements(t, employee_example, `/empinfo/employee[@id][2]`, 8) } func TestOperators(t *testing.T) { test_xpath_elements(t, employee_example, `//designation[@discipline and @experience]`, 5, 10) test_xpath_elements(t, employee_example, `//designation[@discipline or @experience]`, 5, 10, 15) test_xpath_elements(t, employee_example, `//designation[@discipline | @experience]`, 5, 10, 15) test_xpath_elements(t, employee_example, `/empinfo/employee[@id != "2" ]`, 3, 13) test_xpath_elements(t, employee_example, `/empinfo/employee[@id and @id = "2"]`, 8) test_xpath_elements(t, employee_example, `/empinfo/employee[@id = "1" or @id = "2"]`, 3, 8) } func TestNestedPredicates(t *testing.T) { test_xpath_elements(t, employee_example, `//employee[./name[@from]]`, 8) test_xpath_elements(t, employee_example, `//employee[.//name[@from = "CA"]]`, 8) } golang-github-antchfx-xpath-1.3.3/xpath_test.go000066400000000000000000000553731473310030300215410ustar00rootroot00000000000000package xpath import ( "bytes" "fmt" "math" "sort" "strings" "testing" ) var ( employee_example = createEmployeeExample() book_example = createBookExample() html_example = createHtmlExample() empty_example = createNode("", RootNode) mybook_example = createMyBookExample() ) type testQuery string func (t testQuery) Select(_ iterator) NodeNavigator { panic("implement me") } func (t testQuery) Clone() query { return t } func (t testQuery) Evaluate(_ iterator) interface{} { return string(t) } func (t testQuery) ValueType() resultType { return xpathResultType.Any } func (t testQuery) Properties() queryProp { return queryProps.None } func test_xpath_elements(t *testing.T, root *TNode, expr string, expected ...int) { result := selectNodes(root, expr) assertEqual(t, len(expected), len(result)) for i := 0; i < len(expected); i++ { assertEqual(t, expected[i], result[i].lines) } } func test_xpath_values(t testing.TB, root *TNode, expr string, expected ...string) { result := selectNodes(root, expr) assertEqual(t, len(expected), len(result)) for i := 0; i < len(expected); i++ { assertEqual(t, expected[i], result[i].Value()) } } func test_xpath_tags(t *testing.T, root *TNode, expr string, expected ...string) { result := selectNodes(root, expr) assertEqual(t, len(expected), len(result)) for i := 0; i < len(expected); i++ { assertEqual(t, expected[i], result[i].Data) } } func test_xpath_count(t *testing.T, root *TNode, expr string, expected int) { result := selectNodes(root, expr) assertEqual(t, expected, len(result)) } func test_xpath_eval(t *testing.T, root *TNode, expr string, expected ...interface{}) { e, err := Compile(expr) assertNoErr(t, err) v := e.Evaluate(createNavigator(root)) // if is a node-set if iter, ok := v.(*NodeIterator); ok { got := iterateNavs(iter) assertEqual(t, len(expected), len(got)) for i := 0; i < len(expected); i++ { assertEqual(t, expected[i], got[i]) } return } assertEqual(t, expected[0], v) } func Test_Predicates_MultiParent(t *testing.T) { // https://github.com/antchfx/xpath/issues/75 /* field1 field2 31854 159773 field3 field4 1234 567 */ doc := createNode("", RootNode) measCollecFile := doc.createChildNode("measCollecFile", ElementNode) measData := measCollecFile.createChildNode("measData", ElementNode) data := []struct { measType map[string]string measValue map[string]string }{ {measType: map[string]string{"1": "field1", "2": "field2"}, measValue: map[string]string{"1": "31854", "2": "159773"}}, {measType: map[string]string{"1": "field3", "2": "field4"}, measValue: map[string]string{"1": "1234", "2": "567"}}, } for j := 0; j < len(data); j++ { d := data[j] measInfo := measData.createChildNode("measInfo", ElementNode) measType := measInfo.createChildNode("measType", ElementNode) var keys []string for k := range d.measType { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { measType.addAttribute("p", k) measType.createChildNode(d.measType[k], TextNode) } measValue := measInfo.createChildNode("measValue", ElementNode) keys = make([]string, 0) for k := range d.measValue { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { r := measValue.createChildNode("r", ElementNode) r.addAttribute("p", k) r.createChildNode(d.measValue[k], TextNode) } } test_xpath_values(t, doc, `//r[@p=../../measType/@p]`, "31854", "159773", "1234", "567") } func TestCompile(t *testing.T) { var err error _, err = Compile("//a") assertNil(t, err) _, err = Compile("//a[id=']/span") assertErr(t, err) _, err = Compile("//ul/li/@class") assertNil(t, err) _, err = Compile("/a/b/(c, .[not(c)])") assertNil(t, err) _, err = Compile("/pre:foo") assertNil(t, err) } func TestInvalidXPath(t *testing.T) { var err error _, err = Compile("()") assertErr(t, err) _, err = Compile("(1,2,3)") assertErr(t, err) } func TestCompileWithNS(t *testing.T) { _, err := CompileWithNS("/foo", nil) assertNil(t, err) _, err = CompileWithNS("/foo", map[string]string{}) assertNil(t, err) _, err = CompileWithNS("/foo", map[string]string{"a": "b"}) assertNil(t, err) _, err = CompileWithNS("/a:foo", map[string]string{"a": "b"}) assertNil(t, err) _, err = CompileWithNS("/u:foo", map[string]string{"a": "b"}) assertErr(t, err) } func TestNamespacePrefixQuery(t *testing.T) { /* book1 book2 book3 */ doc := createNode("", RootNode) books := doc.createChildNode("books", ElementNode) books.lines = 2 book1 := books.createChildNode("book", ElementNode) book1.lines = 3 book1.createChildNode("book1", TextNode) book2 := books.createChildNode("b:book", ElementNode) book2.lines = 4 book2.addAttribute("xmlns:b", "ns") book2.createChildNode("book2", TextNode) book3 := books.createChildNode("c:book", ElementNode) book3.lines = 5 book3.addAttribute("xmlns:c", "ns") book3.createChildNode("book3", TextNode) test_xpath_elements(t, doc, `//b:book`, 4) // expected [4 , 5] // With namespace bindings: exp, _ := CompileWithNS("//x:book", map[string]string{"x": "ns"}) nodes := iterateNodes(exp.Select(createNavigator(doc))) assertEqual(t, 2, len(nodes)) assertEqual(t, "book2", nodes[0].Value()) assertEqual(t, "book3", nodes[1].Value()) } func TestMustCompile(t *testing.T) { expr := MustCompile("//") assertTrue(t, expr != nil) if wanted := (nopQuery{}); expr.q != wanted { t.Fatalf("wanted nopQuery object but got %s", expr) } iter := expr.Select(createNavigator(html_example)) if iter.MoveNext() { t.Fatal("should be an empty node list but got one") } } func Test_plusFunc(t *testing.T) { // 1+1 assertEqual(t, float64(2), plusFunc(nil, float64(1), float64(1))) // string + assertEqual(t, float64(2), plusFunc(nil, "1", "1")) // invalid string v := plusFunc(nil, "a", 1) assertTrue(t, math.IsNaN(v.(float64))) // Nodeset // TODO } func Test_minusFunc(t *testing.T) { // 1 - 1 assertEqual(t, float64(0), minusFunc(nil, float64(1), float64(1))) // string assertEqual(t, float64(0), minusFunc(nil, "1", "1")) // invalid string v := minusFunc(nil, "a", 1) assertTrue(t, math.IsNaN(v.(float64))) } func TestNodeType(t *testing.T) { tests := []struct { expr string expected NodeType }{ {`//employee`, ElementNode}, {`//name[text()]`, ElementNode}, {`//name/text()`, TextNode}, {`//employee/@id`, AttributeNode}, } for _, test := range tests { v := selectNode(employee_example, test.expr) assertTrue(t, v != nil) assertEqual(t, test.expected, v.Type) } doc := createNode("", RootNode) doc.createChildNode("", CommentNode) n := selectNode(doc, "//comment()") assertTrue(t, n != nil) assertEqual(t, CommentNode, n.Type) } func iterateNavs(t *NodeIterator) []*TNodeNavigator { var nodes []*TNodeNavigator for t.MoveNext() { node := t.Current().(*TNodeNavigator) nodes = append(nodes, node) } return nodes } func iterateNodes(t *NodeIterator) []*TNode { var nodes []*TNode for t.MoveNext() { n := t.Current().(*TNodeNavigator) if n.NodeType() == AttributeNode { childNode := &TNode{ Type: TextNode, Data: n.Value(), } nodes = append(nodes, &TNode{ Parent: n.curr, Type: AttributeNode, Data: n.LocalName(), FirstChild: childNode, LastChild: childNode, }) } else { nodes = append(nodes, n.curr) } } return nodes } func selectNode(root *TNode, expr string) *TNode { list := selectNodes(root, expr) if len(list) == 0 { return nil } return list[0] } func selectNodes(root *TNode, expr string) []*TNode { t := Select(createNavigator(root), expr) c := make(map[uint64]bool) var list []*TNode for _, n := range iterateNodes(t) { m := getHashCode(createNavigator(n)) if _, ok := c[m]; ok { continue } c[m] = true list = append(list, n) } return list } func joinValues(nodes []*TNode) string { s := make([]string, 0) for _, n := range nodes { s = append(s, n.Value()) } return strings.Join(s, ",") } func createNavigator(n *TNode) *TNodeNavigator { return &TNodeNavigator{curr: n, root: n, attr: -1} } type Attribute struct { Key, Value string } type TNode struct { Parent, FirstChild, LastChild, PrevSibling, NextSibling *TNode Type NodeType Data string Attr []Attribute NamespaceURL string Prefix string lines int } func (n *TNode) Value() string { if n.Type == TextNode { return n.Data } var buff bytes.Buffer var output func(*TNode) output = func(node *TNode) { if node.Type == TextNode { buff.WriteString(node.Data) } for child := node.FirstChild; child != nil; child = child.NextSibling { output(child) } } output(n) return buff.String() } // TNodeNavigator is for navigating TNode. type TNodeNavigator struct { curr, root *TNode attr int } func (n *TNodeNavigator) NodeType() NodeType { switch n.curr.Type { case CommentNode: return CommentNode case TextNode: return TextNode case ElementNode: if n.attr != -1 { return AttributeNode } return ElementNode } return n.curr.Type } func (n *TNodeNavigator) LocalName() string { if n.attr != -1 { return n.curr.Attr[n.attr].Key } name := n.curr.Data if strings.Contains(name, ":") { return strings.Split(name, ":")[1] } return name } func (n *TNodeNavigator) Prefix() string { if n.attr == -1 && strings.Contains(n.curr.Data, ":") { return strings.Split(n.curr.Data, ":")[0] } return n.curr.Prefix } func (n *TNodeNavigator) NamespaceURL() string { if n.Prefix() != "" { for _, a := range n.curr.Attr { if a.Key == "xmlns:"+n.Prefix() { return a.Value } } } return n.curr.NamespaceURL } func (n *TNodeNavigator) Value() string { switch n.curr.Type { case CommentNode: return n.curr.Data case ElementNode: if n.attr != -1 { return n.curr.Attr[n.attr].Value } var buf bytes.Buffer node := n.curr.FirstChild for node != nil { if node.Type == TextNode { buf.WriteString(strings.TrimSpace(node.Data)) } node = node.NextSibling } return buf.String() case TextNode: return n.curr.Data } return "" } func (n *TNodeNavigator) Copy() NodeNavigator { n2 := *n return &n2 } func (n *TNodeNavigator) MoveToRoot() { n.curr = n.root } func (n *TNodeNavigator) MoveToParent() bool { if n.attr != -1 { n.attr = -1 return true } else if node := n.curr.Parent; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveToNextAttribute() bool { if n.attr >= len(n.curr.Attr)-1 { return false } n.attr++ return true } func (n *TNodeNavigator) MoveToChild() bool { if n.attr != -1 { return false } if node := n.curr.FirstChild; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveToFirst() bool { if n.attr != -1 || n.curr.PrevSibling == nil { return false } for { node := n.curr.PrevSibling if node == nil { break } n.curr = node } return true } func (n *TNodeNavigator) String() string { return n.Value() } func (n *TNodeNavigator) MoveToNext() bool { if n.attr != -1 { return false } if node := n.curr.NextSibling; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveToPrevious() bool { if n.attr != -1 { return false } if node := n.curr.PrevSibling; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveTo(other NodeNavigator) bool { node, ok := other.(*TNodeNavigator) if !ok || node.root != n.root { return false } n.curr = node.curr n.attr = node.attr return true } func createNode(data string, typ NodeType) *TNode { return &TNode{Data: data, Type: typ, Attr: make([]Attribute, 0)} } func (n *TNode) createChildNode(data string, typ NodeType) *TNode { m := createNode(data, typ) m.Parent = n if n.FirstChild == nil { n.FirstChild = m } else { n.LastChild.NextSibling = m m.PrevSibling = n.LastChild } n.LastChild = m return m } func (n *TNode) appendNode(data string, typ NodeType) *TNode { m := createNode(data, typ) m.Parent = n.Parent n.NextSibling = m m.PrevSibling = n if n.Parent != nil { n.Parent.LastChild = m } return m } func (n *TNode) addAttribute(k, v string) { n.Attr = append(n.Attr, Attribute{k, v}) } func (n *TNode) getAttribute(key string) string { for i := 0; i < len(n.Attr); i++ { if n.Attr[i].Key == key { return n.Attr[i].Value } } return "" } func createBookExample() *TNode { /* Everyday Italian Giada De Laurentiis 2005 30.00 Harry Potter J K. Rowling 2005 29.99 XQuery Kick Start James McGovern Per Bothner Kurt Cagle James Linn Vaidyanathan Nagarajan 2003 49.99 Learning XML Erik T. Ray 2003 39.95 */ type Element struct { Data string Attributes map[string]string } books := []struct { category string title Element year int price float64 authors []string }{ { category: "cooking", title: Element{"Everyday Italian", map[string]string{"lang": "en"}}, year: 2005, price: 30.00, authors: []string{"Giada De Laurentiis"}, }, { category: "children", title: Element{"Harry Potter", map[string]string{"lang": "en"}}, year: 2005, price: 29.99, authors: []string{"J K. Rowling"}, }, { category: "web", title: Element{"XQuery Kick Start", map[string]string{"lang": "en"}}, year: 2003, price: 49.99, authors: []string{"James McGovern", "Per Bothner", "Kurt Cagle", "James Linn", "Vaidyanathan Nagarajan"}, }, { category: "web", title: Element{"Learning XML", map[string]string{"lang": "en"}}, year: 2003, price: 39.95, authors: []string{"Erik T. Ray"}, }, } var lines = 0 doc := createNode("", RootNode) lines++ bookstore := doc.createChildNode("bookstore", ElementNode) lines++ bookstore.lines = lines for i := 0; i < len(books); i++ { v := books[i] lines++ book := bookstore.createChildNode("book", ElementNode) book.lines = lines lines++ book.addAttribute("category", v.category) // title title := book.createChildNode("title", ElementNode) title.lines = lines lines++ for k, v := range v.title.Attributes { title.addAttribute(k, v) } title.createChildNode(v.title.Data, TextNode) // authors for j := 0; j < len(v.authors); j++ { author := book.createChildNode("author", ElementNode) author.lines = lines lines++ author.createChildNode(v.authors[j], TextNode) } // year year := book.createChildNode("year", ElementNode) year.lines = lines lines++ year.createChildNode(fmt.Sprintf("%d", v.year), TextNode) // price price := book.createChildNode("price", ElementNode) price.lines = lines lines++ price.createChildNode(fmt.Sprintf("%.2f", v.price), TextNode) } return doc } // The example document from https://way2tutorial.com/xml/xpath-node-test.php func createEmployeeExample() *TNode { /* Opal Kole Senior Engineer OpalKole@myemail.com Max Miller DBA Engineer maxmiller@email.com Beccaa Moss Application Developer beccaamoss@email.com */ type Element struct { Data string Attributes map[string]string } var lines = 0 doc := createNode("", RootNode) lines++ // 1 empinfo := doc.createChildNode("empinfo", ElementNode) lines++ empinfo.lines = lines var employees = []struct { name Element designation Element email Element }{ { name: Element{Data: "Opal Kole"}, designation: Element{Data: "Senior Engineer", Attributes: map[string]string{ "discipline": "web", "experience": "3 year", }}, email: Element{Data: "OpalKole@myemail.com"}, }, { name: Element{Data: "Max Miller", Attributes: map[string]string{"from": "CA"}}, designation: Element{Data: "DBA Engineer", Attributes: map[string]string{ "discipline": "DBA", "experience": "2 year", }}, email: Element{Data: "maxmiller@email.com"}, }, { name: Element{Data: "Beccaa Moss"}, designation: Element{Data: "Application Developer", Attributes: map[string]string{ "discipline": "appdev", }}, email: Element{Data: "beccaamoss@email.com"}, }, } for i := 0; i < len(employees); i++ { v := employees[i] lines++ // employee employee := empinfo.createChildNode("employee", ElementNode) employee.addAttribute("id", fmt.Sprintf("%d", i+1)) employee.lines = lines lines++ // name name := employee.createChildNode("name", ElementNode) name.createChildNode(v.name.Data, TextNode) for k, n := range v.name.Attributes { name.addAttribute(k, n) } name.lines = lines lines++ // designation designation := employee.createChildNode("designation", ElementNode) designation.createChildNode(v.designation.Data, TextNode) for k, n := range v.designation.Attributes { designation.addAttribute(k, n) } designation.lines = lines lines++ // email email := employee.createChildNode("email", ElementNode) email.createChildNode(v.email.Data, TextNode) for k, n := range v.email.Attributes { email.addAttribute(k, n) } email.lines = lines // skiping closed tag lines++ } return doc } func createHtmlExample() *TNode { /* My page

Welcome to my page

This is the first paragraph.

*/ lines := 0 doc := createNode("", RootNode) lines++ xhtml := doc.createChildNode("html", ElementNode) xhtml.lines = lines xhtml.addAttribute("lang", "en") lines++ // head container head := xhtml.createChildNode("head", ElementNode) head.lines = lines lines++ title := head.createChildNode("title", ElementNode) title.lines = lines title.createChildNode("My page", TextNode) lines++ meta := head.createChildNode("meta", ElementNode) meta.lines = lines meta.addAttribute("name", "language") meta.addAttribute("content", "en") // skip the head lines++ lines++ body := xhtml.createChildNode("body", ElementNode) body.lines = lines lines++ h2 := body.createChildNode("h2", ElementNode) h2.lines = lines h2.createChildNode("Welcome to my page", TextNode) lines++ links := []struct { text string href string }{ {text: "Home", href: "/"}, {text: "About", href: "/About"}, {text: "Login", href: "/account"}, } ul := body.createChildNode("ul", ElementNode) ul.lines = lines lines++ for i := 0; i < len(links); i++ { link := links[i] li := ul.createChildNode("li", ElementNode) li.lines = lines lines++ a := li.createChildNode("a", ElementNode) a.lines = lines a.addAttribute("href", link.href) a.createChildNode(link.text, TextNode) lines++ // skip the
  • lines++ } // skip the last ul lines++ p := body.createChildNode("p", ElementNode) p.lines = lines lines++ p.createChildNode("This is the first paragraph.", TextNode) lines++ comment := body.createChildNode("", CommentNode) comment.lines = lines lines++ return doc } func createMyBookExample() *TNode { /* XML Developer's Guide Gambardella, Matthew 44.95 2000-10-01 Midnight Rain Ralls, Kim 5.95 2000-12-16 */ var ( prefix string = "mybook" namespaceURL string = "http://www.contoso.com/books" ) lines := 1 doc := createNode("", RootNode) doc.lines = lines lines++ books := doc.createChildNode("books", ElementNode) books.addAttribute("xmlns:mybook", namespaceURL) books.lines = lines lines++ data := []struct { id string title string author string price float64 publish string }{ {id: "bk101", title: "XML Developer's Guide", author: "Gambardella, Matthew", price: 44.95, publish: "2000-10-01"}, {id: "bk102", title: "Midnight Rain", author: "Ralls, Kim", price: 5.95, publish: "2000-12-16"}, } for i := 0; i < len(data); i++ { v := data[i] book := books.createChildNode("book", ElementNode) book.addAttribute("id", v.id) book.Prefix = prefix book.NamespaceURL = namespaceURL book.lines = lines lines++ title := book.createChildNode("title", ElementNode) title.createChildNode(v.title, TextNode) title.lines = lines lines++ author := book.createChildNode("author", ElementNode) author.createChildNode(v.author, TextNode) author.lines = lines lines++ price := book.createChildNode("price", ElementNode) price.createChildNode(fmt.Sprintf("%.2f", v.price), TextNode) price.lines = lines lines++ publish_date := book.createChildNode("publish_date", ElementNode) publish_date.createChildNode(v.publish, TextNode) publish_date.lines = lines lines++ // skip the last of book element lines++ } return doc }