pax_global_header00006660000000000000000000000064135764065710014530gustar00rootroot0000000000000052 comment=95554a172305da95b4fb4211052c8060368029c4 golang-github-antchfx-xpath-1.1.2/000077500000000000000000000000001357640657100170335ustar00rootroot00000000000000golang-github-antchfx-xpath-1.1.2/.gitignore000066400000000000000000000004621357640657100210250ustar00rootroot00000000000000# 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.1.2/.travis.yml000066400000000000000000000002271357640657100211450ustar00rootroot00000000000000language: go go: - 1.6 - 1.9 - '1.10' install: - go get github.com/mattn/goveralls script: - $HOME/gopath/bin/goveralls -service=travis-cigolang-github-antchfx-xpath-1.1.2/LICENSE000066400000000000000000000017761357640657100200530ustar00rootroot00000000000000Permission 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.1.2/README.md000066400000000000000000000121261357640657100203140ustar00rootroot00000000000000XPath ==== [![GoDoc](https://godoc.org/github.com/antchfx/xpath?status.svg)](https://godoc.org/github.com/antchfx/xpath) [![Coverage Status](https://coveralls.io/repos/github/antchfx/xpath/badge.svg?branch=master)](https://coveralls.io/github/antchfx/xpath?branch=master) [![Build Status](https://travis-ci.org/antchfx/xpath.svg?branch=master)](https://travis-ci.org/antchfx/xpath) [![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 #### Node Axes - `child::*` : The child axis selects children of the current 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 Add * a - b Substract * a * b Multiply * a div b Divide * a mod b Floating point mod, like Java. - `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()`| ✓ | `name()`| ✓ | `namespace-uri()`| ✓ | `normalize-space()`| ✓ | `not()`| ✓ | `number()`| ✓ | `position()`| ✓ | `round()`| ✓ | `starts-with()`| ✓ | `string()`| ✓ | `string-length()`| ✓ | `substring()`| ✓ | `substring-after()`| ✓ | `substring-before()`| ✓ | `sum()`| ✓ | `system-property()`| ✗ | `translate()`| ✓ | `true()`| ✓ | `unparsed-entity-url()` | ✗ | Changelogs === 2019-03-19 - optimize XPath `|` operation performance. [#33](https://github.com/antchfx/xpath/issues/33). Tips: suggest split into multiple subquery if you have a lot of `|` operations. 2019-01-29 - improvement `normalize-space` function. [#32](https://github.com/antchfx/xpath/issues/32) 2018-12-07 - supports XPath 2.0 Sequence expressions. [#30](https://github.com/antchfx/xpath/pull/30) by [@minherz](https://github.com/minherz).golang-github-antchfx-xpath-1.1.2/build.go000066400000000000000000000310501357640657100204600ustar00rootroot00000000000000package xpath import ( "errors" "fmt" ) type flag int const ( noneFlag flag = iota filterFlag ) // builder provides building an XPath expressions. type builder struct { depth int flag flag firstInput query } // axisPredicate creates a predicate to predicating for this axis node. func axisPredicate(root *axisNode) func(NodeNavigator) bool { // get current axix node type. typ := ElementNode switch root.AxeType { case "attribute": typ = AttributeNode case "self", "parent": typ = allNode default: switch root.Prop { case "comment": typ = CommentNode case "text": typ = TextNode // case "processing-instruction": // typ = ProcessingInstructionNode case "node": typ = allNode } } nametest := root.LocalName != "" || root.Prefix != "" predicate := func(n NodeNavigator) bool { if typ == n.NodeType() || typ == allNode || typ == TextNode { if nametest { if root.LocalName == n.LocalName() && root.Prefix == n.Prefix() { return true } } else { return true } } return false } return predicate } // processAxisNode processes a query for the XPath axis node. func (b *builder) processAxisNode(root *axisNode) (query, error) { var ( err error qyInput query qyOutput query predicate = axisPredicate(root) ) if root.Input == nil { qyInput = &contextQuery{} } else { if root.AxeType == "child" && (root.Input.Type() == nodeAxis) { if input := root.Input.(*axisNode); input.AxeType == "descendant-or-self" { var qyGrandInput query if input.Input != nil { qyGrandInput, _ = b.processNode(input.Input) } else { qyGrandInput = &contextQuery{} } qyOutput = &descendantQuery{Input: qyGrandInput, Predicate: predicate, Self: true} return qyOutput, nil } } qyInput, err = b.processNode(root.Input) if err != nil { return nil, err } } switch root.AxeType { case "ancestor": qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate} case "ancestor-or-self": qyOutput = &ancestorQuery{Input: qyInput, Predicate: predicate, Self: true} case "attribute": qyOutput = &attributeQuery{Input: qyInput, Predicate: predicate} case "child": filter := func(n NodeNavigator) bool { v := predicate(n) switch root.Prop { case "text": v = v && n.NodeType() == TextNode case "node": v = v && (n.NodeType() == ElementNode || n.NodeType() == TextNode) case "comment": v = v && n.NodeType() == CommentNode } return v } qyOutput = &childQuery{Input: qyInput, Predicate: filter} case "descendant": qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate} case "descendant-or-self": qyOutput = &descendantQuery{Input: qyInput, Predicate: predicate, Self: true} case "following": qyOutput = &followingQuery{Input: qyInput, Predicate: predicate} 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} 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.AxeType) return nil, err } return qyOutput, nil } // processFilterNode builds query for the XPath filter predicate. func (b *builder) processFilterNode(root *filterNode) (query, error) { b.flag |= filterFlag qyInput, err := b.processNode(root.Input) if err != nil { return nil, err } qyCond, err := b.processNode(root.Condition) if err != nil { return nil, err } qyOutput := &filterQuery{Input: qyInput, Predicate: qyCond} return qyOutput, nil } // processFunctionNode processes query for the XPath function node. func (b *builder) processFunctionNode(root *functionNode) (query, error) { var qyOutput query switch root.FuncName { case "starts-with": arg1, err := b.processNode(root.Args[0]) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1]) if err != nil { return nil, err } qyOutput = &functionQuery{Input: b.firstInput, Func: startwithFunc(arg1, arg2)} case "ends-with": arg1, err := b.processNode(root.Args[0]) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1]) if err != nil { return nil, err } qyOutput = &functionQuery{Input: b.firstInput, Func: endwithFunc(arg1, arg2)} case "contains": arg1, err := b.processNode(root.Args[0]) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1]) if err != nil { return nil, err } qyOutput = &functionQuery{Input: b.firstInput, Func: containsFunc(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]); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1]); err != nil { return nil, err } if len(root.Args) == 3 { if arg3, err = b.processNode(root.Args[2]); err != nil { return nil, err } } qyOutput = &functionQuery{Input: b.firstInput, 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]); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1]); err != nil { return nil, err } qyOutput = &functionQuery{ Input: b.firstInput, 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]) if err != nil { return nil, err } qyOutput = &functionQuery{Input: b.firstInput, Func: stringLengthFunc(arg1)} case "normalize-space": if len(root.Args) == 0 { return nil, errors.New("xpath: normalize-space function must have at least one parameter") } argQuery, err := b.processNode(root.Args[0]) if err != nil { return nil, err } qyOutput = &functionQuery{Input: argQuery, Func: normalizespaceFunc} 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]); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1]); err != nil { return nil, err } if arg3, err = b.processNode(root.Args[2]); err != nil { return nil, err } qyOutput = &functionQuery{Input: b.firstInput, 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]) if err != nil { return nil, err } qyOutput = &functionQuery{Input: argQuery, Func: notFunc} case "name", "local-name", "namespace-uri": inp := b.firstInput 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]) if err != nil { return nil, err } inp = argQuery } f := &functionQuery{Input: inp} switch root.FuncName { case "name": f.Func = nameFunc case "local-name": f.Func = localNameFunc case "namespace-uri": f.Func = namespaceFunc } qyOutput = f case "true", "false": val := root.FuncName == "true" qyOutput = &functionQuery{ Input: b.firstInput, Func: func(_ query, _ iterator) interface{} { return val }, } case "last": qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc} case "position": qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc} case "boolean", "number", "string": inp := b.firstInput 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]) if err != nil { return nil, err } inp = argQuery } f := &functionQuery{Input: inp} switch root.FuncName { case "boolean": f.Func = booleanFunc case "string": f.Func = stringFunc case "number": f.Func = numberFunc } qyOutput = f case "count": //if b.firstInput == nil { // return nil, errors.New("xpath: expression must evaluate to node-set") //} if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0]) if err != nil { return nil, err } qyOutput = &functionQuery{Input: argQuery, Func: countFunc} 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]) if err != nil { return nil, err } qyOutput = &functionQuery{Input: argQuery, Func: sumFunc} 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]) if err != nil { return nil, err } f := &functionQuery{Input: argQuery} switch root.FuncName { case "ceiling": f.Func = ceilingFunc case "floor": f.Func = floorFunc case "round": f.Func = roundFunc } qyOutput = f 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) if err != nil { return nil, err } args = append(args, q) } qyOutput = &functionQuery{Input: b.firstInput, Func: concatFunc(args...)} default: return nil, fmt.Errorf("not yet support this function %s()", root.FuncName) } return qyOutput, nil } func (b *builder) processOperatorNode(root *operatorNode) (query, error) { left, err := b.processNode(root.Left) if err != nil { return nil, err } right, err := b.processNode(root.Right) if err != nil { return nil, err } var qyOutput query switch root.Op { case "+", "-", "div", "mod": // Numeric operator var exprFunc func(interface{}, interface{}) interface{} switch root.Op { case "+": exprFunc = plusFunc case "-": exprFunc = minusFunc 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 "|": qyOutput = &unionQuery{Left: left, Right: right} } return qyOutput, nil } func (b *builder) processNode(root node) (q query, err error) { if b.depth = b.depth + 1; b.depth > 1024 { err = errors.New("the xpath expressions is too complex") return } switch root.Type() { case nodeConstantOperand: n := root.(*operandNode) q = &constantQuery{Val: n.Val} case nodeRoot: q = &contextQuery{Root: true} case nodeAxis: q, err = b.processAxisNode(root.(*axisNode)) b.firstInput = q case nodeFilter: q, err = b.processFilterNode(root.(*filterNode)) case nodeFunction: q, err = b.processFunctionNode(root.(*functionNode)) case nodeOperator: q, err = b.processOperatorNode(root.(*operatorNode)) } return } // build builds a specified XPath expressions expr. func build(expr 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) b := &builder{} return b.processNode(root) } golang-github-antchfx-xpath-1.1.2/doc_test.go000066400000000000000000000013131357640657100211640ustar00rootroot00000000000000package 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.1.2/func.go000066400000000000000000000253661357640657100203310ustar00rootroot00000000000000package xpath import ( "errors" "fmt" "math" "regexp" "strconv" "strings" ) // 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(q query, t iterator) interface{} { var ( count = 1 node = t.Current() ) test := predicate(q) for node.MoveToPrevious() { if test(node) { count++ } } return float64(count) } // lastFunc is a XPath Node Set functions last(). func lastFunc(q query, t iterator) interface{} { var ( count = 0 node = t.Current() ) node.MoveToFirst() test := predicate(q) for { if test(node) { count++ } if !node.MoveToNext() { break } } return float64(count) } // countFunc is a XPath Node Set functions count(node-set). func countFunc(q query, t iterator) interface{} { var count = 0 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(q query, t iterator) interface{} { var sum float64 switch typ := q.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 float64(0) } 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 { panic(errors.New("ceiling() function argument type must be a node-set or number")) } return v } return 0 } // ceilingFunc is a XPath Node Set functions ceiling(node-set). func ceilingFunc(q query, t iterator) interface{} { val := asNumber(t, q.Evaluate(t)) return math.Ceil(val) } // floorFunc is a XPath Node Set functions floor(node-set). func floorFunc(q query, t iterator) interface{} { val := asNumber(t, q.Evaluate(t)) return math.Floor(val) } // roundFunc is a XPath Node Set functions round(node-set). func roundFunc(q query, t iterator) interface{} { val := asNumber(t, q.Evaluate(t)) //return math.Round(val) return round(val) } // nameFunc is a XPath functions name([node-set]). func nameFunc(q query, t iterator) interface{} { v := q.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(q query, t iterator) interface{} { v := q.Select(t) if v == nil { return "" } return v.LocalName() } // namespaceFunc is a XPath functions namespace-uri([node-set]). func namespaceFunc(q query, t iterator) interface{} { v := q.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 bool(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(q query, t iterator) interface{} { v := q.Evaluate(t) return asBool(t, v) } // numberFunc is a XPath functions number([node-set]). func numberFunc(q query, t iterator) interface{} { v := q.Evaluate(t) return asNumber(t, v) } // stringFunc is a XPath functions string([node-set]). func stringFunc(q query, t iterator) interface{} { v := q.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(q query, t iterator) interface{} { var ( m, n string ok bool ) switch typ := 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 = 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(q query, t iterator) interface{} { var ( m, n string ok bool ) switch typ := 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 = 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(q query, t iterator) interface{} { var ( m, n string ok bool ) switch typ := 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 = arg2.Evaluate(t).(string) if !ok { panic(errors.New("contains() function argument type must be string")) } return strings.Contains(m, n) } } var ( regnewline = regexp.MustCompile(`[\r\n\t]`) regseqspace = regexp.MustCompile(`\s{2,}`) ) // normalizespaceFunc is XPath functions normalize-space(string?) func normalizespaceFunc(q query, t iterator) interface{} { var m string switch typ := q.Evaluate(t).(type) { case string: m = typ case query: node := typ.Select(t) if node == nil { return "" } m = node.Value() } m = strings.TrimSpace(m) m = regnewline.ReplaceAllString(m, " ") m = regseqspace.ReplaceAllString(m, " ") return m } // 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(q query, t iterator) interface{} { var m string switch typ := 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 = 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 = 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(q query, t iterator) interface{} { var str string switch v := 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 := 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(q query, t iterator) interface{} { switch v := 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(q query, t iterator) interface{} { str := asString(t, arg1.Evaluate(t)) src := asString(t, arg2.Evaluate(t)) dst := asString(t, arg3.Evaluate(t)) var replace []string 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) } } // notFunc is XPATH functions not(expression) function operation. func notFunc(q query, t iterator) interface{} { switch v := q.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(q query, t iterator) interface{} { var a []string for _, v := range args { switch v := v.Evaluate(t).(type) { case string: a = append(a, v) case query: node := v.Select(t) if node != nil { a = append(a, node.Value()) } } } return strings.Join(a, "") } } golang-github-antchfx-xpath-1.1.2/func_go110.go000066400000000000000000000001511357640657100212210ustar00rootroot00000000000000// +build go1.10 package xpath import "math" func round(f float64) int { return int(math.Round(f)) } golang-github-antchfx-xpath-1.1.2/func_pre_go110.go000066400000000000000000000004451357640657100220750ustar00rootroot00000000000000// +build !go1.10 package xpath import "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)) } golang-github-antchfx-xpath-1.1.2/operator.go000066400000000000000000000135671357640657100212310ustar00rootroot00000000000000package xpath import ( "fmt" "reflect" "strconv" ) // The XPath number operator function list. // valueType is a return value type. type valueType int const ( booleanType valueType = iota numberType stringType nodeSetType ) func getValueType(i interface{}) valueType { v := reflect.ValueOf(i) switch v.Kind() { case reflect.Float64: return numberType case reflect.String: return stringType case reflect.Bool: return booleanType default: if _, ok := i.(query); ok { return nodeSetType } } panic(fmt.Errorf("xpath unknown value type: %v", v.Kind())) } type logical func(iterator, string, interface{}, interface{}) bool var logicalFuncs = [][]logical{ {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 { return false } 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 := getValueType(m) t2 := getValueType(n) return logicalFuncs[t1][t2](t, "=", m, n) } // gtFunc is an `>` operator. func gtFunc(t iterator, m, n interface{}) interface{} { t1 := getValueType(m) t2 := getValueType(n) return logicalFuncs[t1][t2](t, ">", m, n) } // geFunc is an `>=` operator. func geFunc(t iterator, m, n interface{}) interface{} { t1 := getValueType(m) t2 := getValueType(n) return logicalFuncs[t1][t2](t, ">=", m, n) } // ltFunc is an `<` operator. func ltFunc(t iterator, m, n interface{}) interface{} { t1 := getValueType(m) t2 := getValueType(n) return logicalFuncs[t1][t2](t, "<", m, n) } // leFunc is an `<=` operator. func leFunc(t iterator, m, n interface{}) interface{} { t1 := getValueType(m) t2 := getValueType(n) return logicalFuncs[t1][t2](t, "<=", m, n) } // neFunc is an `!=` operator. func neFunc(t iterator, m, n interface{}) interface{} { t1 := getValueType(m) t2 := getValueType(n) return logicalFuncs[t1][t2](t, "!=", m, n) } // orFunc is an `or` operator. var orFunc = func(t iterator, m, n interface{}) interface{} { t1 := getValueType(m) t2 := getValueType(n) return logicalFuncs[t1][t2](t, "or", m, n) } func numericExpr(m, n interface{}, cb func(float64, float64) float64) float64 { typ := reflect.TypeOf(float64(0)) a := reflect.ValueOf(m).Convert(typ) b := reflect.ValueOf(n).Convert(typ) return cb(a.Float(), b.Float()) } // plusFunc is an `+` operator. var plusFunc = func(m, n interface{}) interface{} { return numericExpr(m, n, func(a, b float64) float64 { return a + b }) } // minusFunc is an `-` operator. var minusFunc = func(m, n interface{}) interface{} { return numericExpr(m, n, func(a, b float64) float64 { return a - b }) } // mulFunc is an `*` operator. var mulFunc = func(m, n interface{}) interface{} { return numericExpr(m, n, func(a, b float64) float64 { return a * b }) } // divFunc is an `DIV` operator. var divFunc = func(m, n interface{}) interface{} { return numericExpr(m, n, func(a, b float64) float64 { return a / b }) } // modFunc is an 'MOD' operator. var modFunc = func(m, n interface{}) interface{} { return numericExpr(m, n, func(a, b float64) float64 { return float64(int(a) % int(b)) }) } golang-github-antchfx-xpath-1.1.2/parse.go000066400000000000000000000625711357640657100205070ustar00rootroot00000000000000package xpath import ( "bytes" "errors" "fmt" "strconv" "unicode" ) // 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 ) type parser struct { r *scanner d int } // 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(axeTyp, localName, prefix, prop string, n node) node { return &axisNode{ nodeType: nodeAxis, LocalName: localName, Prefix: prefix, AxeType: axeTyp, Prop: prop, Input: n, } } // 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} } // 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", "", "", "", 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", "", "", "", 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", "", "", "", opnd) case itemSlash: p.next() default: break Loop } } return opnd } // Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep func (p *parser) parseStep(n node) (opnd node) { axeTyp := "child" // default axes value. if p.r.typ == itemDot || p.r.typ == itemDotDot { if p.r.typ == itemDot { axeTyp = "self" } else { axeTyp = "parent" } p.next() opnd = newAxisNode(axeTyp, "", "", "", n) if p.r.typ != itemLBracket { return opnd } } else { switch p.r.typ { case itemAt: p.next() axeTyp = "attribute" case itemAxe: axeTyp = p.r.name p.next() case itemLParens: return p.parseSequence(n) } opnd = p.parseNodeTest(n, axeTyp) } 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) (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) opnd = newAxisNode(axeTyp, name, "", prop, n) } else { prefix := p.r.prefix name := p.r.name p.next() if p.r.name == "*" { name = "" } opnd = newAxisNode(axeTyp, name, prefix, "", n) } case itemStar: opnd = newAxisNode(axeTyp, "", "", "", 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) 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) node { r := &scanner{text: expr} r.nextChar() r.nextItem() p := &parser{r: r} 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] AxeType string // name of the axes.[attribute|ancestor|child|....] LocalName string // local part name of node. Prefix string // prefix name of node. } func (a *axisNode) String() string { var b bytes.Buffer if a.AxeType != "" { b.Write([]byte(a.AxeType + "::")) } 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) } // 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 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) return false } s.curr = rune(s.text[s.pos]) s.pos++ 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 ( c = 0 end = s.curr ) s.nextChar() i := s.pos - 1 for s.curr != end { if !s.nextChar() { panic(errors.New("xpath: scanString got unclosed string")) } c++ } s.nextChar() return s.text[i : i+c] } func (s *scanner) scanName() string { var ( c int i = s.pos - 1 ) for isName(s.curr) { c++ if !s.nextChar() { break } } 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.1.2/query.go000066400000000000000000000411661357640657100205370ustar00rootroot00000000000000package xpath import ( "bytes" "fmt" "hash/fnv" "reflect" ) 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 } // nopQuery is an empty query that always return nil for any query. type nopQuery struct { query } func (nopQuery) Select(iterator) NodeNavigator { return nil } func (nopQuery) Evaluate(iterator) interface{} { return nil } func (nopQuery) Clone() query { return nopQuery{} } // contextQuery is returns current node on the iterator object query. type contextQuery struct { count int Root bool // Moving to root-level node in the current context iterator. } func (c *contextQuery) Select(t iterator) (n NodeNavigator) { if c.count == 0 { c.count++ n = t.Current().Copy() if c.Root { n.MoveToRoot() } } return n } func (c *contextQuery) Evaluate(iterator) interface{} { c.count = 0 return c } func (c *contextQuery) Clone() query { return &contextQuery{count: 0, Root: c.Root} } // ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*) type ancestorQuery struct { iterator func() NodeNavigator Self bool Input query Predicate func(NodeNavigator) bool } func (a *ancestorQuery) Select(t iterator) NodeNavigator { for { if a.iterator == nil { node := a.Input.Select(t) if node == nil { return nil } first := true a.iterator = func() NodeNavigator { if first && a.Self { first = false if a.Predicate(node) { return node } } for node.MoveToParent() { if !a.Predicate(node) { continue } return node } return nil } } if node := a.iterator(); node != nil { 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{Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate} } // attributeQuery is an XPath attribute node query.(@*) type attributeQuery struct { 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{Input: a.Input.Clone(), Predicate: a.Predicate} } // childQuery is an XPath child node query.(child::*) type childQuery struct { 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{Input: c.Input.Clone(), Predicate: c.Predicate} } // position returns a position of current NodeNavigator. func (c *childQuery) position() int { return c.posit } // descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*) type descendantQuery struct { iterator func() NodeNavigator posit 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() level := 0 positmap := make(map[int]int) first := true d.iterator = func() NodeNavigator { if first && d.Self { first = false if d.Predicate(node) { d.posit = 1 positmap[level] = 1 return node } } for { if node.MoveToChild() { level++ positmap[level] = 0 } else { for { if level == 0 { return nil } if node.MoveToNext() { break } node.MoveToParent() level-- } } if d.Predicate(node) { positmap[level]++ d.posit = positmap[level] return node } } } } if node := d.iterator(); node != nil { 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) Clone() query { return &descendantQuery{Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate} } // 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) 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) 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) 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} } // filterQuery is an XPath query for predicate filter. type filterQuery struct { Input query Predicate query posit 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 q, ok := f.Predicate.(query); ok { return q.Select(t) != nil } } return false } func (f *filterQuery) position() int { return f.posit } func (f *filterQuery) Select(t iterator) NodeNavigator { for { node := f.Input.Select(t) if node == nil { return node } node = node.Copy() t.Current().MoveTo(node) if f.do(t) { f.posit++ return node } f.posit = 0 } } 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()} } // functionQuery is an XPath function that call a function to returns // value of current NodeNavigator node. 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 { return &functionQuery{Input: f.Input.Clone(), Func: f.Func} } // 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 } // 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} } // numericQuery is an XPath numeric operator expression. type numericQuery struct { Left, Right query Do func(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(m, k) } func (n *numericQuery) Clone() query { return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do} } 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{} { m := b.Left.Evaluate(t) left := asBool(t, m) if b.IsOr && left { return true } else if !b.IsOr && !left { return false } 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()} } 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 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())) if n.MoveToParent() { sb.WriteString(n.LocalName()) } 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([]byte(sb.String())) return h.Sum64() } func getNodePosition(q query) int { type Position interface { position() int } if count, ok := q.(Position); ok { return count.position() } return 1 } golang-github-antchfx-xpath-1.1.2/xpath.go000066400000000000000000000076071357640657100205200ustar00rootroot00000000000000package xpath import ( "errors" ) // 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 { if !t.node.MoveTo(n) { t.node = n.Copy() } return true } return false } // 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) if err != nil { return nil, err } 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 } golang-github-antchfx-xpath-1.1.2/xpath_test.go000066400000000000000000000522501357640657100215510ustar00rootroot00000000000000package xpath import ( "bytes" "strings" "testing" ) var ( html = example() html2 = example2() ) func TestCompile(t *testing.T) { var err error _, err = Compile("//a") if err != nil { t.Fatalf("//a should be correct but got error %s", err) } _, err = Compile("//a[id=']/span") if err == nil { t.Fatal("//a[id=] should be got correct but is nil") } _, err = Compile("//ul/li/@class") if err != nil { t.Fatalf("//ul/li/@class should be correct but got error %s", err) } _, err = Compile("/a/b/(c, .[not(c)])") if err != nil { t.Fatalf("/a/b/(c, .[not(c)]) should be correct but got error %s", err) } } func TestMustCompile(t *testing.T) { expr := MustCompile("//") if expr == nil { t.Fatal("// should be compiled but got nil object") } if wanted := (nopQuery{}); expr.q != wanted { t.Fatalf("wanted nopQuery object but got %s", expr) } iter := expr.Select(createNavigator(html)) if iter.MoveNext() { t.Fatal("should be an empty node list but got one") } } func TestSelf(t *testing.T) { testXPath(t, html, ".", "html") testXPath(t, html.FirstChild, ".", "head") testXPath(t, html, "self::*", "html") testXPath(t, html.LastChild, "self::body", "body") testXPath2(t, html, "//body/./ul/li/a", 3) } func TestParent(t *testing.T) { testXPath(t, html.LastChild, "..", "html") testXPath(t, html.LastChild, "parent::*", "html") a := selectNode(html, "//li/a") testXPath(t, a, "parent::*", "li") testXPath(t, html, "//title/parent::head", "head") } func TestAttribute(t *testing.T) { testXPath(t, html, "@lang='en'", "html") testXPath2(t, html, "@lang='zh'", 0) testXPath2(t, html, "//@href", 3) testXPath2(t, html, "//a[@*]", 3) } func TestSequence(t *testing.T) { testXPath2(t, html2, "//table/tbody/tr/td/(para, .[not(para)])", 9) testXPath2(t, html2, "//table/tbody/tr/td/(para, .[not(para)], ..)", 12) } func TestRelativePath(t *testing.T) { testXPath(t, html, "head", "head") testXPath(t, html, "/head", "head") testXPath(t, html, "body//li", "li") testXPath(t, html, "/head/title", "title") testXPath2(t, html, "/body/ul/li/a", 3) testXPath(t, html, "//title", "title") testXPath(t, html, "//title/..", "head") testXPath(t, html, "//title/../..", "html") testXPath2(t, html, "//a[@href]", 3) testXPath(t, html, "//ul/../footer", "footer") } func TestChild(t *testing.T) { testXPath(t, html, "/child::head", "head") testXPath(t, html, "/child::head/child::title", "title") testXPath(t, html, "//title/../child::title", "title") testXPath(t, html.Parent, "//child::*", "html") } func TestDescendant(t *testing.T) { testXPath2(t, html, "descendant::*", 15) testXPath2(t, html, "/head/descendant::*", 2) testXPath2(t, html, "//ul/descendant::*", 7) //
  • + testXPath2(t, html, "//ul/descendant::li", 4) //
  • } func TestAncestor(t *testing.T) { testXPath2(t, html, "/body/footer/ancestor::*", 2) // body>html testXPath2(t, html, "/body/ul/li/a/ancestor::li", 3) testXPath2(t, html, "/body/ul/li/a/ancestor-or-self::li", 3) } func TestFollowingSibling(t *testing.T) { var list []*TNode list = selectNodes(html, "//li/following-sibling::*") for _, n := range list { if n.Data != "li" { t.Fatalf("expected node is li,but got:%s", n.Data) } } list = selectNodes(html, "//ul/following-sibling::*") // p,footer for _, n := range list { if n.Data != "p" && n.Data != "footer" { t.Fatal("expected node is not one of the following nodes: [p,footer]") } } testXPath(t, html, "//ul/following-sibling::footer", "footer") list = selectNodes(html, "//h1/following::*") // ul>li>a,p,footer if list[0].Data != "ul" { t.Fatal("expected node is not ul") } if list[1].Data != "li" { t.Fatal("expected node is not li") } if list[len(list)-1].Data != "footer" { t.Fatal("expected node is not footer") } } func TestPrecedingSibling(t *testing.T) { testXPath(t, html, "/body/footer/preceding-sibling::*", "p") testXPath2(t, html, "/body/footer/preceding-sibling::*", 3) // p,ul,h1 list := selectNodes(html, "//h1/preceding::*") // head>title>meta if list[0].Data != "head" { t.Fatal("expected is not head") } if list[1].Data != "title" { t.Fatal("expected is not title") } if list[2].Data != "meta" { t.Fatal("expected is not meta") } } func TestStarWide(t *testing.T) { testXPath(t, html, "/head/*", "title") testXPath2(t, html, "//ul/*", 4) testXPath(t, html, "@*", "html") testXPath2(t, html, "/body/h1/*", 0) testXPath2(t, html, `//ul/*/a`, 3) } func TestNodeTestType(t *testing.T) { testXPath(t, html, "//title/text()", "Hello") testXPath(t, html, "//a[@href='/']/text()", "Home") testXPath2(t, html, "//head/node()", 2) testXPath2(t, html, "//ul/node()", 4) } func TestPosition(t *testing.T) { testXPath3(t, html, "/head[1]", html.FirstChild) // compare to 'head' element ul := selectNode(html, "//ul") testXPath3(t, html, "/head[last()]", html.FirstChild) testXPath3(t, html, "//li[1]", ul.FirstChild) testXPath3(t, html, "//li[4]", ul.LastChild) testXPath3(t, html, "//li[last()]", ul.LastChild) testXPath2(t, html2, "//td[2]", 3) } func TestPredicate(t *testing.T) { testXPath(t, html.Parent, "html[@lang='en']", "html") testXPath(t, html, "//a[@href='/']", "a") testXPath(t, html, "//meta[@name]", "meta") ul := selectNode(html, "//ul") testXPath3(t, html, "//li[position()=4]", ul.LastChild) testXPath3(t, html, "//li[position()=1]", ul.FirstChild) testXPath2(t, html, "//li[position()>0]", 4) testXPath3(t, html, "//a[text()='Home']", selectNode(html, "//a[1]")) } func TestOr_And(t *testing.T) { list := selectNodes(html, "//h1|//footer") if len(list) == 0 { t.Fatal("//h1|//footer no any node found") } if list[0].Data != "h1" { t.Fatalf("expected first node of node-set is h1,but got %s", list[0].Data) } if list[1].Data != "footer" { t.Fatalf("expected first node of node-set is footer,but got %s", list[1].Data) } list = selectNodes(html, "//a[@id=1 or @id=2]") if list[0] != selectNode(html, "//a[@id=1]") { t.Fatal("node is not equal") } if list[1] != selectNode(html, "//a[@id=2]") { t.Fatal("node is not equal") } list = selectNodes(html, "//a[@id or @href]") if list[0] != selectNode(html, "//a[@id=1]") { t.Fatal("node is not equal") } if list[1] != selectNode(html, "//a[@id=2]") { t.Fatal("node is not equal") } testXPath3(t, html, "//a[@id=1 and @href='/']", selectNode(html, "//a[1]")) testXPath3(t, html, "//a[text()='Home' and @id='1']", selectNode(html, "//a[1]")) } func TestFunction(t *testing.T) { testEval(t, html, "boolean(//*[@id])", true) testEval(t, html, "boolean(//*[@x])", false) testEval(t, html, "name(//title)", "title") testXPath2(t, html, "//*[name()='a']", 3) testXPath(t, html, "//*[starts-with(name(),'h1')]", "h1") testXPath(t, html, "//*[ends-with(name(),'itle')]", "title") // Head title testXPath2(t, html, "//*[contains(@href,'a')]", 2) testXPath2(t, html, "//*[starts-with(@href,'/a')]", 2) // a links: `/account`,`/about` testXPath2(t, html, "//*[ends-with(@href,'t')]", 2) // a links: `/account`,`/about` testXPath3(t, html, "//h1[normalize-space(text())='This is a H1']", selectNode(html, "//h1")) testXPath3(t, html, "//title[substring(.,1)='Hello']", selectNode(html, "//title")) testXPath3(t, html, "//title[substring(text(),1,4)='Hell']", selectNode(html, "//title")) testXPath3(t, html, "//title[substring(self::*,1,4)='Hell']", selectNode(html, "//title")) testXPath2(t, html, "//title[substring(child::*,1)]", 0) // Here substring return boolen (false), should it? testXPath2(t, html, "//title[substring(child::*,1) = '']", 1) testXPath3(t, html, "//li[not(a)]", selectNode(html, "//ul/li[4]")) testXPath2(t, html, "//li/a[not(@id='1')]", 2) // //li/a[@id!=1] testXPath2(t, html, "//h1[string-length(normalize-space(' abc ')) = 3]", 1) testXPath2(t, html, "//h1[string-length(normalize-space(self::text())) = 12]", 1) testXPath2(t, html, "//title[string-length(normalize-space(child::*)) = 0]", 1) testXPath2(t, html, "//title[string-length(self::text()) = 5]", 1) // Hello = 5 testXPath2(t, html, "//title[string-length(child::*) = 5]", 0) testXPath2(t, html, "//ul[count(li)=4]", 1) testEval(t, html, "true()", true) testEval(t, html, "false()", false) testEval(t, html, "boolean(0)", false) testEval(t, html, "boolean(1)", true) testEval(t, html, "sum(1+2)", float64(3)) testEval(t, html, "string(sum(1+2))", "3") testEval(t, html, "sum(1.1+2)", float64(3.1)) testEval(t, html, "sum(//a/@id)", float64(6)) // 1+2+3 testEval(t, html, `concat("1","2","3")`, "123") testEval(t, html, `concat(" ",//a[@id='1']/@href," ")`, " / ") testEval(t, html, "ceiling(5.2)", float64(6)) testEval(t, html, "floor(5.2)", float64(5)) testEval(t, html, `substring-before('aa-bb','-')`, "aa") testEval(t, html, `substring-before('aa-bb','a')`, "") testEval(t, html, `substring-before('aa-bb','b')`, "aa-") testEval(t, html, `substring-before('aa-bb','q')`, "") testEval(t, html, `substring-after('aa-bb','-')`, "bb") testEval(t, html, `substring-after('aa-bb','a')`, "a-bb") testEval(t, html, `substring-after('aa-bb','b')`, "b") testEval(t, html, `substring-after('aa-bb','q')`, "") testEval(t, html, `translate('The quick brown fox.', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')`, "THE QUICK BROWN FOX.", ) testEval(t, html, `translate('The quick brown fox.', 'brown', 'red')`, "The quick red fdx.", ) // preceding-sibling::* testXPath3(t, html, "//li[last()]/preceding-sibling::*[2]", selectNode(html, "//li[position()=2]")) // preceding:: testXPath3(t, html, "//li/preceding::*[1]", selectNode(html, "//h1")) } func TestPanic(t *testing.T) { // starts-with assertPanic(t, func() { testXPath(t, html, "//*[starts-with(0, 0)]", "") }) assertPanic(t, func() { testXPath(t, html, "//*[starts-with(name(), 0)]", "") }) //ends-with assertPanic(t, func() { testXPath(t, html, "//*[ends-with(0, 0)]", "") }) assertPanic(t, func() { testXPath(t, html, "//*[ends-with(name(), 0)]", "") }) // contains assertPanic(t, func() { testXPath2(t, html, "//*[contains(0, 0)]", 0) }) assertPanic(t, func() { testXPath2(t, html, "//*[contains(@href, 0)]", 0) }) // sum assertPanic(t, func() { testXPath3(t, html, "//title[sum('Hello') = 0]", nil) }) // substring assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,'')=0]", nil) }) assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,4,'')=0]", nil) }) assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,4,4)=0]", nil) }) //assertPanic(t, func() { testXPath2(t, html, "//title[substring(child::*,0) = '']", 0) }) // Here substring return boolen (false), should it? } func assertPanic(t *testing.T, f func()) { defer func() { if r := recover(); r == nil { t.Errorf("The code did not panic") } }() f() } func TestEvaluate(t *testing.T) { testEval(t, html, "count(//ul/li)", float64(4)) testEval(t, html, "//html/@lang", []string{"en"}) testEval(t, html, "//title/text()", []string{"Hello"}) } func TestOperationOrLogical(t *testing.T) { testXPath3(t, html, "//li[1+1]", selectNode(html, "//li[2]")) testXPath3(t, html, "//li[5 div 2]", selectNode(html, "//li[2]")) testXPath3(t, html, "//li[3 mod 2]", selectNode(html, "//li[1]")) testXPath3(t, html, "//li[3 - 2]", selectNode(html, "//li[1]")) testXPath2(t, html, "//li[position() mod 2 = 0 ]", 2) // //li[2],li[4] testXPath2(t, html, "//a[@id>=1]", 3) // //a[@id>=1] == a[1],a[2],a[3] testXPath2(t, html, "//a[@id<=2]", 2) // //a[@id<=2] == a[1],a[1] testXPath2(t, html, "//a[@id<2]", 1) // //a[@id>=1] == a[1] testXPath2(t, html, "//a[@id!=2]", 2) // //a[@id>=1] == a[1],a[3] testXPath2(t, html, "//a[@id=1 or @id=3]", 2) // //a[@id>=1] == a[1],a[3] testXPath3(t, html, "//a[@id=1 and @href='/']", selectNode(html, "//a[1]")) } func testEval(t *testing.T, root *TNode, expr string, expected interface{}) { v := MustCompile(expr).Evaluate(createNavigator(root)) if it, ok := v.(*NodeIterator); ok { exp, ok := expected.([]string) if !ok { t.Fatalf("expected value, got: %#v", v) } got := iterateNavs(it) if len(exp) != len(got) { t.Fatalf("expected: %#v, got: %#v", exp, got) } for i, n1 := range exp { n2 := got[i] if n1 != n2.Value() { t.Fatalf("expected: %#v, got: %#v", n1, n2) } } return } if v != expected { t.Fatalf("expected: %#v, got: %#v", expected, v) } } func testXPath(t *testing.T, root *TNode, expr string, expected string) { node := selectNode(root, expr) if node == nil { t.Fatalf("`%s` returns node is nil", expr) } if node.Data != expected { t.Fatalf("`%s` expected node is %s,but got %s", expr, expected, node.Data) } } func testXPath2(t *testing.T, root *TNode, expr string, expected int) { list := selectNodes(root, expr) if len(list) != expected { t.Fatalf("`%s` expected node numbers is %d,but got %d", expr, expected, len(list)) } } func testXPath3(t *testing.T, root *TNode, expr string, expected *TNode) { node := selectNode(root, expr) if node == nil { t.Fatalf("`%s` returns node is nil", expr) } if node != expected { t.Fatalf("`%s` %s != %s", expr, node.Value(), expected.Value()) } } 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() { node := (t.Current().(*TNodeNavigator)).curr nodes = append(nodes, node) } return nodes } func selectNode(root *TNode, expr string) (n *TNode) { t := Select(createNavigator(root), expr) if t.MoveNext() { n = (t.Current().(*TNodeNavigator)).curr } return n } func selectNodes(root *TNode, expr string) []*TNode { t := Select(createNavigator(root), expr) return iterateNodes(t) } 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 } 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 { if n.curr.Type == ElementNode && n.attr != -1 { return AttributeNode } return n.curr.Type } func (n *TNodeNavigator) LocalName() string { if n.attr != -1 { return n.curr.Attr[n.attr].Key } return n.curr.Data } func (n *TNodeNavigator) Prefix() string { return "" } 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 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 node := n.curr.FirstChild; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveToFirst() bool { if 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 node := n.curr.NextSibling; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveToPrevious() bool { 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 example2() *TNode { /* Hello

    This is a H1

    row1-val1 row1-val2 row1-val3
    row2-val1 row2-val2 row2-val3
    row3-val1 row3-val2 row3-val3
    */ doc := createNode("", RootNode) xhtml := doc.createChildNode("html", ElementNode) xhtml.addAttribute("lang", "en") // The HTML head section. head := xhtml.createChildNode("head", ElementNode) n := head.createChildNode("title", ElementNode) n = n.createChildNode("Hello", TextNode) n = head.createChildNode("meta", ElementNode) n.addAttribute("name", "language") n.addAttribute("content", "en") // The HTML body section. body := xhtml.createChildNode("body", ElementNode) n = body.createChildNode("h1", ElementNode) n = n.createChildNode(" This is a H1 ", TextNode) n = body.createChildNode("table", ElementNode) tbody := n.createChildNode("tbody", ElementNode) n = tbody.createChildNode("tr", ElementNode) n.createChildNode("td", ElementNode).createChildNode("row1-val1", TextNode) n.createChildNode("td", ElementNode).createChildNode("row1-val2", TextNode) n.createChildNode("td", ElementNode).createChildNode("row1-val3", TextNode) n = tbody.createChildNode("tr", ElementNode) n.createChildNode("td", ElementNode).createChildNode("para", ElementNode).createChildNode("row2-val1", TextNode) n.createChildNode("td", ElementNode).createChildNode("para", ElementNode).createChildNode("row2-val2", TextNode) n.createChildNode("td", ElementNode).createChildNode("para", ElementNode).createChildNode("row2-val3", TextNode) n = tbody.createChildNode("tr", ElementNode) n.createChildNode("td", ElementNode).createChildNode("row3-val1", TextNode) n.createChildNode("td", ElementNode).createChildNode("para", ElementNode).createChildNode("row3-val2", TextNode) n.createChildNode("td", ElementNode).createChildNode("row3-val3", TextNode) return xhtml } func example() *TNode { /* Hello

    This is a H1

    Hello,This is an example for gxpath.

    */ doc := createNode("", RootNode) xhtml := doc.createChildNode("html", ElementNode) xhtml.addAttribute("lang", "en") // The HTML head section. head := xhtml.createChildNode("head", ElementNode) n := head.createChildNode("title", ElementNode) n = n.createChildNode("Hello", TextNode) n = head.createChildNode("meta", ElementNode) n.addAttribute("name", "language") n.addAttribute("content", "en") // The HTML body section. body := xhtml.createChildNode("body", ElementNode) n = body.createChildNode("h1", ElementNode) n = n.createChildNode("\nThis is a H1\n", TextNode) ul := body.createChildNode("ul", ElementNode) n = ul.createChildNode("li", ElementNode) n = n.createChildNode("a", ElementNode) n.addAttribute("id", "1") n.addAttribute("href", "/") n = n.createChildNode("Home", TextNode) n = ul.createChildNode("li", ElementNode) n = n.createChildNode("a", ElementNode) n.addAttribute("id", "2") n.addAttribute("href", "/about") n = n.createChildNode("about", TextNode) n = ul.createChildNode("li", ElementNode) n = n.createChildNode("a", ElementNode) n.addAttribute("id", "3") n.addAttribute("href", "/account") n = n.createChildNode("login", TextNode) n = ul.createChildNode("li", ElementNode) n = body.createChildNode("p", ElementNode) n = n.createChildNode("Hello,This is an example for gxpath.", TextNode) n = body.createChildNode("footer", ElementNode) n = n.createChildNode("footer script", TextNode) return xhtml }