package xpath import ( "errors" "fmt" ) type flag int var flagsEnum = struct { None flag SmartDesc flag PosFilter flag Filter flag Condition flag }{ None: 0, SmartDesc: 1, PosFilter: 2, Filter: 4, Condition: 8, } type builderProp int var builderProps = struct { None builderProp PosFilter builderProp HasPosition builderProp HasLast builderProp NonFlat builderProp }{ None: 0, PosFilter: 1, HasPosition: 2, HasLast: 4, NonFlat: 8, } // builder provides building an XPath expressions. type builder struct { parseDepth int firstInput query } // axisPredicate creates a predicate to predicating for this axis node. func axisPredicate(root *axisNode) func(NodeNavigator) bool { nametest := root.LocalName != "" || root.Prefix != "" predicate := func(n NodeNavigator) bool { if root.typeTest == n.NodeType() || root.typeTest == allNode { if nametest { type namespaceURL interface { NamespaceURL() string } if ns, ok := n.(namespaceURL); ok && root.hasNamespaceURI { return root.LocalName == n.LocalName() && root.namespaceURI == ns.NamespaceURL() } if root.LocalName == n.LocalName() && root.Prefix == n.Prefix() { return true } } else { return true } } return false } return predicate } // processAxis processes a query for the XPath axis node. func (b *builder) processAxis(root *axisNode, flags flag, props *builderProp) (query, error) { var ( err error qyInput query qyOutput query ) b.firstInput = nil predicate := axisPredicate(root) if root.Input == nil { qyInput = &contextQuery{} *props = builderProps.None } else { inputFlags := flagsEnum.None if (flags & flagsEnum.Filter) == 0 { if root.AxisType == "child" && (root.Input.Type() == nodeAxis) { if input := root.Input.(*axisNode); input.AxisType == "descendant-or-self" { var qyGrandInput query if input.Input != nil { qyGrandInput, err = b.processNode(input.Input, flagsEnum.SmartDesc, props) if err != nil { return nil, err } } else { qyGrandInput = &contextQuery{} } qyOutput = &descendantQuery{name: root.LocalName, Input: qyGrandInput, Predicate: predicate, Self: false} *props |= builderProps.NonFlat return qyOutput, nil } } if root.AxisType == "descendant" || root.AxisType == "descendant-or-self" { inputFlags |= flagsEnum.SmartDesc } } qyInput, err = b.processNode(root.Input, inputFlags, props) if err != nil { return nil, err } } switch root.AxisType { case "ancestor": qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} *props |= builderProps.NonFlat case "ancestor-or-self": qyOutput = &ancestorQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true} *props |= builderProps.NonFlat case "attribute": qyOutput = &attributeQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} case "child": if (*props & builderProps.NonFlat) == 0 { qyOutput = &childQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} } else { qyOutput = &cachedChildQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} } case "descendant": if (flags & flagsEnum.SmartDesc) != flagsEnum.None { qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: false, Predicate: predicate} } else { qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate} } *props |= builderProps.NonFlat case "descendant-or-self": if (flags & flagsEnum.SmartDesc) != flagsEnum.None { qyOutput = &descendantOverDescendantQuery{name: root.LocalName, Input: qyInput, MatchSelf: true, Predicate: predicate} } else { qyOutput = &descendantQuery{name: root.LocalName, Input: qyInput, Predicate: predicate, Self: true} } *props |= builderProps.NonFlat case "following": qyOutput = &followingQuery{Input: qyInput, Predicate: predicate} *props |= builderProps.NonFlat case "following-sibling": qyOutput = &followingQuery{Input: qyInput, Predicate: predicate, Sibling: true} case "parent": qyOutput = &parentQuery{Input: qyInput, Predicate: predicate} case "preceding": qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate} *props |= builderProps.NonFlat case "preceding-sibling": qyOutput = &precedingQuery{Input: qyInput, Predicate: predicate, Sibling: true} case "self": qyOutput = &selfQuery{Input: qyInput, Predicate: predicate} case "namespace": // haha,what will you do someting?? default: err = fmt.Errorf("unknown axe type: %s", root.AxisType) return nil, err } return qyOutput, nil } func canBeNumber(q query) bool { if q.ValueType() != xpathResultType.Any { return q.ValueType() == xpathResultType.Number } return true } // processFilterNode builds query for the XPath filter predicate. func (b *builder) processFilter(root *filterNode, flags flag, props *builderProp) (query, error) { first := (flags & flagsEnum.Filter) == 0 qyInput, err := b.processNode(root.Input, (flags | flagsEnum.Filter), props) if err != nil { return nil, err } firstInput := b.firstInput var propsCond builderProp cond, err := b.processNode(root.Condition, flags, &propsCond) if err != nil { return nil, err } // Checking whether is number if canBeNumber(cond) || ((propsCond & (builderProps.HasPosition | builderProps.HasLast)) != 0) { propsCond |= builderProps.HasPosition flags |= flagsEnum.PosFilter } if root.Input.Type() != nodeFilter { *props &= ^builderProps.PosFilter } if (propsCond & builderProps.HasPosition) != 0 { *props |= builderProps.PosFilter } if (propsCond & builderProps.HasPosition) != builderProps.None { if (propsCond & builderProps.HasLast) != 0 { // https://github.com/antchfx/xpath/issues/76 // https://github.com/antchfx/xpath/issues/78 if qyFunc, ok := cond.(*functionQuery); ok { switch qyFunc.Input.(type) { case *filterQuery: cond = &lastFuncQuery{Input: qyFunc.Input} } } } } merge := (qyInput.Properties() & queryProps.Merge) != 0 if first && firstInput != nil { if merge && ((*props & builderProps.PosFilter) != 0) { var ( rootQuery = &contextQuery{} parent query ) switch axisQuery := firstInput.(type) { case *ancestorQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *attributeQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *childQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *cachedChildQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *descendantQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *followingQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *precedingQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *parentQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *selfQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *groupQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } case *descendantOverDescendantQuery: if _, ok := axisQuery.Input.(*contextQuery); !ok { parent = axisQuery.Input axisQuery.Input = rootQuery } } b.firstInput = nil child := &filterQuery{Input: qyInput, Predicate: cond, NoPosition: false} if parent != nil { return &mergeQuery{Input: parent, Child: child}, nil } return child, nil } b.firstInput = nil } resultQuery := &filterQuery{ Input: qyInput, Predicate: cond, NoPosition: (propsCond & builderProps.HasPosition) == 0, } return resultQuery, nil } // processFunctionNode processes query for the XPath function node. func (b *builder) processFunction(root *functionNode, props *builderProp) (query, error) { // Reset builder props *props = builderProps.None var qyOutput query switch root.FuncName { case "lower-case": arg, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: lowerCaseFunc(arg)} case "starts-with": arg1, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: startwithFunc(arg1, arg2)} case "ends-with": arg1, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: endwithFunc(arg1, arg2)} case "contains": arg1, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } arg2, err := b.processNode(root.Args[1], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: containsFunc(arg1, arg2)} case "matches": //matches(string , pattern) if len(root.Args) != 2 { return nil, errors.New("xpath: matches function must have two parameters") } var ( arg1, arg2 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } // Issue #92, testing the regular expression before. if q, ok := arg2.(*constantQuery); ok { if _, err = getRegexp(q.Val.(string)); err != nil { return nil, fmt.Errorf("matches() got error. %v", err) } } qyOutput = &functionQuery{Func: matchesFunc(arg1, arg2)} case "substring": //substring( string , start [, length] ) if len(root.Args) < 2 { return nil, errors.New("xpath: substring function must have at least two parameter") } var ( arg1, arg2, arg3 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } if len(root.Args) == 3 { if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil { return nil, err } } qyOutput = &functionQuery{Func: substringFunc(arg1, arg2, arg3)} case "substring-before", "substring-after": //substring-xxxx( haystack, needle ) if len(root.Args) != 2 { return nil, errors.New("xpath: substring-before function must have two parameters") } var ( arg1, arg2 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } qyOutput = &functionQuery{ Func: substringIndFunc(arg1, arg2, root.FuncName == "substring-after"), } case "string-length": // string-length( [string] ) if len(root.Args) < 1 { return nil, errors.New("xpath: string-length function must have at least one parameter") } arg1, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: stringLengthFunc(arg1)} case "normalize-space": var arg node if len(root.Args) > 0 { arg = root.Args[0] } else { arg = newAxisNode("self", allNode, "", "", "", nil) } arg1, err := b.processNode(arg, flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: normalizespaceFunc(arg1)} case "replace": //replace( string , string, string ) if len(root.Args) != 3 { return nil, errors.New("xpath: replace function must have three parameters") } var ( arg1, arg2, arg3 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil { return nil, err } qyOutput = &functionQuery{Func: replaceFunc(arg1, arg2, arg3)} case "translate": //translate( string , string, string ) if len(root.Args) != 3 { return nil, errors.New("xpath: translate function must have three parameters") } var ( arg1, arg2, arg3 query err error ) if arg1, err = b.processNode(root.Args[0], flagsEnum.None, props); err != nil { return nil, err } if arg2, err = b.processNode(root.Args[1], flagsEnum.None, props); err != nil { return nil, err } if arg3, err = b.processNode(root.Args[2], flagsEnum.None, props); err != nil { return nil, err } qyOutput = &functionQuery{Func: translateFunc(arg1, arg2, arg3)} case "not": if len(root.Args) == 0 { return nil, errors.New("xpath: not function must have at least one parameter") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: notFunc(argQuery)} case "name", "local-name", "namespace-uri": if len(root.Args) > 1 { return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName) } var ( arg query err error ) if len(root.Args) == 1 { arg, err = b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } } switch root.FuncName { case "name": qyOutput = &functionQuery{Func: nameFunc(arg)} case "local-name": qyOutput = &functionQuery{Func: localNameFunc(arg)} case "namespace-uri": qyOutput = &functionQuery{Func: namespaceFunc(arg)} } case "true", "false": val := root.FuncName == "true" qyOutput = &functionQuery{ Func: func(_ query, _ iterator) interface{} { return val }, } case "last": qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc()} *props |= builderProps.HasLast case "position": qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc()} *props |= builderProps.HasPosition case "boolean", "number", "string": var inp query if len(root.Args) > 1 { return nil, fmt.Errorf("xpath: %s function must have at most one parameter", root.FuncName) } if len(root.Args) == 1 { argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } inp = argQuery } switch root.FuncName { case "boolean": qyOutput = &functionQuery{Func: booleanFunc(inp)} case "string": qyOutput = &functionQuery{Func: stringFunc(inp)} case "number": qyOutput = &functionQuery{Func: numberFunc(inp)} } case "count": if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: count(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: countFunc(argQuery)} case "sum": if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: sum(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: sumFunc(argQuery)} case "ceiling", "floor", "round": if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: ceiling(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } switch root.FuncName { case "ceiling": qyOutput = &functionQuery{Func: ceilingFunc(argQuery)} case "floor": qyOutput = &functionQuery{Func: floorFunc(argQuery)} case "round": qyOutput = &functionQuery{Func: roundFunc(argQuery)} } case "concat": if len(root.Args) < 2 { return nil, fmt.Errorf("xpath: concat() must have at least two arguments") } var args []query for _, v := range root.Args { q, err := b.processNode(v, flagsEnum.None, props) if err != nil { return nil, err } args = append(args, q) } qyOutput = &functionQuery{Func: concatFunc(args...)} case "reverse": if len(root.Args) == 0 { return nil, fmt.Errorf("xpath: reverse(node-sets) function must with have parameters node-sets") } argQuery, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &transformFunctionQuery{Input: argQuery, Func: reverseFunc} case "string-join": if len(root.Args) != 2 { return nil, fmt.Errorf("xpath: string-join(node-sets, separator) function requires node-set and argument") } input, err := b.processNode(root.Args[0], flagsEnum.None, props) if err != nil { return nil, err } arg1, err := b.processNode(root.Args[1], flagsEnum.None, props) if err != nil { return nil, err } qyOutput = &functionQuery{Func: stringJoinFunc(input, arg1)} default: return nil, fmt.Errorf("not yet support this function %s()", root.FuncName) } return qyOutput, nil } func (b *builder) processOperator(root *operatorNode, props *builderProp) (query, error) { var ( leftProp builderProp rightProp builderProp ) left, err := b.processNode(root.Left, flagsEnum.None, &leftProp) if err != nil { return nil, err } right, err := b.processNode(root.Right, flagsEnum.None, &rightProp) if err != nil { return nil, err } *props = leftProp | rightProp var qyOutput query switch root.Op { case "+", "-", "*", "div", "mod": // Numeric operator var exprFunc func(iterator, interface{}, interface{}) interface{} switch root.Op { case "+": exprFunc = plusFunc case "-": exprFunc = minusFunc case "*": exprFunc = mulFunc case "div": exprFunc = divFunc case "mod": exprFunc = modFunc } qyOutput = &numericQuery{Left: left, Right: right, Do: exprFunc} case "=", ">", ">=", "<", "<=", "!=": var exprFunc func(iterator, interface{}, interface{}) interface{} switch root.Op { case "=": exprFunc = eqFunc case ">": exprFunc = gtFunc case ">=": exprFunc = geFunc case "<": exprFunc = ltFunc case "<=": exprFunc = leFunc case "!=": exprFunc = neFunc } qyOutput = &logicalQuery{Left: left, Right: right, Do: exprFunc} case "or", "and": isOr := false if root.Op == "or" { isOr = true } qyOutput = &booleanQuery{Left: left, Right: right, IsOr: isOr} case "|": *props |= builderProps.NonFlat qyOutput = &unionQuery{Left: left, Right: right} } return qyOutput, nil } func (b *builder) processNode(root node, flags flag, props *builderProp) (q query, err error) { if b.parseDepth = b.parseDepth + 1; b.parseDepth > 1024 { err = errors.New("the xpath expressions is too complex") return } *props = builderProps.None switch root.Type() { case nodeConstantOperand: n := root.(*operandNode) q = &constantQuery{Val: n.Val} case nodeRoot: q = &absoluteQuery{} case nodeAxis: q, err = b.processAxis(root.(*axisNode), flags, props) b.firstInput = q case nodeFilter: q, err = b.processFilter(root.(*filterNode), flags, props) b.firstInput = q case nodeFunction: q, err = b.processFunction(root.(*functionNode), props) case nodeOperator: q, err = b.processOperator(root.(*operatorNode), props) case nodeGroup: q, err = b.processNode(root.(*groupNode).Input, flagsEnum.None, props) if err != nil { return } q = &groupQuery{Input: q} if b.firstInput == nil { b.firstInput = q } } b.parseDepth-- return } // build builds a specified XPath expressions expr. func build(expr string, namespaces map[string]string) (q query, err error) { defer func() { if e := recover(); e != nil { switch x := e.(type) { case string: err = errors.New(x) case error: err = x default: err = errors.New("unknown panic") } } }() root := parse(expr, namespaces) b := &builder{} props := builderProps.None return b.processNode(root, flagsEnum.None, &props) }