package build import ( "errors" "fmt" "goutil/xmlUtil/gxpath/internal/parse" "goutil/xmlUtil/gxpath/internal/query" "goutil/xmlUtil/gxpath/xpath" ) type flag int const ( noneFlag flag = iota filterFlag ) // builder provides building an XPath expressions. type builder struct { depth int flag flag firstInput query.Query } // axisPredicate creates a predicate to predicating for this axis node. func axisPredicate(root *parse.AxisNode) func(xpath.NodeNavigator) bool { // get current axix node type. typ := xpath.ElementNode if root.AxeType == "attribute" { typ = xpath.AttributeNode } else { switch root.Prop { case "comment": typ = xpath.CommentNode case "text": typ = xpath.TextNode // case "processing-instruction": // typ = xpath.ProcessingInstructionNode case "node": typ = xpath.ElementNode } } predicate := func(n xpath.NodeNavigator) bool { if typ == n.NodeType() { if root.LocalName == "" || (root.LocalName == n.LocalName() && root.Prefix == n.Prefix()) { return true } } return false } return predicate } // processAxisNode buildes a query for the XPath axis node. func (b *builder) processAxisNode(root *parse.AxisNode) (query.Query, error) { var ( err error qyInput query.Query qyOutput query.Query predicate = axisPredicate(root) ) if root.Input == nil { qyInput = &query.ContextQuery{} } else { if b.flag&filterFlag == 0 { if root.AxeType == "child" && (root.Input.Type() == parse.NodeAxis) { if input := root.Input.(*parse.AxisNode); input.AxeType == "descendant-or-self" { var qyGrandInput query.Query if input.Input != nil { qyGrandInput, _ = b.processNode(input.Input) } else { qyGrandInput = &query.ContextQuery{} } qyOutput = &query.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 = &query.AncestorQuery{Input: qyInput, Predicate: predicate} case "ancestor-or-self": qyOutput = &query.AncestorQuery{Input: qyInput, Predicate: predicate, Self: true} case "attribute": qyOutput = &query.AttributeQuery{Input: qyInput, Predicate: predicate} case "child": filter := func(n xpath.NodeNavigator) bool { v := predicate(n) switch root.Prop { case "text": v = v && n.NodeType() == xpath.TextNode case "node": v = v && (n.NodeType() == xpath.ElementNode || n.NodeType() == xpath.TextNode) case "comment": v = v && n.NodeType() == xpath.CommentNode } return v } qyOutput = &query.ChildQuery{Input: qyInput, Predicate: filter} case "descendant": qyOutput = &query.DescendantQuery{Input: qyInput, Predicate: predicate} case "descendant-or-self": qyOutput = &query.DescendantQuery{Input: qyInput, Predicate: predicate, Self: true} case "following": qyOutput = &query.FollowingQuery{Input: qyInput, Predicate: predicate} case "following-sibling": qyOutput = &query.FollowingQuery{Input: qyInput, Predicate: predicate, Sibling: true} case "parent": qyOutput = &query.ParentQuery{Input: qyInput, Predicate: predicate} case "preceding": qyOutput = &query.PrecedingQuery{Input: qyInput, Predicate: predicate} case "preceding-sibling": qyOutput = &query.PrecedingQuery{Input: qyInput, Predicate: predicate, Sibling: true} case "self": qyOutput = &query.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.Query for the XPath filter predicate. func (b *builder) processFilterNode(root *parse.FilterNode) (query.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 := &query.FilterQuery{Input: qyInput, Predicate: qyCond} return qyOutput, nil } // processFunctionNode buildes query.Query for the XPath function node. func (b *builder) processFunctionNode(root *parse.FunctionNode) (query.Query, error) { var qyOutput query.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 } startwith := &startwithFunc{arg1, arg2} qyOutput = &query.XPathFunction{Input: b.firstInput, Func: startwith.do} 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.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 } } substring := &substringFunc{arg1, arg2, arg3} qyOutput = &query.XPathFunction{Input: b.firstInput, Func: substring.do} 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 = &query.XPathFunction{Input: argQuery, Func: normalizespaceFunc} case "name": qyOutput = &query.XPathFunction{Input: b.firstInput, Func: nameFunc} case "last": qyOutput = &query.XPathFunction{Input: b.firstInput, Func: lastFunc} case "position": qyOutput = &query.XPathFunction{Input: b.firstInput, Func: positionFunc} 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 = &query.XPathFunction{Input: argQuery, Func: countFunc} default: return nil, fmt.Errorf("not yet support this function %s()", root.FuncName) } return qyOutput, nil } func (b *builder) processOperatorNode(root *parse.OperatorNode) (query.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.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 = &query.NumericExpr{Left: left, Right: right, Do: exprFunc} case "=", ">", ">=", "<", "<=", "!=": var exprFunc func(query.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 = &query.LogicalExpr{Left: left, Right: right, Do: exprFunc} case "or", "and", "|": isOr := false if root.Op == "or" || root.Op == "|" { isOr = true } qyOutput = &query.BooleanExpr{Left: left, Right: right, IsOr: isOr} } return qyOutput, nil } func (b *builder) processNode(root parse.Node) (q query.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 parse.NodeConstantOperand: n := root.(*parse.OperandNode) q = &query.XPathConstant{Val: n.Val} case parse.NodeRoot: q = &query.ContextQuery{Root: true} case parse.NodeAxis: q, err = b.processAxisNode(root.(*parse.AxisNode)) b.firstInput = q case parse.NodeFilter: q, err = b.processFilterNode(root.(*parse.FilterNode)) case parse.NodeFunction: q, err = b.processFunctionNode(root.(*parse.FunctionNode)) case parse.NodeOperator: q, err = b.processOperatorNode(root.(*parse.OperatorNode)) } return } // Build builds a specified XPath expressions expr. func Build(expr string) (query.Query, error) { root := parse.Parse(expr) b := &builder{} return b.processNode(root) }