302 lines
8.4 KiB
Plaintext
302 lines
8.4 KiB
Plaintext
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)
|
|
}
|