Skip to content

Commit

Permalink
add support for absolute path queries.
Browse files Browse the repository at this point in the history
Paths that are prefixed with '/' or '//' are now handled.  If a
path is prefixed with '//', it will perform an all-descendant
search starting from the root.  If a path is prefixed with a
single '/', it will perform a search starting from the root.

THIS IS A BREAKING CHANGE FOR '//' QUERIES.

Previously, a path that was prefixed with '//' would begin from
the current element, not from the root. To retain the old
behavior, the path must be prefixed with './/' instead of '//'.
  • Loading branch information
beevik committed Apr 26, 2018
1 parent 3c7a5c1 commit 4d4283b
Show file tree
Hide file tree
Showing 2 changed files with 58 additions and 14 deletions.
38 changes: 25 additions & 13 deletions path.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,11 @@ only the following limited syntax is supported:
Examples:
Select the title elements of all descendant book elements having a
'category' attribute of 'WEB':
Starting from the root element, select the bookstore child element:
/bookstore
Starting from the root element, select the title elements of all descendant
book elements having a 'category' attribute of 'WEB':
//book[@category='WEB']/title
Select the first book element with a title child containing the text
Expand All @@ -49,7 +52,7 @@ containing the text 'special':
Select all descendant book elements whose title element has an attribute
'language' set to 'french':
//book/title[@language='french']/..
.//book/title[@language='french']/..
*/
type Path struct {
Expand Down Expand Up @@ -180,22 +183,20 @@ type compiler struct {
// through an element tree and returns a slice of segment
// descriptors.
func (c *compiler) parsePath(path string) []segment {
// If path starts or ends with //, fix it
if strings.HasPrefix(path, "//") {
path = "." + path
}
// If path ends with //, fix it
if strings.HasSuffix(path, "//") {
path = path + "*"
}

// Paths cannot be absolute
var segments []segment

// Check for an absolute path
if strings.HasPrefix(path, "/") {
c.err = ErrPath("paths cannot be absolute.")
return nil
segments = append(segments, segment{new(selectRoot), []filter{}})
path = path[1:]
}

// Split path into segment objects
var segments []segment
// Split path into segments
for _, s := range splitPath(path) {
segments = append(segments, c.parseSegment(s))
if c.err != ErrPath("") {
Expand Down Expand Up @@ -225,7 +226,7 @@ func (c *compiler) parseSegment(path string) segment {
pieces := strings.Split(path, "[")
seg := segment{
sel: c.parseSelector(pieces[0]),
filters: make([]filter, 0),
filters: []filter{},
}
for i := 1; i < len(pieces); i++ {
fpath := pieces[i]
Expand Down Expand Up @@ -305,6 +306,17 @@ func (s *selectSelf) apply(e *Element, p *pather) {
p.candidates = append(p.candidates, e)
}

// selectRoot selects the element's root node.
type selectRoot struct{}

func (s *selectRoot) apply(e *Element, p *pather) {
root := e
for root.parent != nil {
root = root.parent
}
p.candidates = append(p.candidates, root)
}

// selectParent selects the element's parent into the candidate list.
type selectParent struct{}

Expand Down
34 changes: 33 additions & 1 deletion path_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,19 @@ var tests = []test{
// parent queries
{"./bookstore/book[@category='COOKING']/title/../../book[4]/title", "Learning XML"},

// root queries
{"/bookstore/book[1]/title", "Everyday Italian"},
{"/bookstore/book[4]/title", "Learning XML"},
{"/bookstore/book[5]/title", nil},
{"/bookstore/book[3]/author[0]", "James McGovern"},
{"/bookstore/book[3]/author[1]", "James McGovern"},
{"/bookstore/book[3]/author[3]/./.", "Kurt Cagle"},
{"/bookstore/book[3]/author[6]", nil},
{"/bookstore/book[-1]/title", "Learning XML"},
{"/bookstore/book[-4]/title", "Everyday Italian"},
{"/bookstore/book[-5]/title", nil},

// bad paths
{"/bookstore", errorResult("etree: paths cannot be absolute.")},
{"./bookstore/book[]", errorResult("etree: path contains an empty filter expression.")},
{"./bookstore/book[@category='WEB'", errorResult("etree: path has invalid filter [brackets].")},
{"./bookstore/book[@category='WEB]", errorResult("etree: path has mismatched filter quotes.")},
Expand Down Expand Up @@ -171,3 +182,24 @@ func TestPath(t *testing.T) {
func fail(t *testing.T, test test) {
t.Errorf("etree: failed test '%s'\n", test.path)
}

func TestAbsolutePath(t *testing.T) {
doc := NewDocument()
err := doc.ReadFromString(testXML)
if err != nil {
t.Error(err)
}

elements := doc.FindElements("//book/author")
for _, e := range elements {
title := e.FindElement("/bookstore/book[1]/title")
if title == nil || title.Text() != "Everyday Italian" {
t.Errorf("etree: absolute path test failed")
}

title = e.FindElement("//book[p:price='29.99']/title")
if title == nil || title.Text() != "Harry Potter" {
t.Errorf("etree: absolute path test failed")
}
}
}

0 comments on commit 4d4283b

Please sign in to comment.