Skip to content

Commit

Permalink
fix last() #76,#78
Browse files Browse the repository at this point in the history
  • Loading branch information
zhengchun committed Jan 26, 2023
1 parent f6c2a72 commit ef5e98c
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 33 deletions.
11 changes: 10 additions & 1 deletion build.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,15 @@ func (b *builder) processFunctionNode(root *functionNode) (query, error) {
},
}
case "last":
qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc}
switch typ := b.firstInput.(type) {
case *groupQuery, *filterQuery:
// https://github.com/antchfx/xpath/issues/76
// https://github.com/antchfx/xpath/issues/78
qyOutput = &lastQuery{Input: typ}
default:
qyOutput = &functionQuery{Input: b.firstInput, Func: lastFunc}
}

case "position":
qyOutput = &functionQuery{Input: b.firstInput, Func: positionFunc}
case "boolean", "number", "string":
Expand Down Expand Up @@ -514,6 +522,7 @@ func (b *builder) processNode(root node) (q query, err error) {
b.firstInput = q
case nodeFilter:
q, err = b.processFilterNode(root.(*filterNode))
b.firstInput = q
case nodeFunction:
q, err = b.processFunctionNode(root.(*functionNode))
case nodeOperator:
Expand Down
43 changes: 15 additions & 28 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -894,46 +894,33 @@ func (u *unionQuery) Clone() query {
return &unionQuery{Left: u.Left.Clone(), Right: u.Right.Clone()}
}

type cacheQuery struct {
posit int
buffer []NodeNavigator
iterator func() NodeNavigator
type lastQuery struct {
buffer []NodeNavigator
counted bool

Input query
}

func (c *cacheQuery) Select(t iterator) NodeNavigator {
if c.iterator == nil {
func (q *lastQuery) Select(t iterator) NodeNavigator {
return nil
}

func (q *lastQuery) Evaluate(t iterator) interface{} {
if !q.counted {
for {
node := c.Input.Select(t)
node := q.Input.Select(t)
if node == nil {
break
}
c.buffer = append(c.buffer, node.Copy())
}
c.iterator = func() NodeNavigator {
if c.posit >= len(c.buffer) {
return nil
}
node := c.buffer[c.posit]
c.posit++
return node
q.buffer = append(q.buffer, node.Copy())
}
q.counted = true
}
return c.iterator()
}

func (c *cacheQuery) Evaluate(t iterator) interface{} {
c.posit = 0
return c.Input.Evaluate(t)
}

func (c *cacheQuery) Clone() query {
return &cacheQuery{Input: c.Input.Clone()}
return float64(len(q.buffer))
}

func (c *cacheQuery) position() int {
return c.posit
func (q *lastQuery) Clone() query {
return &lastQuery{Input: q.Input.Clone()}
}

func getHashCode(n NodeNavigator) uint64 {
Expand Down
40 changes: 36 additions & 4 deletions xpath_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,26 @@ func TestSelf(t *testing.T) {
testXPath2(t, html, "//body/./ul/li/a", 3)
}

func TestLastFunc(t *testing.T) {
testXPath3(t, html, "/head[last()]", html.FirstChild)
ul := selectNode(html, "//ul")
testXPath3(t, html, "//li[last()]", ul.LastChild)
list := selectNodes(html, "//li/a[last()]")
if got := len(list); got != 3 {
t.Fatalf("expected %d, but got %d", 3, got)
}
testXPath3(t, html, "(//ul/li)[last()]", ul.LastChild)

n := selectNode(html, "//meta[@name][last()]")
if n == nil {
t.Fatal("should found one, but got nil")
}
if expected, value := "description", n.getAttribute("name"); expected != value {
t.Fatalf("expected, %s but got %s", expected, value)
}

}

func TestParent(t *testing.T) {
testXPath(t, html.LastChild, "..", "html")
testXPath(t, html.LastChild, "parent::*", "html")
Expand Down Expand Up @@ -96,8 +116,8 @@ func TestChild(t *testing.T) {
}

func TestDescendant(t *testing.T) {
testXPath2(t, html, "descendant::*", 15)
testXPath2(t, html, "/head/descendant::*", 2)
testXPath2(t, html, "descendant::*", 16)
testXPath2(t, html, "/head/descendant::*", 3)
testXPath2(t, html, "//ul/descendant::*", 7) // <li> + <a>
testXPath2(t, html, "//ul/descendant::li", 4) // <li>
}
Expand Down Expand Up @@ -168,14 +188,14 @@ func TestStarWide(t *testing.T) {
func TestNodeTestType(t *testing.T) {
testXPath(t, html, "//title/text()", "Hello")
testXPath(t, html, "//a[@href='/']/text()", "Home")
testXPath2(t, html, "//head/node()", 2)
testXPath2(t, html, "//head/node()", 3)
testXPath2(t, html, "//ul/node()", 4)
}

func TestPosition(t *testing.T) {
testXPath3(t, html, "/head[1]", html.FirstChild) // compare to 'head' element
ul := selectNode(html, "//ul")
testXPath3(t, html, "/head[last()]", html.FirstChild)

testXPath3(t, html, "//li[1]", ul.FirstChild)
testXPath3(t, html, "//li[4]", ul.LastChild)
testXPath3(t, html, "//li[last()]", ul.LastChild)
Expand Down Expand Up @@ -636,6 +656,14 @@ func (n *TNode) addAttribute(k, v string) {
n.Attr = append(n.Attr, Attribute{k, v})
}

func (n *TNode) getAttribute(key string) string {
for i := 0; i < len(n.Attr); i++ {
if n.Attr[i].Key == key {
return n.Attr[i].Value
}
}
return ""
}
func example2() *TNode {
/*
<html lang="en">
Expand Down Expand Up @@ -711,6 +739,7 @@ func example() *TNode {
<head>
<title>Hello</title>
<meta name="language" content="en"/>
<meta name="description" content="some description"/>
</head>
<body>
<h1>
Expand Down Expand Up @@ -740,6 +769,9 @@ func example() *TNode {
n = head.createChildNode("meta", ElementNode)
n.addAttribute("name", "language")
n.addAttribute("content", "en")
n = head.createChildNode("meta", ElementNode)
n.addAttribute("name", "description")
n.addAttribute("content", "some description")
// The HTML body section.
body := xhtml.createChildNode("body", ElementNode)
n = body.createChildNode("h1", ElementNode)
Expand Down

0 comments on commit ef5e98c

Please sign in to comment.