github.com/avicd/go-utilx@v0.1.0/xmlx/xpath.go (about)

     1  package xmlx
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/avicd/go-utilx/conv"
     6  	"github.com/avicd/go-utilx/evalx"
     7  	"github.com/avicd/go-utilx/refx"
     8  	"io"
     9  	"strings"
    10  )
    11  
    12  type Xpath struct {
    13  	stacks []*xStack
    14  	expr   string
    15  }
    16  
    17  func NewXpath(text string) (*Xpath, error) {
    18  	stacks, err := parseXpath(text)
    19  	if err != nil {
    20  		return nil, err
    21  	}
    22  	return &Xpath{stacks: stacks, expr: text}, nil
    23  }
    24  
    25  func (it *Xpath) SelectFirst(node *Node) *Node {
    26  	for _, stack := range it.stacks {
    27  		list := stack.eval(node, SelectFirst)
    28  		if len(list) > 0 {
    29  			return list[0]
    30  		}
    31  	}
    32  	return nil
    33  }
    34  
    35  func (it *Xpath) SelectAll(node *Node) []*Node {
    36  	var list []*Node
    37  	for _, stack := range it.stacks {
    38  		rslt := stack.eval(node, SelectAll)
    39  		if len(rslt) > 0 {
    40  			list = append(list, rslt...)
    41  		}
    42  	}
    43  	return list
    44  }
    45  
    46  func (it *Xpath) SelectLast(node *Node) *Node {
    47  	list := it.SelectAll(node)
    48  	if len(list) > 0 {
    49  		return list[len(list)-1]
    50  	}
    51  	return nil
    52  }
    53  
    54  type xStack struct {
    55  	call   string
    56  	sel    string
    57  	axis   string
    58  	choose string
    59  	root   bool
    60  	next   *xStack
    61  }
    62  
    63  func (it *xStack) pushNext() *xStack {
    64  	next := &xStack{}
    65  	if it != nil {
    66  		it.next = next
    67  	}
    68  	return next
    69  }
    70  
    71  func (it *xStack) evalStack(input *Node, handler XNodeHandler, pool *indexPool) {
    72  	xnd := NewXpathNode(input)
    73  	var list []*Node
    74  	xnd.setCheckin(func(node *Node) bool {
    75  		pool.cacheIndex(node)
    76  		list = append(list, node)
    77  		if it.choose == "" {
    78  			if it.next != nil {
    79  				it.next.evalStack(node, handler, pool)
    80  			} else {
    81  				return handler(node)
    82  			}
    83  		}
    84  		return true
    85  	})
    86  	ctx := newEvalCtx(xnd)
    87  	ctx.bind("indexPool", pool)
    88  	evalx.Eval(it.axis, ctx)
    89  	if it.choose != "" && len(list) > 0 {
    90  		for _, p := range list {
    91  			pctx := newEvalCtx(NewXpathNode(p))
    92  			val, _ := evalx.Eval(it.choose, pctx)
    93  			var pass *Node
    94  			if refx.IsGeneralInt(val) {
    95  				if refx.AsInt64(val) == int64(pool.GetIndex(p)) {
    96  					pass = p
    97  				}
    98  			} else if refx.AsBool(val) {
    99  				pass = p
   100  			}
   101  			if pass != nil {
   102  				if it.next != nil {
   103  					it.next.evalStack(pass, handler, pool)
   104  				} else {
   105  					handler(pass)
   106  				}
   107  			}
   108  		}
   109  	}
   110  }
   111  
   112  func (it *xStack) eval(input *Node, stype SelectType) []*Node {
   113  	var list []*Node
   114  	pool := &indexPool{cache: map[*Node]map[*Node]int{}}
   115  	var handler XNodeHandler
   116  	rc := map[*Node]bool{}
   117  	handler = func(node *Node) bool {
   118  		if _, ok := rc[node]; !ok {
   119  			rc[node] = true
   120  			list = append(list, node)
   121  			if stype == SelectFirst {
   122  				return false
   123  			}
   124  		}
   125  		return true
   126  	}
   127  	it.evalStack(input, handler, pool)
   128  	return list
   129  }
   130  
   131  func parseXpath(text string) ([]*xStack, error) {
   132  	r := strings.NewReader(strings.TrimSpace(text))
   133  	var pre byte
   134  	var stacks []*xStack
   135  	var root *xStack
   136  	var current *xStack
   137  	buf := &strings.Builder{}
   138  	index := -1
   139  	var sl byte
   140  	var cl bool
   141  	split := func() {
   142  		if current != nil && buf.Len() > 0 {
   143  			current.sel = buf.String()
   144  			buf.Reset()
   145  		}
   146  	}
   147  	collect := func() {
   148  		split()
   149  		stacks = append(stacks, root)
   150  		root = nil
   151  		current = nil
   152  		index = -1
   153  	}
   154  	for {
   155  		ch, err := r.ReadByte()
   156  		if err == io.EOF {
   157  			collect()
   158  			break
   159  		}
   160  		index++
   161  		if (sl == 0) || ch == sl {
   162  			switch ch {
   163  			case '/':
   164  				if pre == '/' {
   165  					current.call = "DescendantOrSelf"
   166  				} else {
   167  					split()
   168  					current = current.pushNext()
   169  					current.call = "Child"
   170  					current.root = index == 0
   171  				}
   172  			case '[':
   173  				current.sel = buf.String()
   174  				buf.Reset()
   175  				cl = true
   176  			case ']':
   177  				current.choose = buf.String()
   178  				buf.Reset()
   179  				cl = false
   180  			case '\'', '"':
   181  				if sl == 0 {
   182  					sl = ch
   183  				} else {
   184  					sl = 0
   185  				}
   186  				buf.WriteByte('"')
   187  			case ':':
   188  				if pre == ':' {
   189  					current.call = conv.BigCamelCase(buf.String())
   190  					buf.Reset()
   191  				}
   192  			case '.':
   193  				if pre == '.' {
   194  					current.call = "Parent"
   195  				}
   196  			case '@':
   197  				if !cl {
   198  					if current == nil {
   199  						current = current.pushNext()
   200  					}
   201  					current.call = "Attribute"
   202  				} else {
   203  					buf.WriteString(xpathAttr)
   204  				}
   205  			case '=':
   206  				if !(pre == '!' || pre == '>' || pre == '<') {
   207  					buf.WriteString("==")
   208  				} else {
   209  					buf.WriteByte('=')
   210  				}
   211  			case '|':
   212  				collect()
   213  			default:
   214  				if current == nil {
   215  					current = current.pushNext()
   216  					current.call = "Child"
   217  				}
   218  				buf.WriteByte(ch)
   219  			}
   220  		} else {
   221  			buf.WriteByte(ch)
   222  		}
   223  
   224  		if current != nil && root == nil {
   225  			root = current
   226  		}
   227  		pre = ch
   228  	}
   229  	for hi, hd := range stacks {
   230  		if hd.root {
   231  			stacks[hi] = &xStack{next: hd, call: "self.Root"}
   232  		} else {
   233  			hd.call = "self." + hd.call
   234  		}
   235  		for p := stacks[hi]; p != nil; p = p.next {
   236  			var axis string
   237  			if p.sel != "" {
   238  				axis = fmt.Sprintf("%s(\"%s\")", p.call, p.sel)
   239  			} else {
   240  				axis = p.call + "()"
   241  			}
   242  			p.axis = axis
   243  		}
   244  	}
   245  	return stacks, nil
   246  }