1435 lines
28 KiB
Go
1435 lines
28 KiB
Go
package xpath
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"hash/fnv"
|
|
"reflect"
|
|
)
|
|
|
|
// The return type of the XPath expression.
|
|
type resultType int
|
|
|
|
var xpathResultType = struct {
|
|
Boolean resultType
|
|
// A numeric value
|
|
Number resultType
|
|
String resultType
|
|
// A node collection.
|
|
NodeSet resultType
|
|
// Any of the XPath node types.
|
|
Any resultType
|
|
}{
|
|
Boolean: 0,
|
|
Number: 1,
|
|
String: 2,
|
|
NodeSet: 3,
|
|
Any: 4,
|
|
}
|
|
|
|
type queryProp int
|
|
|
|
var queryProps = struct {
|
|
None queryProp
|
|
Position queryProp
|
|
Count queryProp
|
|
Cached queryProp
|
|
Reverse queryProp
|
|
Merge queryProp
|
|
}{
|
|
None: 0,
|
|
Position: 1,
|
|
Count: 2,
|
|
Cached: 4,
|
|
Reverse: 8,
|
|
Merge: 16,
|
|
}
|
|
|
|
type iterator interface {
|
|
Current() NodeNavigator
|
|
}
|
|
|
|
// An XPath query interface.
|
|
type query interface {
|
|
// Select traversing iterator returns a query matched node NodeNavigator.
|
|
Select(iterator) NodeNavigator
|
|
|
|
// Evaluate evaluates query and returns values of the current query.
|
|
Evaluate(iterator) interface{}
|
|
|
|
Clone() query
|
|
|
|
// ValueType returns the value type of the current query.
|
|
ValueType() resultType
|
|
|
|
Properties() queryProp
|
|
}
|
|
|
|
// nopQuery is an empty query that always return nil for any query.
|
|
type nopQuery struct{}
|
|
|
|
func (nopQuery) Select(iterator) NodeNavigator { return nil }
|
|
|
|
func (nopQuery) Evaluate(iterator) interface{} { return nil }
|
|
|
|
func (nopQuery) Clone() query { return nopQuery{} }
|
|
|
|
func (nopQuery) ValueType() resultType { return xpathResultType.NodeSet }
|
|
|
|
func (nopQuery) Properties() queryProp {
|
|
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
|
|
}
|
|
|
|
// contextQuery is returns current node on the iterator object query.
|
|
type contextQuery struct {
|
|
count int
|
|
}
|
|
|
|
func (c *contextQuery) Select(t iterator) NodeNavigator {
|
|
if c.count > 0 {
|
|
return nil
|
|
}
|
|
c.count++
|
|
return t.Current().Copy()
|
|
}
|
|
|
|
func (c *contextQuery) Evaluate(iterator) interface{} {
|
|
c.count = 0
|
|
return c
|
|
}
|
|
|
|
func (c *contextQuery) Clone() query {
|
|
return &contextQuery{}
|
|
}
|
|
|
|
func (c *contextQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (c *contextQuery) Properties() queryProp {
|
|
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
|
|
}
|
|
|
|
type absoluteQuery struct {
|
|
count int
|
|
}
|
|
|
|
func (a *absoluteQuery) Select(t iterator) (n NodeNavigator) {
|
|
if a.count > 0 {
|
|
return
|
|
}
|
|
a.count++
|
|
n = t.Current().Copy()
|
|
n.MoveToRoot()
|
|
return
|
|
}
|
|
|
|
func (a *absoluteQuery) Evaluate(t iterator) interface{} {
|
|
a.count = 0
|
|
return a
|
|
}
|
|
|
|
func (a *absoluteQuery) Clone() query {
|
|
return &absoluteQuery{}
|
|
}
|
|
|
|
func (a *absoluteQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (a *absoluteQuery) Properties() queryProp {
|
|
return queryProps.Merge | queryProps.Position | queryProps.Count | queryProps.Cached
|
|
}
|
|
|
|
// ancestorQuery is an XPath ancestor node query.(ancestor::*|ancestor-self::*)
|
|
type ancestorQuery struct {
|
|
name string
|
|
iterator func() NodeNavigator
|
|
table map[uint64]bool
|
|
|
|
Self bool
|
|
Input query
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (a *ancestorQuery) Select(t iterator) NodeNavigator {
|
|
if a.table == nil {
|
|
a.table = make(map[uint64]bool)
|
|
}
|
|
|
|
for {
|
|
if a.iterator == nil {
|
|
node := a.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
first := true
|
|
node = node.Copy()
|
|
a.iterator = func() NodeNavigator {
|
|
if first {
|
|
first = false
|
|
if a.Self && a.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
for node.MoveToParent() {
|
|
if a.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
for node := a.iterator(); node != nil; node = a.iterator() {
|
|
node_id := getHashCode(node.Copy())
|
|
if _, ok := a.table[node_id]; !ok {
|
|
a.table[node_id] = true
|
|
return node
|
|
}
|
|
}
|
|
a.iterator = nil
|
|
}
|
|
}
|
|
|
|
func (a *ancestorQuery) Evaluate(t iterator) interface{} {
|
|
a.Input.Evaluate(t)
|
|
a.iterator = nil
|
|
return a
|
|
}
|
|
|
|
func (a *ancestorQuery) Test(n NodeNavigator) bool {
|
|
return a.Predicate(n)
|
|
}
|
|
|
|
func (a *ancestorQuery) Clone() query {
|
|
return &ancestorQuery{name: a.name, Self: a.Self, Input: a.Input.Clone(), Predicate: a.Predicate}
|
|
}
|
|
|
|
func (a *ancestorQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (a *ancestorQuery) Properties() queryProp {
|
|
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge | queryProps.Reverse
|
|
}
|
|
|
|
// attributeQuery is an XPath attribute node query.(@*)
|
|
type attributeQuery struct {
|
|
name string
|
|
iterator func() NodeNavigator
|
|
|
|
Input query
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (a *attributeQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
if a.iterator == nil {
|
|
node := a.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
node = node.Copy()
|
|
a.iterator = func() NodeNavigator {
|
|
for {
|
|
onAttr := node.MoveToNextAttribute()
|
|
if !onAttr {
|
|
return nil
|
|
}
|
|
if a.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if node := a.iterator(); node != nil {
|
|
return node
|
|
}
|
|
a.iterator = nil
|
|
}
|
|
}
|
|
|
|
func (a *attributeQuery) Evaluate(t iterator) interface{} {
|
|
a.Input.Evaluate(t)
|
|
a.iterator = nil
|
|
return a
|
|
}
|
|
|
|
func (a *attributeQuery) Test(n NodeNavigator) bool {
|
|
return a.Predicate(n)
|
|
}
|
|
|
|
func (a *attributeQuery) Clone() query {
|
|
return &attributeQuery{name: a.name, Input: a.Input.Clone(), Predicate: a.Predicate}
|
|
}
|
|
|
|
func (a *attributeQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (a *attributeQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
// childQuery is an XPath child node query.(child::*)
|
|
type childQuery struct {
|
|
name string
|
|
posit int
|
|
iterator func() NodeNavigator
|
|
|
|
Input query
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (c *childQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
if c.iterator == nil {
|
|
c.posit = 0
|
|
node := c.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
node = node.Copy()
|
|
first := true
|
|
c.iterator = func() NodeNavigator {
|
|
for {
|
|
if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) {
|
|
return nil
|
|
}
|
|
first = false
|
|
if c.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if node := c.iterator(); node != nil {
|
|
c.posit++
|
|
return node
|
|
}
|
|
c.iterator = nil
|
|
}
|
|
}
|
|
|
|
func (c *childQuery) Evaluate(t iterator) interface{} {
|
|
c.Input.Evaluate(t)
|
|
c.iterator = nil
|
|
return c
|
|
}
|
|
|
|
func (c *childQuery) Test(n NodeNavigator) bool {
|
|
return c.Predicate(n)
|
|
}
|
|
|
|
func (c *childQuery) Clone() query {
|
|
return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
|
|
}
|
|
|
|
func (c *childQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (c *childQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
// position returns a position of current NodeNavigator.
|
|
func (c *childQuery) position() int {
|
|
return c.posit
|
|
}
|
|
|
|
type cachedChildQuery struct {
|
|
name string
|
|
posit int
|
|
iterator func() NodeNavigator
|
|
|
|
Input query
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (c *cachedChildQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
if c.iterator == nil {
|
|
c.posit = 0
|
|
node := c.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
node = node.Copy()
|
|
first := true
|
|
c.iterator = func() NodeNavigator {
|
|
for {
|
|
if (first && !node.MoveToChild()) || (!first && !node.MoveToNext()) {
|
|
return nil
|
|
}
|
|
first = false
|
|
if c.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if node := c.iterator(); node != nil {
|
|
c.posit++
|
|
return node
|
|
}
|
|
c.iterator = nil
|
|
}
|
|
}
|
|
|
|
func (c *cachedChildQuery) Evaluate(t iterator) interface{} {
|
|
c.Input.Evaluate(t)
|
|
c.iterator = nil
|
|
return c
|
|
}
|
|
|
|
func (c *cachedChildQuery) position() int {
|
|
return c.posit
|
|
}
|
|
|
|
func (c *cachedChildQuery) Test(n NodeNavigator) bool {
|
|
return c.Predicate(n)
|
|
}
|
|
|
|
func (c *cachedChildQuery) Clone() query {
|
|
return &childQuery{name: c.name, Input: c.Input.Clone(), Predicate: c.Predicate}
|
|
}
|
|
|
|
func (c *cachedChildQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (c *cachedChildQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
// descendantQuery is an XPath descendant node query.(descendant::* | descendant-or-self::*)
|
|
type descendantQuery struct {
|
|
name string
|
|
iterator func() NodeNavigator
|
|
posit int
|
|
level int
|
|
|
|
Self bool
|
|
Input query
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (d *descendantQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
if d.iterator == nil {
|
|
d.posit = 0
|
|
node := d.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
node = node.Copy()
|
|
d.level = 0
|
|
first := true
|
|
d.iterator = func() NodeNavigator {
|
|
if first {
|
|
first = false
|
|
if d.Self && d.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
|
|
for {
|
|
if node.MoveToChild() {
|
|
d.level = d.level + 1
|
|
} else {
|
|
for {
|
|
if d.level == 0 {
|
|
return nil
|
|
}
|
|
if node.MoveToNext() {
|
|
break
|
|
}
|
|
node.MoveToParent()
|
|
d.level = d.level - 1
|
|
}
|
|
}
|
|
if d.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if node := d.iterator(); node != nil {
|
|
d.posit++
|
|
return node
|
|
}
|
|
d.iterator = nil
|
|
}
|
|
}
|
|
|
|
func (d *descendantQuery) Evaluate(t iterator) interface{} {
|
|
d.Input.Evaluate(t)
|
|
d.iterator = nil
|
|
return d
|
|
}
|
|
|
|
func (d *descendantQuery) Test(n NodeNavigator) bool {
|
|
return d.Predicate(n)
|
|
}
|
|
|
|
// position returns a position of current NodeNavigator.
|
|
func (d *descendantQuery) position() int {
|
|
return d.posit
|
|
}
|
|
|
|
func (d *descendantQuery) depth() int {
|
|
return d.level
|
|
}
|
|
|
|
func (d *descendantQuery) Clone() query {
|
|
return &descendantQuery{name: d.name, Self: d.Self, Input: d.Input.Clone(), Predicate: d.Predicate}
|
|
}
|
|
|
|
func (d *descendantQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (d *descendantQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
// followingQuery is an XPath following node query.(following::*|following-sibling::*)
|
|
type followingQuery struct {
|
|
posit int
|
|
iterator func() NodeNavigator
|
|
|
|
Input query
|
|
Sibling bool // The matching sibling node of current node.
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (f *followingQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
if f.iterator == nil {
|
|
f.posit = 0
|
|
node := f.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
node = node.Copy()
|
|
if f.Sibling {
|
|
f.iterator = func() NodeNavigator {
|
|
for {
|
|
if !node.MoveToNext() {
|
|
return nil
|
|
}
|
|
if f.Predicate(node) {
|
|
f.posit++
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
var q *descendantQuery // descendant query
|
|
f.iterator = func() NodeNavigator {
|
|
for {
|
|
if q == nil {
|
|
for !node.MoveToNext() {
|
|
if !node.MoveToParent() {
|
|
return nil
|
|
}
|
|
}
|
|
q = &descendantQuery{
|
|
Self: true,
|
|
Input: &contextQuery{},
|
|
Predicate: f.Predicate,
|
|
}
|
|
t.Current().MoveTo(node)
|
|
}
|
|
if node := q.Select(t); node != nil {
|
|
f.posit = q.posit
|
|
return node
|
|
}
|
|
q = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if node := f.iterator(); node != nil {
|
|
return node
|
|
}
|
|
f.iterator = nil
|
|
}
|
|
}
|
|
|
|
func (f *followingQuery) Evaluate(t iterator) interface{} {
|
|
f.Input.Evaluate(t)
|
|
return f
|
|
}
|
|
|
|
func (f *followingQuery) Test(n NodeNavigator) bool {
|
|
return f.Predicate(n)
|
|
}
|
|
|
|
func (f *followingQuery) Clone() query {
|
|
return &followingQuery{Input: f.Input.Clone(), Sibling: f.Sibling, Predicate: f.Predicate}
|
|
}
|
|
|
|
func (f *followingQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (f *followingQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
func (f *followingQuery) position() int {
|
|
return f.posit
|
|
}
|
|
|
|
// precedingQuery is an XPath preceding node query.(preceding::*)
|
|
type precedingQuery struct {
|
|
iterator func() NodeNavigator
|
|
posit int
|
|
Input query
|
|
Sibling bool // The matching sibling node of current node.
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (p *precedingQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
if p.iterator == nil {
|
|
p.posit = 0
|
|
node := p.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
node = node.Copy()
|
|
if p.Sibling {
|
|
p.iterator = func() NodeNavigator {
|
|
for {
|
|
for !node.MoveToPrevious() {
|
|
return nil
|
|
}
|
|
if p.Predicate(node) {
|
|
p.posit++
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
var q query
|
|
p.iterator = func() NodeNavigator {
|
|
for {
|
|
if q == nil {
|
|
for !node.MoveToPrevious() {
|
|
if !node.MoveToParent() {
|
|
return nil
|
|
}
|
|
p.posit = 0
|
|
}
|
|
q = &descendantQuery{
|
|
Self: true,
|
|
Input: &contextQuery{},
|
|
Predicate: p.Predicate,
|
|
}
|
|
t.Current().MoveTo(node)
|
|
}
|
|
if node := q.Select(t); node != nil {
|
|
p.posit++
|
|
return node
|
|
}
|
|
q = nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if node := p.iterator(); node != nil {
|
|
return node
|
|
}
|
|
p.iterator = nil
|
|
}
|
|
}
|
|
|
|
func (p *precedingQuery) Evaluate(t iterator) interface{} {
|
|
p.Input.Evaluate(t)
|
|
return p
|
|
}
|
|
|
|
func (p *precedingQuery) Test(n NodeNavigator) bool {
|
|
return p.Predicate(n)
|
|
}
|
|
|
|
func (p *precedingQuery) Clone() query {
|
|
return &precedingQuery{Input: p.Input.Clone(), Sibling: p.Sibling, Predicate: p.Predicate}
|
|
}
|
|
|
|
func (p *precedingQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (p *precedingQuery) Properties() queryProp {
|
|
return queryProps.Merge | queryProps.Reverse
|
|
}
|
|
|
|
func (p *precedingQuery) position() int {
|
|
return p.posit
|
|
}
|
|
|
|
// parentQuery is an XPath parent node query.(parent::*)
|
|
type parentQuery struct {
|
|
Input query
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (p *parentQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
node := p.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
node = node.Copy()
|
|
if node.MoveToParent() && p.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *parentQuery) Evaluate(t iterator) interface{} {
|
|
p.Input.Evaluate(t)
|
|
return p
|
|
}
|
|
|
|
func (p *parentQuery) Clone() query {
|
|
return &parentQuery{Input: p.Input.Clone(), Predicate: p.Predicate}
|
|
}
|
|
|
|
func (p *parentQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (p *parentQuery) Properties() queryProp {
|
|
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
|
|
}
|
|
|
|
func (p *parentQuery) Test(n NodeNavigator) bool {
|
|
return p.Predicate(n)
|
|
}
|
|
|
|
// selfQuery is an Self node query.(self::*)
|
|
type selfQuery struct {
|
|
Input query
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (s *selfQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
node := s.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
|
|
if s.Predicate(node) {
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
|
|
func (s *selfQuery) Evaluate(t iterator) interface{} {
|
|
s.Input.Evaluate(t)
|
|
return s
|
|
}
|
|
|
|
func (s *selfQuery) Test(n NodeNavigator) bool {
|
|
return s.Predicate(n)
|
|
}
|
|
|
|
func (s *selfQuery) Clone() query {
|
|
return &selfQuery{Input: s.Input.Clone(), Predicate: s.Predicate}
|
|
}
|
|
|
|
func (s *selfQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (s *selfQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
// filterQuery is an XPath query for predicate filter.
|
|
type filterQuery struct {
|
|
Input query
|
|
Predicate query
|
|
NoPosition bool
|
|
|
|
posit int
|
|
positmap map[int]int
|
|
}
|
|
|
|
func (f *filterQuery) do(t iterator) bool {
|
|
val := reflect.ValueOf(f.Predicate.Evaluate(t))
|
|
switch val.Kind() {
|
|
case reflect.Bool:
|
|
return val.Bool()
|
|
case reflect.String:
|
|
return len(val.String()) > 0
|
|
case reflect.Float64:
|
|
pt := getNodePosition(f.Input)
|
|
return int(val.Float()) == pt
|
|
default:
|
|
if f.Predicate != nil {
|
|
return f.Predicate.Select(t) != nil
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (f *filterQuery) position() int {
|
|
return f.posit
|
|
}
|
|
|
|
func (f *filterQuery) Select(t iterator) NodeNavigator {
|
|
if f.positmap == nil {
|
|
f.positmap = make(map[int]int)
|
|
}
|
|
for {
|
|
|
|
node := f.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
node = node.Copy()
|
|
|
|
t.Current().MoveTo(node)
|
|
if f.do(t) {
|
|
// fix https://github.com/antchfx/htmlquery/issues/26
|
|
// Calculate and keep the each of matching node's position in the same depth.
|
|
level := getNodeDepth(f.Input)
|
|
f.positmap[level]++
|
|
f.posit = f.positmap[level]
|
|
return node
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *filterQuery) Evaluate(t iterator) interface{} {
|
|
f.Input.Evaluate(t)
|
|
return f
|
|
}
|
|
|
|
func (f *filterQuery) Clone() query {
|
|
return &filterQuery{Input: f.Input.Clone(), Predicate: f.Predicate.Clone()}
|
|
}
|
|
|
|
func (f *filterQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (f *filterQuery) Properties() queryProp {
|
|
return (queryProps.Position | f.Input.Properties()) & (queryProps.Reverse | queryProps.Merge)
|
|
}
|
|
|
|
// functionQuery is an XPath function that returns a computed value for
|
|
// the Evaluate call of the current NodeNavigator node. Select call isn't
|
|
// applicable for functionQuery.
|
|
type functionQuery struct {
|
|
Input query // Node Set
|
|
Func func(query, iterator) interface{} // The xpath function.
|
|
}
|
|
|
|
func (f *functionQuery) Select(t iterator) NodeNavigator {
|
|
return nil
|
|
}
|
|
|
|
// Evaluate call a specified function that will returns the
|
|
// following value type: number,string,boolean.
|
|
func (f *functionQuery) Evaluate(t iterator) interface{} {
|
|
return f.Func(f.Input, t)
|
|
}
|
|
|
|
func (f *functionQuery) Clone() query {
|
|
return &functionQuery{Input: f.Input.Clone(), Func: f.Func}
|
|
}
|
|
|
|
func (f *functionQuery) ValueType() resultType {
|
|
return xpathResultType.Any
|
|
}
|
|
|
|
func (f *functionQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
// transformFunctionQuery diffs from functionQuery where the latter computes a scalar
|
|
// value (number,string,boolean) for the current NodeNavigator node while the former
|
|
// (transformFunctionQuery) performs a mapping or transform of the current NodeNavigator
|
|
// and returns a new NodeNavigator. It is used for non-scalar XPath functions such as
|
|
// reverse(), remove(), subsequence(), unordered(), etc.
|
|
type transformFunctionQuery struct {
|
|
Input query
|
|
Func func(query, iterator) func() NodeNavigator
|
|
iterator func() NodeNavigator
|
|
}
|
|
|
|
func (f *transformFunctionQuery) Select(t iterator) NodeNavigator {
|
|
if f.iterator == nil {
|
|
f.iterator = f.Func(f.Input, t)
|
|
}
|
|
return f.iterator()
|
|
}
|
|
|
|
func (f *transformFunctionQuery) Evaluate(t iterator) interface{} {
|
|
f.Input.Evaluate(t)
|
|
f.iterator = nil
|
|
return f
|
|
}
|
|
|
|
func (f *transformFunctionQuery) Clone() query {
|
|
return &transformFunctionQuery{Input: f.Input.Clone(), Func: f.Func}
|
|
}
|
|
|
|
func (f *transformFunctionQuery) ValueType() resultType {
|
|
return xpathResultType.Any
|
|
}
|
|
|
|
func (f *transformFunctionQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
// constantQuery is an XPath constant operand.
|
|
type constantQuery struct {
|
|
Val interface{}
|
|
}
|
|
|
|
func (c *constantQuery) Select(t iterator) NodeNavigator {
|
|
return nil
|
|
}
|
|
|
|
func (c *constantQuery) Evaluate(t iterator) interface{} {
|
|
return c.Val
|
|
}
|
|
|
|
func (c *constantQuery) Clone() query {
|
|
return c
|
|
}
|
|
|
|
func (c *constantQuery) ValueType() resultType {
|
|
return getXPathType(c.Val)
|
|
}
|
|
|
|
func (c *constantQuery) Properties() queryProp {
|
|
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
|
|
}
|
|
|
|
type groupQuery struct {
|
|
posit int
|
|
|
|
Input query
|
|
}
|
|
|
|
func (g *groupQuery) Select(t iterator) NodeNavigator {
|
|
node := g.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
g.posit++
|
|
return node
|
|
}
|
|
|
|
func (g *groupQuery) Evaluate(t iterator) interface{} {
|
|
return g.Input.Evaluate(t)
|
|
}
|
|
|
|
func (g *groupQuery) Clone() query {
|
|
return &groupQuery{Input: g.Input.Clone()}
|
|
}
|
|
|
|
func (g *groupQuery) ValueType() resultType {
|
|
return g.Input.ValueType()
|
|
}
|
|
|
|
func (g *groupQuery) Properties() queryProp {
|
|
return queryProps.Position
|
|
}
|
|
|
|
func (g *groupQuery) position() int {
|
|
return g.posit
|
|
}
|
|
|
|
// logicalQuery is an XPath logical expression.
|
|
type logicalQuery struct {
|
|
Left, Right query
|
|
|
|
Do func(iterator, interface{}, interface{}) interface{}
|
|
}
|
|
|
|
func (l *logicalQuery) Select(t iterator) NodeNavigator {
|
|
// When a XPath expr is logical expression.
|
|
node := t.Current().Copy()
|
|
val := l.Evaluate(t)
|
|
switch val.(type) {
|
|
case bool:
|
|
if val.(bool) == true {
|
|
return node
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *logicalQuery) Evaluate(t iterator) interface{} {
|
|
m := l.Left.Evaluate(t)
|
|
n := l.Right.Evaluate(t)
|
|
return l.Do(t, m, n)
|
|
}
|
|
|
|
func (l *logicalQuery) Clone() query {
|
|
return &logicalQuery{Left: l.Left.Clone(), Right: l.Right.Clone(), Do: l.Do}
|
|
}
|
|
|
|
func (l *logicalQuery) ValueType() resultType {
|
|
return xpathResultType.Boolean
|
|
}
|
|
|
|
func (l *logicalQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
// numericQuery is an XPath numeric operator expression.
|
|
type numericQuery struct {
|
|
Left, Right query
|
|
|
|
Do func(iterator, interface{}, interface{}) interface{}
|
|
}
|
|
|
|
func (n *numericQuery) Select(t iterator) NodeNavigator {
|
|
return nil
|
|
}
|
|
|
|
func (n *numericQuery) Evaluate(t iterator) interface{} {
|
|
m := n.Left.Evaluate(t)
|
|
k := n.Right.Evaluate(t)
|
|
return n.Do(t, m, k)
|
|
}
|
|
|
|
func (n *numericQuery) Clone() query {
|
|
return &numericQuery{Left: n.Left.Clone(), Right: n.Right.Clone(), Do: n.Do}
|
|
}
|
|
|
|
func (n *numericQuery) ValueType() resultType {
|
|
return xpathResultType.Number
|
|
}
|
|
|
|
func (n *numericQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
type booleanQuery struct {
|
|
IsOr bool
|
|
Left, Right query
|
|
iterator func() NodeNavigator
|
|
}
|
|
|
|
func (b *booleanQuery) Select(t iterator) NodeNavigator {
|
|
if b.iterator == nil {
|
|
var list []NodeNavigator
|
|
i := 0
|
|
root := t.Current().Copy()
|
|
if b.IsOr {
|
|
for {
|
|
node := b.Left.Select(t)
|
|
if node == nil {
|
|
break
|
|
}
|
|
node = node.Copy()
|
|
list = append(list, node)
|
|
}
|
|
t.Current().MoveTo(root)
|
|
for {
|
|
node := b.Right.Select(t)
|
|
if node == nil {
|
|
break
|
|
}
|
|
node = node.Copy()
|
|
list = append(list, node)
|
|
}
|
|
} else {
|
|
var m []NodeNavigator
|
|
var n []NodeNavigator
|
|
for {
|
|
node := b.Left.Select(t)
|
|
if node == nil {
|
|
break
|
|
}
|
|
node = node.Copy()
|
|
list = append(m, node)
|
|
}
|
|
t.Current().MoveTo(root)
|
|
for {
|
|
node := b.Right.Select(t)
|
|
if node == nil {
|
|
break
|
|
}
|
|
node = node.Copy()
|
|
list = append(n, node)
|
|
}
|
|
for _, k := range m {
|
|
for _, j := range n {
|
|
if k == j {
|
|
list = append(list, k)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
b.iterator = func() NodeNavigator {
|
|
if i >= len(list) {
|
|
return nil
|
|
}
|
|
node := list[i]
|
|
i++
|
|
return node
|
|
}
|
|
}
|
|
return b.iterator()
|
|
}
|
|
|
|
func (b *booleanQuery) Evaluate(t iterator) interface{} {
|
|
n := t.Current().Copy()
|
|
|
|
m := b.Left.Evaluate(t)
|
|
left := asBool(t, m)
|
|
if b.IsOr && left {
|
|
return true
|
|
} else if !b.IsOr && !left {
|
|
return false
|
|
}
|
|
|
|
t.Current().MoveTo(n)
|
|
m = b.Right.Evaluate(t)
|
|
return asBool(t, m)
|
|
}
|
|
|
|
func (b *booleanQuery) Clone() query {
|
|
return &booleanQuery{IsOr: b.IsOr, Left: b.Left.Clone(), Right: b.Right.Clone()}
|
|
}
|
|
|
|
func (b *booleanQuery) ValueType() resultType {
|
|
return xpathResultType.Boolean
|
|
}
|
|
|
|
func (b *booleanQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
type unionQuery struct {
|
|
Left, Right query
|
|
iterator func() NodeNavigator
|
|
}
|
|
|
|
func (u *unionQuery) Select(t iterator) NodeNavigator {
|
|
if u.iterator == nil {
|
|
var list []NodeNavigator
|
|
var m = make(map[uint64]bool)
|
|
root := t.Current().Copy()
|
|
for {
|
|
node := u.Left.Select(t)
|
|
if node == nil {
|
|
break
|
|
}
|
|
code := getHashCode(node.Copy())
|
|
if _, ok := m[code]; !ok {
|
|
m[code] = true
|
|
list = append(list, node.Copy())
|
|
}
|
|
}
|
|
t.Current().MoveTo(root)
|
|
for {
|
|
node := u.Right.Select(t)
|
|
if node == nil {
|
|
break
|
|
}
|
|
code := getHashCode(node.Copy())
|
|
if _, ok := m[code]; !ok {
|
|
m[code] = true
|
|
list = append(list, node.Copy())
|
|
}
|
|
}
|
|
var i int
|
|
u.iterator = func() NodeNavigator {
|
|
if i >= len(list) {
|
|
return nil
|
|
}
|
|
node := list[i]
|
|
i++
|
|
return node
|
|
}
|
|
}
|
|
return u.iterator()
|
|
}
|
|
|
|
func (u *unionQuery) Evaluate(t iterator) interface{} {
|
|
u.iterator = nil
|
|
u.Left.Evaluate(t)
|
|
u.Right.Evaluate(t)
|
|
return u
|
|
}
|
|
|
|
func (u *unionQuery) Clone() query {
|
|
return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()}
|
|
}
|
|
|
|
func (u *unionQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (u *unionQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
type lastQuery struct {
|
|
buffer []NodeNavigator
|
|
counted bool
|
|
|
|
Input query
|
|
}
|
|
|
|
func (q *lastQuery) Select(t iterator) NodeNavigator {
|
|
return nil
|
|
}
|
|
|
|
func (q *lastQuery) Evaluate(t iterator) interface{} {
|
|
if !q.counted {
|
|
for {
|
|
node := q.Input.Select(t)
|
|
if node == nil {
|
|
break
|
|
}
|
|
q.buffer = append(q.buffer, node.Copy())
|
|
}
|
|
q.counted = true
|
|
}
|
|
return float64(len(q.buffer))
|
|
}
|
|
|
|
func (q *lastQuery) Clone() query {
|
|
return &lastQuery{Input: q.Input.Clone()}
|
|
}
|
|
|
|
func (q *lastQuery) ValueType() resultType {
|
|
return xpathResultType.Number
|
|
}
|
|
|
|
func (q *lastQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
type descendantOverDescendantQuery struct {
|
|
name string
|
|
level int
|
|
posit int
|
|
currentNode NodeNavigator
|
|
|
|
Input query
|
|
MatchSelf bool
|
|
Predicate func(NodeNavigator) bool
|
|
}
|
|
|
|
func (d *descendantOverDescendantQuery) moveToFirstChild() bool {
|
|
if d.currentNode.MoveToChild() {
|
|
d.level++
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *descendantOverDescendantQuery) moveUpUntilNext() bool {
|
|
for !d.currentNode.MoveToNext() {
|
|
d.level--
|
|
if d.level == 0 {
|
|
return false
|
|
}
|
|
d.currentNode.MoveToParent()
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (d *descendantOverDescendantQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
if d.level == 0 {
|
|
node := d.Input.Select(t)
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
d.currentNode = node.Copy()
|
|
d.posit = 0
|
|
if d.MatchSelf && d.Predicate(d.currentNode) {
|
|
d.posit = 1
|
|
return d.currentNode
|
|
}
|
|
d.moveToFirstChild()
|
|
} else if !d.moveUpUntilNext() {
|
|
continue
|
|
}
|
|
for ok := true; ok; ok = d.moveToFirstChild() {
|
|
if d.Predicate(d.currentNode) {
|
|
d.posit++
|
|
return d.currentNode
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func (d *descendantOverDescendantQuery) Evaluate(t iterator) interface{} {
|
|
d.Input.Evaluate(t)
|
|
return d
|
|
}
|
|
|
|
func (d *descendantOverDescendantQuery) Clone() query {
|
|
return &descendantOverDescendantQuery{Input: d.Input.Clone(), Predicate: d.Predicate, MatchSelf: d.MatchSelf}
|
|
}
|
|
|
|
func (d *descendantOverDescendantQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (d *descendantOverDescendantQuery) Properties() queryProp {
|
|
return queryProps.Merge
|
|
}
|
|
|
|
func (d *descendantOverDescendantQuery) position() int {
|
|
return d.posit
|
|
}
|
|
|
|
type mergeQuery struct {
|
|
Input query
|
|
Child query
|
|
|
|
iterator func() NodeNavigator
|
|
}
|
|
|
|
func (m *mergeQuery) Select(t iterator) NodeNavigator {
|
|
for {
|
|
if m.iterator == nil {
|
|
root := m.Input.Select(t)
|
|
if root == nil {
|
|
return nil
|
|
}
|
|
m.Child.Evaluate(t)
|
|
root = root.Copy()
|
|
t.Current().MoveTo(root)
|
|
var list []NodeNavigator
|
|
for node := m.Child.Select(t); node != nil; node = m.Child.Select(t) {
|
|
list = append(list, node.Copy())
|
|
}
|
|
i := 0
|
|
m.iterator = func() NodeNavigator {
|
|
if i >= len(list) {
|
|
return nil
|
|
}
|
|
result := list[i]
|
|
i++
|
|
return result
|
|
}
|
|
}
|
|
|
|
if node := m.iterator(); node != nil {
|
|
return node
|
|
}
|
|
m.iterator = nil
|
|
}
|
|
}
|
|
|
|
func (m *mergeQuery) Evaluate(t iterator) interface{} {
|
|
m.Input.Evaluate(t)
|
|
return m
|
|
}
|
|
|
|
func (m *mergeQuery) Clone() query {
|
|
return &mergeQuery{Input: m.Input.Clone(), Child: m.Child.Clone()}
|
|
}
|
|
|
|
func (m *mergeQuery) ValueType() resultType {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
|
|
func (m *mergeQuery) Properties() queryProp {
|
|
return queryProps.Position | queryProps.Count | queryProps.Cached | queryProps.Merge
|
|
}
|
|
|
|
func getHashCode(n NodeNavigator) uint64 {
|
|
var sb bytes.Buffer
|
|
switch n.NodeType() {
|
|
case AttributeNode, TextNode, CommentNode:
|
|
sb.WriteString(fmt.Sprintf("%s=%s", n.LocalName(), n.Value()))
|
|
// https://github.com/antchfx/htmlquery/issues/25
|
|
d := 1
|
|
for n.MoveToPrevious() {
|
|
d++
|
|
}
|
|
sb.WriteString(fmt.Sprintf("-%d", d))
|
|
for n.MoveToParent() {
|
|
d = 1
|
|
for n.MoveToPrevious() {
|
|
d++
|
|
}
|
|
sb.WriteString(fmt.Sprintf("-%d", d))
|
|
}
|
|
case ElementNode:
|
|
sb.WriteString(n.Prefix() + n.LocalName())
|
|
d := 1
|
|
for n.MoveToPrevious() {
|
|
d++
|
|
}
|
|
sb.WriteString(fmt.Sprintf("-%d", d))
|
|
|
|
for n.MoveToParent() {
|
|
d = 1
|
|
for n.MoveToPrevious() {
|
|
d++
|
|
}
|
|
sb.WriteString(fmt.Sprintf("-%d", d))
|
|
}
|
|
}
|
|
h := fnv.New64a()
|
|
h.Write(sb.Bytes())
|
|
return h.Sum64()
|
|
}
|
|
|
|
func getNodePosition(q query) int {
|
|
type Position interface {
|
|
position() int
|
|
}
|
|
if count, ok := q.(Position); ok {
|
|
return count.position()
|
|
}
|
|
return 1
|
|
}
|
|
|
|
func getNodeDepth(q query) int {
|
|
type Depth interface {
|
|
depth() int
|
|
}
|
|
if count, ok := q.(Depth); ok {
|
|
return count.depth()
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func getXPathType(i interface{}) resultType {
|
|
v := reflect.ValueOf(i)
|
|
switch v.Kind() {
|
|
case reflect.Float64:
|
|
return xpathResultType.Number
|
|
case reflect.String:
|
|
return xpathResultType.String
|
|
case reflect.Bool:
|
|
return xpathResultType.Boolean
|
|
default:
|
|
if _, ok := i.(query); ok {
|
|
return xpathResultType.NodeSet
|
|
}
|
|
}
|
|
panic(fmt.Errorf("xpath unknown value type: %v", v.Kind()))
|
|
}
|