Merge lp:~brice-figureau/xmlpath/namespace-support into lp:xmlpath

Proposed by Brice figureau
Status: Needs review
Proposed branch: lp:~brice-figureau/xmlpath/namespace-support
Merge into: lp:xmlpath
Diff against target: 260 lines (+162/-26)
3 files modified
all_test.go (+73/-0)
doc.go (+23/-1)
path.go (+66/-25)
To merge this branch: bzr merge lp:~brice-figureau/xmlpath/namespace-support
Reviewer Review Type Date Requested Status
Gustavo Niemeyer Pending
Review via email: mp+207015@code.launchpad.net

Description of the change

This patch provides xml namespace support to the xmlpath library.

To post a comment you must log in.

Unmerged revisions

5. By <email address hidden>

Experimental support for XML Namespaces

This patch adds support for XML Namespaces. The user
needs to submit the list of known Namespaces during
the path compilation.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'all_test.go'
2--- all_test.go 2013-06-14 04:31:38 +0000
3+++ all_test.go 2014-02-18 18:51:14 +0000
4@@ -279,6 +279,79 @@
5 </library>
6 `)
7
8+func (s *BasicSuite) TestNamespace(c *C) {
9+ node, err := xmlpath.Parse(bytes.NewBuffer(namespaceXml))
10+ c.Assert(err, IsNil)
11+ for _, test := range namespaceTable {
12+ cmt := Commentf("xml path: %s", test.path)
13+ path, err := xmlpath.CompileWithNamespace(test.path, namespaces)
14+ if want, ok := test.result.(cerror); ok {
15+ c.Assert(err, ErrorMatches, string(want), cmt)
16+ c.Assert(path, IsNil, cmt)
17+ continue
18+ }
19+ c.Assert(err, IsNil)
20+ switch want := test.result.(type) {
21+ case string:
22+ got, ok := path.String(node)
23+ c.Assert(ok, Equals, true, cmt)
24+ c.Assert(got, Equals, want, cmt)
25+ c.Assert(path.Exists(node), Equals, true, cmt)
26+ iter := path.Iter(node)
27+ iter.Next()
28+ node := iter.Node()
29+ c.Assert(node.String(), Equals, want, cmt)
30+ c.Assert(string(node.Bytes()), Equals, want, cmt)
31+ case []string:
32+ var alls []string
33+ var allb []string
34+ iter := path.Iter(node)
35+ for iter.Next() {
36+ alls = append(alls, iter.Node().String())
37+ allb = append(allb, string(iter.Node().Bytes()))
38+ }
39+ c.Assert(alls, DeepEquals, want, cmt)
40+ c.Assert(allb, DeepEquals, want, cmt)
41+ s, sok := path.String(node)
42+ b, bok := path.Bytes(node)
43+ if len(want) == 0 {
44+ c.Assert(sok, Equals, false, cmt)
45+ c.Assert(bok, Equals, false, cmt)
46+ c.Assert(s, Equals, "")
47+ c.Assert(b, IsNil)
48+ } else {
49+ c.Assert(sok, Equals, true, cmt)
50+ c.Assert(bok, Equals, true, cmt)
51+ c.Assert(s, Equals, alls[0], cmt)
52+ c.Assert(string(b), Equals, alls[0], cmt)
53+ c.Assert(path.Exists(node), Equals, true, cmt)
54+ }
55+ case exists:
56+ wantb := bool(want)
57+ ok := path.Exists(node)
58+ c.Assert(ok, Equals, wantb, cmt)
59+ _, ok = path.String(node)
60+ c.Assert(ok, Equals, wantb, cmt)
61+ }
62+ }
63+}
64+
65+var namespaceXml = []byte(`<s:Envelope xml:lang="en-US" xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"><s:Header><a:Action>http://schemas.microsoft.com/wbem/wsman/1/windows/shell/ReceiveResponse</a:Action><a:MessageID>uuid:AAD46BD4-6315-4C3C-93D4-94A55773287D</a:MessageID><a:To>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:To><a:RelatesTo>uuid:18A52A06-9027-41DC-8850-3F244595AF62</a:RelatesTo></s:Header><s:Body><rsp:ReceiveResponse><rsp:Stream Name="stdout" CommandId="1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4">VGhhdCdzIGFsbCBmb2xrcyEhIQ==</rsp:Stream><rsp:Stream Name="stderr" CommandId="1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4">VGhpcyBpcyBzdGRlcnIsIEknbSBwcmV0dHkgc3VyZSE=</rsp:Stream><rsp:CommandState CommandId="1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4" State="http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Running"></rsp:CommandState></rsp:ReceiveResponse></s:Body></s:Envelope>`)
66+
67+var namespaces = []xmlpath.Namespace {
68+ { "a", "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
69+ { "rsp", "http://schemas.microsoft.com/wbem/wsman/1/windows/shell" },
70+}
71+
72+var namespaceTable = []struct{ path string; result interface{} }{
73+ { "//a:To", "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous" },
74+ { "//rsp:Stream[@Name='stdout']", "VGhhdCdzIGFsbCBmb2xrcyEhIQ==" },
75+ { "//rsp:CommandState/@CommandId", "1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4" },
76+ { "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']", exists(false) },
77+ { "//rsp:Stream", []string{ "VGhhdCdzIGFsbCBmb2xrcyEhIQ==", "VGhpcyBpcyBzdGRlcnIsIEknbSBwcmV0dHkgc3VyZSE=" }},
78+ { "//s:Header", cerror(`.*: unknown namespace prefix: s`) },
79+}
80+
81 func (s *BasicSuite) BenchmarkParse(c *C) {
82 for i := 0; i < c.N; i++ {
83 _, err := xmlpath.Parse(bytes.NewBuffer(instancesXml))
84
85=== modified file 'doc.go'
86--- doc.go 2013-06-07 19:21:41 +0000
87+++ doc.go 2014-02-18 18:51:14 +0000
88@@ -17,7 +17,8 @@
89 // - All node types except for namespace are supported
90 // - Predicates are restricted to [N], [path], and [path=literal] forms
91 // - Only a single predicate is supported per path step
92-// - Richer expressions and namespaces are not supported
93+// - Namespaces are experimentally supported
94+// - Richer expressions
95 //
96 // For example, assuming the following document:
97 //
98@@ -70,4 +71,25 @@
99 // fmt.Println("Found:", value)
100 // }
101 //
102+// To use xmlpath with namespaces, it is required to give the supported set of namespace
103+// when compiling:
104+//
105+//
106+// var namespaces = []xmlpath.Namespace {
107+// { "s", "http://www.w3.org/2003/05/soap-envelope" },
108+// { "a", "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
109+// }
110+// path, err := xmlpath.CompileWithNamespace("/s:Header/a:To", namespaces)
111+// if err != nil {
112+// log.Fatal(err)
113+// }
114+// root, err := xmlpath.Parse(file)
115+// if err != nil {
116+// log.Fatal(err)
117+// }
118+// if value, ok := path.String(root); ok {
119+// fmt.Println("Found:", value)
120+// }
121+//
122+
123 package xmlpath
124
125=== modified file 'path.go'
126--- path.go 2013-06-07 19:21:41 +0000
127+++ path.go 2014-02-18 18:51:14 +0000
128@@ -6,6 +6,12 @@
129 "unicode/utf8"
130 )
131
132+// Namespace represents a given XML Namespace
133+type Namespace struct {
134+ Prefix string
135+ Uri string
136+}
137+
138 // Path is a compiled path that can be applied to a context
139 // node to obtain a matching node set.
140 // A single Path can be applied concurrently to any number
141@@ -339,17 +345,19 @@
142 }
143
144 type pathStep struct {
145- root bool
146- axis string
147- name string
148- kind nodeKind
149- pred *pathPredicate
150+ root bool
151+ axis string
152+ name string
153+ prefix string
154+ uri string
155+ kind nodeKind
156+ pred *pathPredicate
157 }
158
159 func (step *pathStep) match(node *Node) bool {
160 return node.kind != endNode &&
161 (step.kind == anyNode || step.kind == node.kind) &&
162- (step.name == "*" || node.name.Local == step.name)
163+ (step.name == "*" || (node.name.Local == step.name && (node.name.Space != "" && node.name.Space == step.uri || node.name.Space == "")))
164 }
165
166 // MustCompile returns the compiled path, and panics if
167@@ -364,7 +372,20 @@
168
169 // Compile returns the compiled path.
170 func Compile(path string) (*Path, error) {
171- c := pathCompiler{path, 0}
172+ c := pathCompiler{path, 0, []Namespace{} }
173+ if path == "" {
174+ return nil, c.errorf("empty path")
175+ }
176+ p, err := c.parsePath()
177+ if err != nil {
178+ return nil, err
179+ }
180+ return p, nil
181+}
182+
183+// Compile the path with the knowledge of the given namespaces
184+func CompileWithNamespace(path string, ns []Namespace) (*Path, error) {
185+ c := pathCompiler{path, 0, ns}
186 if path == "" {
187 return nil, c.errorf("empty path")
188 }
189@@ -378,6 +399,7 @@
190 type pathCompiler struct {
191 path string
192 i int
193+ ns []Namespace
194 }
195
196 func (c *pathCompiler) errorf(format string, args ...interface{}) error {
197@@ -425,26 +447,45 @@
198 } else {
199 if c.skipByte(':') {
200 if !c.skipByte(':') {
201- return nil, c.errorf("missing ':'")
202- }
203- switch step.name {
204- case "attribute":
205- step.kind = attrNode
206- case "self", "child", "parent":
207- case "descendant", "descendant-or-self":
208- case "ancestor", "ancestor-or-self":
209- case "following", "following-sibling":
210- case "preceding", "preceding-sibling":
211- default:
212- return nil, c.errorf("unsupported axis: %q", step.name)
213- }
214- step.axis = step.name
215+ mark = c.i
216+ if c.skipName() {
217+ step.prefix = step.name
218+ step.name = c.path[mark:c.i]
219+ // check prefix
220+ found := false
221+ for _, ns := range c.ns {
222+ if ns.Prefix == step.prefix {
223+ step.uri = ns.Uri
224+ found = true
225+ break
226+ }
227+ }
228+ if !found {
229+ return nil, c.errorf("unknown namespace prefix: %s", step.prefix)
230+ }
231+ } else {
232+ return nil, c.errorf("missing name after namespace prefix")
233+ }
234+ } else {
235+ switch step.name {
236+ case "attribute":
237+ step.kind = attrNode
238+ case "self", "child", "parent":
239+ case "descendant", "descendant-or-self":
240+ case "ancestor", "ancestor-or-self":
241+ case "following", "following-sibling":
242+ case "preceding", "preceding-sibling":
243+ default:
244+ return nil, c.errorf("unsupported axis: %q", step.name)
245+ }
246+ step.axis = step.name
247
248- mark = c.i
249- if !c.skipName() {
250- return nil, c.errorf("missing name")
251+ mark = c.i
252+ if !c.skipName() {
253+ return nil, c.errorf("missing name")
254+ }
255+ step.name = c.path[mark:c.i]
256 }
257- step.name = c.path[mark:c.i]
258 }
259 if c.skipByte('(') {
260 conflict := step.kind != anyNode

Subscribers

People subscribed via source and target branches