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
=== modified file 'all_test.go'
--- all_test.go 2013-06-14 04:31:38 +0000
+++ all_test.go 2014-02-18 18:51:14 +0000
@@ -279,6 +279,79 @@
279</library>279</library>
280`)280`)
281281
282func (s *BasicSuite) TestNamespace(c *C) {
283 node, err := xmlpath.Parse(bytes.NewBuffer(namespaceXml))
284 c.Assert(err, IsNil)
285 for _, test := range namespaceTable {
286 cmt := Commentf("xml path: %s", test.path)
287 path, err := xmlpath.CompileWithNamespace(test.path, namespaces)
288 if want, ok := test.result.(cerror); ok {
289 c.Assert(err, ErrorMatches, string(want), cmt)
290 c.Assert(path, IsNil, cmt)
291 continue
292 }
293 c.Assert(err, IsNil)
294 switch want := test.result.(type) {
295 case string:
296 got, ok := path.String(node)
297 c.Assert(ok, Equals, true, cmt)
298 c.Assert(got, Equals, want, cmt)
299 c.Assert(path.Exists(node), Equals, true, cmt)
300 iter := path.Iter(node)
301 iter.Next()
302 node := iter.Node()
303 c.Assert(node.String(), Equals, want, cmt)
304 c.Assert(string(node.Bytes()), Equals, want, cmt)
305 case []string:
306 var alls []string
307 var allb []string
308 iter := path.Iter(node)
309 for iter.Next() {
310 alls = append(alls, iter.Node().String())
311 allb = append(allb, string(iter.Node().Bytes()))
312 }
313 c.Assert(alls, DeepEquals, want, cmt)
314 c.Assert(allb, DeepEquals, want, cmt)
315 s, sok := path.String(node)
316 b, bok := path.Bytes(node)
317 if len(want) == 0 {
318 c.Assert(sok, Equals, false, cmt)
319 c.Assert(bok, Equals, false, cmt)
320 c.Assert(s, Equals, "")
321 c.Assert(b, IsNil)
322 } else {
323 c.Assert(sok, Equals, true, cmt)
324 c.Assert(bok, Equals, true, cmt)
325 c.Assert(s, Equals, alls[0], cmt)
326 c.Assert(string(b), Equals, alls[0], cmt)
327 c.Assert(path.Exists(node), Equals, true, cmt)
328 }
329 case exists:
330 wantb := bool(want)
331 ok := path.Exists(node)
332 c.Assert(ok, Equals, wantb, cmt)
333 _, ok = path.String(node)
334 c.Assert(ok, Equals, wantb, cmt)
335 }
336 }
337}
338
339var 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>`)
340
341var namespaces = []xmlpath.Namespace {
342 { "a", "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
343 { "rsp", "http://schemas.microsoft.com/wbem/wsman/1/windows/shell" },
344}
345
346var namespaceTable = []struct{ path string; result interface{} }{
347 { "//a:To", "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous" },
348 { "//rsp:Stream[@Name='stdout']", "VGhhdCdzIGFsbCBmb2xrcyEhIQ==" },
349 { "//rsp:CommandState/@CommandId", "1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4" },
350 { "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']", exists(false) },
351 { "//rsp:Stream", []string{ "VGhhdCdzIGFsbCBmb2xrcyEhIQ==", "VGhpcyBpcyBzdGRlcnIsIEknbSBwcmV0dHkgc3VyZSE=" }},
352 { "//s:Header", cerror(`.*: unknown namespace prefix: s`) },
353}
354
282func (s *BasicSuite) BenchmarkParse(c *C) {355func (s *BasicSuite) BenchmarkParse(c *C) {
283 for i := 0; i < c.N; i++ {356 for i := 0; i < c.N; i++ {
284 _, err := xmlpath.Parse(bytes.NewBuffer(instancesXml))357 _, err := xmlpath.Parse(bytes.NewBuffer(instancesXml))
285358
=== modified file 'doc.go'
--- doc.go 2013-06-07 19:21:41 +0000
+++ doc.go 2014-02-18 18:51:14 +0000
@@ -17,7 +17,8 @@
17// - All node types except for namespace are supported17// - All node types except for namespace are supported
18// - Predicates are restricted to [N], [path], and [path=literal] forms18// - Predicates are restricted to [N], [path], and [path=literal] forms
19// - Only a single predicate is supported per path step19// - Only a single predicate is supported per path step
20// - Richer expressions and namespaces are not supported20// - Namespaces are experimentally supported
21// - Richer expressions
21//22//
22// For example, assuming the following document:23// For example, assuming the following document:
23//24//
@@ -70,4 +71,25 @@
70// fmt.Println("Found:", value)71// fmt.Println("Found:", value)
71// }72// }
72//73//
74// To use xmlpath with namespaces, it is required to give the supported set of namespace
75// when compiling:
76//
77//
78// var namespaces = []xmlpath.Namespace {
79// { "s", "http://www.w3.org/2003/05/soap-envelope" },
80// { "a", "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
81// }
82// path, err := xmlpath.CompileWithNamespace("/s:Header/a:To", namespaces)
83// if err != nil {
84// log.Fatal(err)
85// }
86// root, err := xmlpath.Parse(file)
87// if err != nil {
88// log.Fatal(err)
89// }
90// if value, ok := path.String(root); ok {
91// fmt.Println("Found:", value)
92// }
93//
94
73package xmlpath95package xmlpath
7496
=== modified file 'path.go'
--- path.go 2013-06-07 19:21:41 +0000
+++ path.go 2014-02-18 18:51:14 +0000
@@ -6,6 +6,12 @@
6 "unicode/utf8"6 "unicode/utf8"
7)7)
88
9// Namespace represents a given XML Namespace
10type Namespace struct {
11 Prefix string
12 Uri string
13}
14
9// Path is a compiled path that can be applied to a context15// Path is a compiled path that can be applied to a context
10// node to obtain a matching node set.16// node to obtain a matching node set.
11// A single Path can be applied concurrently to any number17// A single Path can be applied concurrently to any number
@@ -339,17 +345,19 @@
339}345}
340346
341type pathStep struct {347type pathStep struct {
342 root bool348 root bool
343 axis string349 axis string
344 name string350 name string
345 kind nodeKind351 prefix string
346 pred *pathPredicate352 uri string
353 kind nodeKind
354 pred *pathPredicate
347}355}
348356
349func (step *pathStep) match(node *Node) bool {357func (step *pathStep) match(node *Node) bool {
350 return node.kind != endNode &&358 return node.kind != endNode &&
351 (step.kind == anyNode || step.kind == node.kind) &&359 (step.kind == anyNode || step.kind == node.kind) &&
352 (step.name == "*" || node.name.Local == step.name)360 (step.name == "*" || (node.name.Local == step.name && (node.name.Space != "" && node.name.Space == step.uri || node.name.Space == "")))
353}361}
354362
355// MustCompile returns the compiled path, and panics if363// MustCompile returns the compiled path, and panics if
@@ -364,7 +372,20 @@
364372
365// Compile returns the compiled path.373// Compile returns the compiled path.
366func Compile(path string) (*Path, error) {374func Compile(path string) (*Path, error) {
367 c := pathCompiler{path, 0}375 c := pathCompiler{path, 0, []Namespace{} }
376 if path == "" {
377 return nil, c.errorf("empty path")
378 }
379 p, err := c.parsePath()
380 if err != nil {
381 return nil, err
382 }
383 return p, nil
384}
385
386// Compile the path with the knowledge of the given namespaces
387func CompileWithNamespace(path string, ns []Namespace) (*Path, error) {
388 c := pathCompiler{path, 0, ns}
368 if path == "" {389 if path == "" {
369 return nil, c.errorf("empty path")390 return nil, c.errorf("empty path")
370 }391 }
@@ -378,6 +399,7 @@
378type pathCompiler struct {399type pathCompiler struct {
379 path string400 path string
380 i int401 i int
402 ns []Namespace
381}403}
382404
383func (c *pathCompiler) errorf(format string, args ...interface{}) error {405func (c *pathCompiler) errorf(format string, args ...interface{}) error {
@@ -425,26 +447,45 @@
425 } else {447 } else {
426 if c.skipByte(':') {448 if c.skipByte(':') {
427 if !c.skipByte(':') {449 if !c.skipByte(':') {
428 return nil, c.errorf("missing ':'")450 mark = c.i
429 }451 if c.skipName() {
430 switch step.name {452 step.prefix = step.name
431 case "attribute":453 step.name = c.path[mark:c.i]
432 step.kind = attrNode454 // check prefix
433 case "self", "child", "parent":455 found := false
434 case "descendant", "descendant-or-self":456 for _, ns := range c.ns {
435 case "ancestor", "ancestor-or-self":457 if ns.Prefix == step.prefix {
436 case "following", "following-sibling":458 step.uri = ns.Uri
437 case "preceding", "preceding-sibling":459 found = true
438 default:460 break
439 return nil, c.errorf("unsupported axis: %q", step.name)461 }
440 }462 }
441 step.axis = step.name463 if !found {
464 return nil, c.errorf("unknown namespace prefix: %s", step.prefix)
465 }
466 } else {
467 return nil, c.errorf("missing name after namespace prefix")
468 }
469 } else {
470 switch step.name {
471 case "attribute":
472 step.kind = attrNode
473 case "self", "child", "parent":
474 case "descendant", "descendant-or-self":
475 case "ancestor", "ancestor-or-self":
476 case "following", "following-sibling":
477 case "preceding", "preceding-sibling":
478 default:
479 return nil, c.errorf("unsupported axis: %q", step.name)
480 }
481 step.axis = step.name
442482
443 mark = c.i483 mark = c.i
444 if !c.skipName() {484 if !c.skipName() {
445 return nil, c.errorf("missing name")485 return nil, c.errorf("missing name")
486 }
487 step.name = c.path[mark:c.i]
446 }488 }
447 step.name = c.path[mark:c.i]
448 }489 }
449 if c.skipByte('(') {490 if c.skipByte('(') {
450 conflict := step.kind != anyNode491 conflict := step.kind != anyNode

Subscribers

People subscribed via source and target branches