package xmlpath_test
import (
"bytes"
"encoding/xml"
. "launchpad.net/gocheck"
"launchpad.net/xmlpath"
"testing"
)
func Test(t *testing.T) {
TestingT(t)
}
var _ = Suite(&BasicSuite{})
type BasicSuite struct{}
var trivialXml = []byte(`abcdefg`)
func (s *BasicSuite) TestRootText(c *C) {
node, err := xmlpath.Parse(bytes.NewBuffer(trivialXml))
c.Assert(err, IsNil)
path := xmlpath.MustCompile("/")
result, ok := path.String(node)
c.Assert(ok, Equals, true)
c.Assert(result, Equals, "abcdefg")
}
var trivialHtml = []byte(`<a>`)
func (s *BasicSuite) TestHTML(c *C) {
node, err := xmlpath.ParseHTML(bytes.NewBuffer(trivialHtml))
c.Assert(err, IsNil)
path := xmlpath.MustCompile("/root/foo")
result, ok := path.String(node)
c.Assert(ok, Equals, true)
c.Assert(result, Equals, "")
}
func (s *BasicSuite) TestLibraryTable(c *C) {
node, err := xmlpath.Parse(bytes.NewBuffer(libraryXml))
c.Assert(err, IsNil)
for _, test := range libraryTable {
cmt := Commentf("xml path: %s", test.path)
path, err := xmlpath.Compile(test.path)
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)
}
}
}
type cerror string
type exists bool
var libraryTable = []struct{ path string; result interface{} }{
// These are the examples in the package documentation:
{"/library/book/isbn", "0836217462"},
{"library/*/isbn", "0836217462"},
{"/library/book/../book/./isbn", "0836217462"},
{"/library/book/character[2]/name", "Snoopy"},
{"/library/book/character[born='1950-10-04']/name", "Snoopy"},
{"/library/book//node()[@id='PP']/name", "Peppermint Patty"},
{"//book[author/@id='CMS']/title", "Being a Dog Is a Full-Time Job"},
{"/library/book/preceding::comment()", " Great book. "},
// A few simple
{"/library/book/isbn", exists(true)},
{"/library/isbn", exists(false)},
{"/library/book/isbn/bad", exists(false)},
{"/library/book/bad", exists(false)},
{"/library/bad/isbn", exists(false)},
{"/bad/book/isbn", exists(false)},
// Simple paths.
{"/library/book/isbn", "0836217462"},
{"/library/book/author/name", "Charles M Schulz"},
{"/library/book/author/born", "1922-11-26"},
{"/library/book/character/name", "Peppermint Patty"},
{"/library/book/character/qualification", "bold, brash and tomboyish"},
// Unrooted path with root node as context.
{"library/book/isbn", "0836217462"},
// Multiple entries from simple paths.
{"/library/book/isbn", []string{"0836217462", "0883556316"}},
{"/library/book/character/name", []string{"Peppermint Patty", "Snoopy", "Schroeder", "Lucy", "Barney Google", "Spark Plug", "Snuffy Smith"}},
// Handling of wildcards.
{"/library/book/author/*", []string{"Charles M Schulz", "1922-11-26", "2000-02-12", "Charles M Schulz", "1922-11-26", "2000-02-12"}},
// Unsupported axis and note test.
{"/foo()", cerror(`compiling xml path "/foo\(\)":5: unsupported expression: foo\(\)`)},
{"/foo::node()", cerror(`compiling xml path "/foo::node\(\)":6: unsupported axis: "foo"`)},
// The attribute axis.
{"/library/book/title/attribute::lang", "en"},
{"/library/book/title/@lang", "en"},
{"/library/book/@available/parent::node()/@id", "b0836217462"},
{"/library/book/attribute::*", []string{"b0836217462", "true", "b0883556316", "true"}},
{"/library/book/attribute::text()", cerror(`.*: text\(\) cannot succeed on axis "attribute"`)},
// The self axis.
{"/library/book/isbn/./self::node()", "0836217462"},
// The descendant axis.
{"/library/book/isbn/descendant::isbn", exists(false)},
{"/library/descendant::isbn", []string{"0836217462", "0883556316"}},
{"/descendant::*/isbn", []string{"0836217462", "0883556316"}},
{"/descendant::isbn", []string{"0836217462", "0883556316"}},
// The descendant-or-self axis.
{"/library/book/isbn/descendant-or-self::isbn", "0836217462"},
{"/library//isbn", []string{"0836217462", "0883556316"}},
{"//isbn", []string{"0836217462", "0883556316"}},
{"/descendant-or-self::node()/child::book/child::*", "0836217462"},
// The parent axis.
{"/library/book/isbn/../isbn/parent::node()//title", "Being a Dog Is a Full-Time Job"},
// The ancestor axis.
{"/library/book/isbn/ancestor::book/title", "Being a Dog Is a Full-Time Job"},
{"/library/book/ancestor::book/title", exists(false)},
// The ancestor-or-self axis.
{"/library/book/isbn/ancestor-or-self::book/title", "Being a Dog Is a Full-Time Job"},
{"/library/book/ancestor-or-self::book/title", "Being a Dog Is a Full-Time Job"},
// The following axis.
// The first author name must not be included, as it's within the context
// node (author) rather than following it. These queries exercise de-duping
// of nodes, since the following axis runs to the end multiple times.
{"/library/book/author/following::name", []string{"Peppermint Patty", "Snoopy", "Schroeder", "Lucy", "Charles M Schulz", "Barney Google", "Spark Plug", "Snuffy Smith"}},
{"//following::book/author/name", []string{"Charles M Schulz", "Charles M Schulz"}},
// The following-sibling axis.
{"/library/book/quote/following-sibling::node()/name", []string{"Charles M Schulz", "Peppermint Patty", "Snoopy", "Schroeder", "Lucy"}},
// The preceding axis.
{"/library/book/author/born/preceding::name", []string{"Charles M Schulz", "Charles M Schulz", "Lucy", "Schroeder", "Snoopy", "Peppermint Patty"}},
{"/library/book/author/born/preceding::author/name", []string{"Charles M Schulz"}},
{"/library/book/author/born/preceding::library", exists(false)},
// The preceding-sibling axis.
{"/library/book/author/born/preceding-sibling::name", []string{"Charles M Schulz", "Charles M Schulz"}},
{"/library/book/author/born/preceding::author/name", []string{"Charles M Schulz"}},
// Comments.
{"/library/comment()", []string{" Great book. ", " Another great book. "}},
{"//self::comment()", []string{" Great book. ", " Another great book. "}},
{`comment("")`, cerror(`.*: comment\(\) has no arguments`)},
// Processing instructions.
{`/library/book/author/processing-instruction()`, `"go rocks"`},
{`/library/book/author/processing-instruction("echo")`, `"go rocks"`},
{`/library//processing-instruction("echo")`, `"go rocks"`},
{`/library/book/author/processing-instruction("foo")`, exists(false)},
{`/library/book/author/processing-instruction(")`, cerror(`.*: missing '"'`)},
// Predicates.
{"library/book[@id='b0883556316']/isbn", []string{"0883556316"}},
{"library/book[isbn='0836217462']/character[born='1950-10-04']/name", []string{"Snoopy"}},
{"library/book[quote]/@id", []string{"b0836217462"}},
{"library/book[./character/born='1922-07-17']/@id", []string{"b0883556316"}},
{"library/book[2]/isbn", []string{"0883556316"}},
{"library/book[0]/isbn", cerror(".*: positions start at 1")},
{"library/book[-1]/isbn", cerror(".*: positions must be positive")},
// Bogus expressions.
{"/foo)", cerror(`compiling xml path "/foo\)":4: unexpected '\)'`)},
}
var libraryXml = []byte(
`
0836217462
Being a Dog Is a Full-Time Job
I'd dog paddle the deepest ocean.
Charles M Schulz
1922-11-26
2000-02-12
Peppermint Patty
1966-08-22
bold, brash and tomboyish
Snoopy
1950-10-04
extroverted beagle
Schroeder
1951-05-30
brought classical music to the Peanuts strip
Lucy
1952-03-03
bossy, crabby and selfish
0883556316
Barney Google and Snuffy Smith
Charles M Schulz
1922-11-26
2000-02-12
Barney Google
1919-01-01
goggle-eyed, moustached, gloved and top-hatted, bulbous-nosed, cigar-chomping shrimp
Spark Plug
1922-07-17
brown-eyed, bow-legged nag, seldom races, patched blanket
Snuffy Smith
1934-01-01
volatile and diminutive moonshiner, ornery little cuss, sawed-off and shiftless
`)
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(`http://schemas.microsoft.com/wbem/wsman/1/windows/shell/ReceiveResponseuuid:AAD46BD4-6315-4C3C-93D4-94A55773287Dhttp://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymousuuid:18A52A06-9027-41DC-8850-3F244595AF62VGhhdCdzIGFsbCBmb2xrcyEhIQ==VGhpcyBpcyBzdGRlcnIsIEknbSBwcmV0dHkgc3VyZSE=`)
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))
c.Assert(err, IsNil)
}
}
func (s *BasicSuite) BenchmarkSimplePathCompile(c *C) {
var err error
c.ResetTimer()
for i := 0; i < c.N; i++ {
_, err = xmlpath.Compile("/DescribeInstancesResponse/reservationSet/item/groupSet/item/groupId")
}
c.StopTimer()
c.Assert(err, IsNil)
}
func (s *BasicSuite) BenchmarkSimplePathString(c *C) {
node, err := xmlpath.Parse(bytes.NewBuffer(instancesXml))
c.Assert(err, IsNil)
path := xmlpath.MustCompile("/DescribeInstancesResponse/reservationSet/item/instancesSet/item/instanceType")
var str string
c.ResetTimer()
for i := 0; i < c.N; i++ {
str, _ = path.String(node)
}
c.StopTimer()
c.Assert(str, Equals, "m1.small")
}
func (s *BasicSuite) BenchmarkSimplePathStringUnmarshal(c *C) {
// For a vague comparison.
var result struct{ Str string `xml:"reservationSet>item>instancesSet>item>instanceType"` }
for i := 0; i < c.N; i++ {
xml.Unmarshal(instancesXml, &result)
}
c.StopTimer()
c.Assert(result.Str, Equals, "m1.large")
}
func (s *BasicSuite) BenchmarkSimplePathExists(c *C) {
node, err := xmlpath.Parse(bytes.NewBuffer(instancesXml))
c.Assert(err, IsNil)
path := xmlpath.MustCompile("/DescribeInstancesResponse/reservationSet/item/instancesSet/item/instanceType")
var exists bool
c.ResetTimer()
for i := 0; i < c.N; i++ {
exists = path.Exists(node)
}
c.StopTimer()
c.Assert(exists, Equals, true)
}
var instancesXml = []byte(
`
98e3c9a4-848c-4d6d-8e8a-b1bdEXAMPLE
-
r-b27e30d9
999988887777
-
sg-67ad940e
default
-
i-c5cd56af
ami-1a2b3c4d
16
running
domU-12-31-39-10-56-34.compute-1.internal
ec2-174-129-165-232.compute-1.amazonaws.com
GSG_Keypair
0
m1.small
2010-08-17T01:15:18.000Z
us-east-1b
aki-94c527fd
ari-96c527ff
disabled
10.198.85.190
174.129.165.232
i386
ebs
/dev/sda1
-
/dev/sda1
vol-a082c1c9
attached
2010-08-17T01:15:21.000Z
false
spot
sir-7a688402
paravirtual
xen
854251627541
-
r-b67e30dd
999988887777
-
sg-67ad940e
default
-
i-d9cd56b3
ami-1a2b3c4d
16
running
domU-12-31-39-10-54-E5.compute-1.internal
ec2-184-73-58-78.compute-1.amazonaws.com
GSG_Keypair
0
m1.large
2010-08-17T01:15:19.000Z
us-east-1b
aki-94c527fd
ari-96c527ff
disabled
10.198.87.19
184.73.58.78
i386
ebs
/dev/sda1
-
/dev/sda1
vol-a282c1cb
attached
2010-08-17T01:15:23.000Z
false
spot
sir-55a3aa02
paravirtual
xen
854251627541
`)