Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ var libraryTable = []struct {
{"//*[contains(born,'1922')]/name", "Charles M Schulz"},
{"library/book[not(@id)]", exists(false)},
{"library/book[not(@foo) and @id='b0883556316']/isbn", []string{"0883556316"}},
{"//character[starts-with(@id, 'Snoo')]/name", "Snoopy"},
{"//character[starts-with(@id, 'Snoopy ')]/name", exists(false)},
{"//character[starts-with(@id, 'noopy')]/name", exists(false)},
{"//title[starts-with(.,'Barney Goo')]", "Barney Google and Snuffy Smith"},
{"//title[starts-with(., 'noopy')]", exists(false)},

// Multiple predicates.
{"library/book/character[@id='Snoopy' and ./born='1950-10-04']/born", []string{"1950-10-04"}},
Expand Down
3 changes: 2 additions & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// - All axes are supported ("child", "following-sibling", etc)
// - All abbreviated forms are supported (".", "//", etc)
// - All node types except for namespace are supported
// - Predicates may be [N], [path], [not(path)], [path=literal] or [contains(path, literal)]
// - Predicates may be [N], [path], [not(path)], [path=literal], [contains(path, literal)] or [starts-with(@path, literal)]
// - Predicates may be joined with "or", "and", and parenthesis
// - Richer expressions and namespaces are not supported
//
Expand Down Expand Up @@ -58,6 +58,7 @@
// //book[author/@id='CMS']/title => "Being a Dog Is a Full-Time Job",
// /library/book/preceding::comment() => " Great book. "
// //*[contains(born,'1922')]/name => "Charles M Schulz"
// //character[starts-with(@id, 'Snoo')]/name => "Snoopy"
// //*[@id='PP' or @id='Snoopy']/born => {"1966-08-22", "1950-10-04"}
//
// To run an expression, compile it, and then apply the compiled path to any
Expand Down
26 changes: 26 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,32 @@ func (node *Node) contains(s string) (ok bool) {
return false
}

// startswith returns whether the string value of node has prefix s
func (node *Node) startsWith(s string) (ok bool) {
if len(s) == 0 {
return true
}
if node.kind == attrNode {
return strings.HasPrefix(node.attr, s)
}
si := 0
for i := node.pos; i < node.end; i++ {
if node.nodes[i].kind != textNode {
continue
}
for _, c := range node.nodes[i].text {
if c != s[si] {
break
}
si++
if si == len(s) {
return true
}
}
}
return false
}

// Parse reads an xml document from r, parses it, and returns its root node.
func Parse(r io.Reader) (*Node, error) {
return ParseDecoder(xml.NewDecoder(r))
Expand Down
46 changes: 39 additions & 7 deletions path.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,13 @@ func (s *pathStepState) test(pred predicate) bool {
return true
}
}
case startsWithPredicate:
iter := pred.path.Iter(s.node)
for iter.Next() {
if iter.Node().startsWith(pred.value) {
return true
}
}
case notPredicate:
iter := pred.path.Iter(s.node)
if !iter.Next() {
Expand Down Expand Up @@ -381,6 +388,11 @@ type containsPredicate struct {
value string
}

type startsWithPredicate struct {
path *Path
value string
}

type notPredicate struct {
path *Path
}
Expand All @@ -398,13 +410,14 @@ type predicate interface {
predicate()
}

func (positionPredicate) predicate() {}
func (existsPredicate) predicate() {}
func (equalsPredicate) predicate() {}
func (containsPredicate) predicate() {}
func (notPredicate) predicate() {}
func (andPredicate) predicate() {}
func (orPredicate) predicate() {}
func (positionPredicate) predicate() {}
func (existsPredicate) predicate() {}
func (equalsPredicate) predicate() {}
func (containsPredicate) predicate() {}
func (startsWithPredicate) predicate() {}
func (notPredicate) predicate() {}
func (andPredicate) predicate() {}
func (orPredicate) predicate() {}

type pathStep struct {
root bool
Expand Down Expand Up @@ -600,6 +613,25 @@ func (c *pathCompiler) parsePath() (path *Path, err error) {
return nil, c.errorf("contains() missing ')'")
}
next = containsPredicate{path, value}
} else if c.skipString("starts-with(") {
path, err := c.parsePath()
if err != nil {
return nil, err
}
c.skipSpaces()
if !c.skipByte(',') {
return nil, c.errorf("starts-with() expected ',' followed by a literal string")
}
c.skipSpaces()
value, err := c.parseLiteral()
if err != nil {
return nil, err
}
c.skipSpaces()
if !c.skipByte(')') {
return nil, c.errorf("starts-with() missing ')'")
}
next = startsWithPredicate{path, value}
} else if c.skipString("not(") {
// TODO Generalize to handle any predicate expression.
path, err := c.parsePath()
Expand Down