From 3c11c35e3321bec28b45be39379b50eed20dea02 Mon Sep 17 00:00:00 2001 From: Brett Vickers Date: Tue, 12 Feb 2019 07:47:29 -0800 Subject: Generalize path function queries. Added unit tests for namespace-based path queries. --- etree.go | 10 ---------- path.go | 39 +++++++++++++++++++-------------------- path_test.go | 21 +++++++++++++++++---- 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 = ` James Linn Vaidyanathan Nagarajan 2003 - 49.99 + 49.99 @@ -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"}}, -- cgit v1.2.3