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 }