aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrett Vickers <brett@beevik.com>2019-02-12 07:47:29 -0800
committerBrett Vickers <brett@beevik.com>2019-02-12 07:47:29 -0800
commit3c11c35e3321bec28b45be39379b50eed20dea02 (patch)
tree737dba1f349f1028590f119686e8913f62b90a1d
parent76880d49f6cebdab3d9cfedd7ae3b9e4ebc5f942 (diff)
downloadgo-etree-3c11c35e3321bec28b45be39379b50eed20dea02.tar.gz
Generalize path function queries.
Added unit tests for namespace-based path queries.
-rw-r--r--etree.go10
-rw-r--r--path.go39
-rw-r--r--path_test.go21
3 files changed, 36 insertions, 34 deletions
diff --git a/etree.go b/etree.go
index a24d4d4..259d2aa 100644
--- a/etree.go
+++ b/etree.go
@@ -433,16 +433,6 @@ func (e *Element) findDefaultNamespaceURI() string {
return e.parent.findDefaultNamespaceURI()
}
-// hasText returns true if the element has character data immediately
-// folllowing the element's opening tag.
-func (e *Element) hasText() bool {
- if len(e.Child) == 0 {
- return false
- }
- _, ok := e.Child[0].(*CharData)
- return ok
-}
-
// namespacePrefix returns the namespace prefix associated with the element.
func (e *Element) namespacePrefix() string {
return e.Space
diff --git a/path.go b/path.go
index 82db0ac..8997cb2 100644
--- a/path.go
+++ b/path.go
@@ -46,7 +46,9 @@ The following function filters are also supported:
[text()='val'] Keep elements whose text matches val.
[local-name()='val'] Keep elements whose un-prefixed tag matches val.
[name()='val'] Keep elements whose full tag exactly matches val.
+ [namespace-prefix()] Keep elements with non-empty namespace prefixes.
[namespace-prefix()='val'] Keep elements whose namespace prefix matches val.
+ [namespace-uri()] Keep elements with non-empty namespace URIs.
[namespace-uri()='val'] Keep elements whose namespace URI matches val.
Here are some examples of Path strings:
@@ -280,15 +282,12 @@ func (c *compiler) parseSelector(path string) selector {
}
}
-var fnTable = map[string]struct {
- hasFn func(e *Element) bool
- getValFn func(e *Element) string
-}{
- "local-name": {nil, (*Element).name},
- "name": {nil, (*Element).FullTag},
- "namespace-prefix": {nil, (*Element).namespacePrefix},
- "namespace-uri": {nil, (*Element).NamespaceURI},
- "text": {(*Element).hasText, (*Element).Text},
+var fnTable = map[string]func(e *Element) string{
+ "local-name": (*Element).name,
+ "name": (*Element).FullTag,
+ "namespace-prefix": (*Element).namespacePrefix,
+ "namespace-uri": (*Element).NamespaceURI,
+ "text": (*Element).Text,
}
// parseFilter parses a path filter contained within [brackets].
@@ -314,11 +313,11 @@ func (c *compiler) parseFilter(path string) filter {
case key[0] == '@':
return newFilterAttrVal(key[1:], value)
case strings.HasSuffix(key, "()"):
- fn := key[:len(key)-2]
- if t, ok := fnTable[fn]; ok && t.getValFn != nil {
- return newFilterFuncVal(t.getValFn, value)
+ name := key[:len(key)-2]
+ if fn, ok := fnTable[name]; ok {
+ return newFilterFuncVal(fn, value)
}
- c.err = ErrPath("path has unknown function " + fn)
+ c.err = ErrPath("path has unknown function " + name)
return nil
default:
return newFilterChildText(key, value)
@@ -330,11 +329,11 @@ func (c *compiler) parseFilter(path string) filter {
case path[0] == '@':
return newFilterAttr(path[1:])
case strings.HasSuffix(path, "()"):
- fn := path[:len(path)-2]
- if t, ok := fnTable[fn]; ok && t.hasFn != nil {
- return newFilterFunc(t.hasFn)
+ name := path[:len(path)-2]
+ if fn, ok := fnTable[name]; ok {
+ return newFilterFunc(fn)
}
- c.err = ErrPath("path has unknown function " + fn)
+ c.err = ErrPath("path has unknown function " + name)
return nil
case isInteger(path):
pos, _ := strconv.Atoi(path)
@@ -496,16 +495,16 @@ func (f *filterAttrVal) apply(p *pather) {
// filterFunc filters the candidate list for elements satisfying a custom
// boolean function.
type filterFunc struct {
- fn func(e *Element) bool
+ fn func(e *Element) string
}
-func newFilterFunc(fn func(e *Element) bool) *filterFunc {
+func newFilterFunc(fn func(e *Element) string) *filterFunc {
return &filterFunc{fn}
}
func (f *filterFunc) apply(p *pather) {
for _, c := range p.candidates {
- if f.fn(c) {
+ if f.fn(c) != "" {
p.scratch = append(p.scratch, c)
}
}
diff --git a/path_test.go b/path_test.go
index d94968a..0e02227 100644
--- a/path_test.go
+++ b/path_test.go
@@ -37,7 +37,7 @@ var testXML = `
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
- <p:price>49.99</p:price>
+ <price>49.99</p:price>
<editor>
</editor>
</book>
@@ -66,7 +66,7 @@ var tests = []test{
{"./bookstore/book/title", []string{"Everyday Italian", "Harry Potter", "XQuery Kick Start", "Learning XML"}},
{"./bookstore/book/author", []string{"Giada De Laurentiis", "J K. Rowling", "James McGovern", "Per Bothner", "Kurt Cagle", "James Linn", "Vaidyanathan Nagarajan", "Erik T. Ray"}},
{"./bookstore/book/year", []string{"2005", "2005", "2003", "2003"}},
- {"./bookstore/book/p:price", []string{"30.00", "29.99", "49.99", "39.95"}},
+ {"./bookstore/book/p:price", []string{"30.00", "29.99", "39.95"}},
{"./bookstore/book/isbn", nil},
// descendant queries
@@ -75,7 +75,7 @@ var tests = []test{
{".//title", []string{"Everyday Italian", "Harry Potter", "XQuery Kick Start", "Learning XML"}},
{".//bookstore//title", []string{"Everyday Italian", "Harry Potter", "XQuery Kick Start", "Learning XML"}},
{".//book/title", []string{"Everyday Italian", "Harry Potter", "XQuery Kick Start", "Learning XML"}},
- {".//p:price/.", []string{"30.00", "29.99", "49.99", "39.95"}},
+ {".//p:price/.", []string{"30.00", "29.99", "39.95"}},
{".//price", []string{"30.00", "29.99", "49.99", "39.95"}},
// positional queries
@@ -90,7 +90,7 @@ var tests = []test{
{"./bookstore/book[-4]/title", "Everyday Italian"},
{"./bookstore/book[-5]/title", nil},
- // text queries
+ // text function queries
{"./bookstore/book[author='James McGovern']/title", "XQuery Kick Start"},
{"./bookstore/book[author='Per Bothner']/title", "XQuery Kick Start"},
{"./bookstore/book[author='Kurt Cagle']/title", "XQuery Kick Start"},
@@ -102,6 +102,19 @@ var tests = []test{
{"//book/author[text()='Kurt Cagle']", "Kurt Cagle"},
{"//book/editor[text()]", []string{"Clarkson Potter", "\n\t\t"}},
+ // namespace function queries
+ {"//*[namespace-uri()]", []string{"30.00", "29.99", "39.95"}},
+ {"//*[namespace-uri()='urn:books-com:prices']", []string{"30.00", "29.99", "39.95"}},
+ {"//*[namespace-uri()='foo']", nil},
+ {"//*[namespace-prefix()]", []string{"30.00", "29.99", "39.95"}},
+ {"//*[namespace-prefix()='p']", []string{"30.00", "29.99", "39.95"}},
+ {"//*[name()='p:price']", []string{"30.00", "29.99", "39.95"}},
+ {"//*[local-name()='price']", []string{"30.00", "29.99", "49.99", "39.95"}},
+ {"//price[namespace-uri()='']", []string{"49.99"}},
+ {"//price[namespace-prefix()='']", []string{"49.99"}},
+ {"//price[name()='price']", []string{"49.99"}},
+ {"//price[local-name()='price']", []string{"30.00", "29.99", "49.99", "39.95"}},
+
// attribute queries
{"./bookstore/book[@category='WEB']/title", []string{"XQuery Kick Start", "Learning XML"}},
{"./bookstore/book[@path='/books/xml']/title", []string{"Learning XML"}},