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
74 changes: 74 additions & 0 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,80 @@ var libraryXml = []byte(`
</library>
`)

func (s *BasicSuite) TestNamespace(c *C) {
node, err := xmlpath.Parse(bytes.NewBuffer(namespaceXml))
c.Assert(err, IsNil)
for _, test := range namespaceTable {
cmt := Commentf("xml path: %s", test.path)
path, err := xmlpath.CompileWithNamespace(test.path, namespaces)
if want, ok := test.result.(cerror); ok {
c.Assert(err, ErrorMatches, string(want), cmt)
c.Assert(path, IsNil, cmt)
continue
}
c.Assert(err, IsNil)
switch want := test.result.(type) {
case string:
got, ok := path.String(node)
c.Assert(ok, Equals, true, cmt)
c.Assert(got, Equals, want, cmt)
c.Assert(path.Exists(node), Equals, true, cmt)
iter := path.Iter(node)
iter.Next()
node := iter.Node()
c.Assert(node.String(), Equals, want, cmt)
c.Assert(string(node.Bytes()), Equals, want, cmt)
case []string:
var alls []string
var allb []string
iter := path.Iter(node)
for iter.Next() {
alls = append(alls, iter.Node().String())
allb = append(allb, string(iter.Node().Bytes()))
}
c.Assert(alls, DeepEquals, want, cmt)
c.Assert(allb, DeepEquals, want, cmt)
s, sok := path.String(node)
b, bok := path.Bytes(node)
if len(want) == 0 {
c.Assert(sok, Equals, false, cmt)
c.Assert(bok, Equals, false, cmt)
c.Assert(s, Equals, "")
c.Assert(b, IsNil)
} else {
c.Assert(sok, Equals, true, cmt)
c.Assert(bok, Equals, true, cmt)
c.Assert(s, Equals, alls[0], cmt)
c.Assert(string(b), Equals, alls[0], cmt)
c.Assert(path.Exists(node), Equals, true, cmt)
}
case exists:
wantb := bool(want)
ok := path.Exists(node)
c.Assert(ok, Equals, wantb, cmt)
_, ok = path.String(node)
c.Assert(ok, Equals, wantb, cmt)
}
}
}

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>`)

var namespaces = []xmlpath.Namespace {
{ "a", "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
{ "rsp", "http://schemas.microsoft.com/wbem/wsman/1/windows/shell" },
}

var namespaceTable = []struct{ path string; result interface{} }{
{ "//a:To", "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous" },
{ "//rsp:Stream[@Name='stdout']", "VGhhdCdzIGFsbCBmb2xrcyEhIQ==" },
{ "//rsp:CommandState/@CommandId", "1A6DEE6B-EC68-4DD6-87E9-030C0048ECC4" },
{ "//*[@State='http://schemas.microsoft.com/wbem/wsman/1/windows/shell/CommandState/Done']", exists(false) },
{ "//rsp:Stream", []string{ "VGhhdCdzIGFsbCBmb2xrcyEhIQ==", "VGhpcyBpcyBzdGRlcnIsIEknbSBwcmV0dHkgc3VyZSE=" }},
{ "//s:Header", cerror(`.*: unknown namespace prefix: s`) },
}


func (s *BasicSuite) BenchmarkParse(c *C) {
for i := 0; i < c.N; i++ {
_, err := xmlpath.Parse(bytes.NewBuffer(instancesXml))
Expand Down
19 changes: 18 additions & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
// - All node types except for namespace are supported
// - Predicates may be [N], [path], [not(path)], [path=literal] or [contains(path, literal)]
// - Predicates may be joined with "or", "and", and parenthesis
// - Richer expressions and namespaces are not supported
// - Namespaces are experimentally supported
// - Richer expressions are not supported
//
// For example, assuming the following document:
//
Expand Down Expand Up @@ -72,4 +73,20 @@
// fmt.Println("Found:", value)
// }
//
// To use xmlpath with namespaces, it is required to give the supported set of namespace
// when compiling:
//
// var namespaces = []xmlpath.Namespace {
// { "s", "http://www.w3.org/2003/05/soap-envelope" },
// { "a", "http://schemas.xmlsoap.org/ws/2004/08/addressing" },
// }
// path := xmlpath.MustCompileWithNamespace("/s:Header/a:To", namespaces)
// root, err := xmlpath.Parse(file)
// if err != nil {
// log.Fatal(err)
// }
// if value, ok := path.String(root); ok {
// fmt.Println("Found:", value)
// }
//
package xmlpath
118 changes: 81 additions & 37 deletions path.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ import (
"unicode/utf8"
)

// Namespace represents a given XML Namespace
type Namespace struct {
Prefix string
Uri string
}

// Path is a compiled path that can be applied to a context
// node to obtain a matching node set.
// A single Path can be applied concurrently to any number
Expand Down Expand Up @@ -407,17 +413,42 @@ func (andPredicate) predicate() {}
func (orPredicate) predicate() {}

type pathStep struct {
root bool
axis string
name string
kind nodeKind
pred predicate
root bool
axis string
name string
prefix string
uri string
kind nodeKind
pred predicate
}

func (step *pathStep) match(node *Node) bool {
return node.kind != endNode &&
(step.kind == anyNode || step.kind == node.kind) &&
(step.name == "*" || node.name.Local == step.name)
(step.name == "*" || node.name.Local == step.name &&
(node.name.Space != "" && node.name.Space == step.uri || node.name.Space == ""))
}

func compile(path string, ns []Namespace) (*Path, error) {
c := pathCompiler{path, 0, ns}
if path == "" {
return nil, c.errorf("empty path")
}
p, err := c.parsePath()
if err != nil {
return nil, err
}
return p, nil
}

// Compile returns the compiled path.
func Compile(path string) (*Path, error) {
return compile(path, []Namespace{})
}

// Compile the path with the knowledge of the given namespaces
func CompileWithNamespace(path string, ns []Namespace) (*Path, error) {
return compile(path, ns)
}

// MustCompile returns the compiled path, and panics if
Expand All @@ -430,22 +461,21 @@ func MustCompile(path string) *Path {
return e
}

// Compile returns the compiled path.
func Compile(path string) (*Path, error) {
c := pathCompiler{path, 0}
if path == "" {
return nil, c.errorf("empty path")
}
p, err := c.parsePath()
// MustCompileWithNamespace returns the compiled path with
// knowledge of the given namespaces, and panics if
// there are any errors.
func MustCompileWithNamespace(path string, ns []Namespace) *Path {
e, err := CompileWithNamespace(path, ns)
if err != nil {
return nil, err
panic(err)
}
return p, nil
return e
}

type pathCompiler struct {
path string
i int
ns []Namespace
}

func (c *pathCompiler) errorf(format string, args ...interface{}) error {
Expand Down Expand Up @@ -496,29 +526,43 @@ func (c *pathCompiler) parsePath() (path *Path, err error) {
} else {
if c.skipByte(':') {
if !c.skipByte(':') {
return nil, c.errorf("missing ':'")
}
c.skipSpaces()
switch step.name {
case "attribute":
step.kind = attrNode
case "self", "child", "parent":
case "descendant", "descendant-or-self":
case "ancestor", "ancestor-or-self":
case "following", "following-sibling":
case "preceding", "preceding-sibling":
default:
return nil, c.errorf("unsupported axis: %q", step.name)
}
step.axis = step.name

mark = c.i
if !c.skipName() {
return nil, c.errorf("missing name")
mark = c.i
if c.skipName() {
step.prefix = step.name
step.name = c.path[mark:c.i]
found := false
for _,ns := range c.ns {
if ns.Prefix == step.prefix {
step.uri = ns.Uri
found = true
break
}
}
if !found {
return nil,c.errorf("unknown namespace prefix: %s",step.prefix)
}
}
} else {
c.skipSpaces()
switch step.name {
case "attribute":
step.kind = attrNode
case "self", "child", "parent":
case "descendant", "descendant-or-self":
case "ancestor", "ancestor-or-self":
case "following", "following-sibling":
case "preceding", "preceding-sibling":
default:
return nil, c.errorf("unsupported axis: %q", step.name)
}
step.axis = step.name
mark = c.i
if !c.skipName() {
return nil, c.errorf("missing name")
}
step.name = c.path[mark:c.i]
c.skipSpaces()
}
step.name = c.path[mark:c.i]

c.skipSpaces()
}
if c.skipByte('(') {
c.skipSpaces()
Expand Down