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