package xmlpath_test import ( "bytes" "encoding/xml" "testing" . "gopkg.in/check.v1" "gopkg.in/xmlpath.v2" "strings" ) 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 htmlTable = []struct { html string path string result interface{} }{ {"
  • a
  • b", "/html/body/li", "a"}, {"
  • a
  • b", "/html/body/li", "a"}, {"<a>", "/html/body", ""}, {"", "/html/head/script", "if(1<2||2>1){}"}, {"\ntext", "/html/body", "text"}, } func (s *BasicSuite) TestHTML(c *C) { for _, test := range htmlTable { c.Logf("Running test: %v", test) node, err := xmlpath.ParseHTML(bytes.NewBuffer([]byte(test.html))) c.Assert(err, IsNil) path, err := xmlpath.Compile(test.path) c.Assert(err, IsNil) result, ok := path.String(node) c.Assert(ok, Equals, true) c.Assert(result, Equals, test.result) } } 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 { if !strings.Contains(err.Error(), string(want)) { c.Fatalf("error should contain `%s` but got `%s`", want, err.Error()) } 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. "}, {"//*[contains(born,'1922')]/name", "Charles M Schulz"}, {"//*[@id='PP' or @id='Snoopy']/born", []string{"1966-08-22", "1950-10-04"}}, // 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()`)}, {"/node(", cerror(`compiling xml path "/node(":6: node() missing ')'`)}, {"/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[ @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")}, {"//title[contains(.,'ney Google and')]", "Barney Google and Snuffy Smith"}, {"//@id[contains(.,'0836')]", "b0836217462"}, {"//*[contains(born,'1922')]/name", "Charles M Schulz"}, {"library/book[not(@id)]", exists(false)}, {"library/book[not(@foo) and @id='b0883556316']/isbn", []string{"0883556316"}}, // Multiple predicates. {"library/book/character[@id='Snoopy' and ./born='1950-10-04']/born", []string{"1950-10-04"}}, {"library/book/character[@id='Snoopy' or @id='Lucy']/born", []string{"1950-10-04", "1952-03-03"}}, {"library/book/character[@id='Snoopy' and ./born='1950-10-04' or @id='Lucy' and ./born='1952-03-03']/born", []string{"1950-10-04", "1952-03-03"}}, {"library/book/character[@id='Snoopy' or @id='Lucy' and @id='NOPE']/born", []string{"1950-10-04"}}, {"library/book/character[@id='Snoopy' and @id='NOPE' or @id='Lucy']/born", []string{"1952-03-03"}}, {"library/book/character[(@id='Snoopy' or @id='Lucy') and (./born='1950-10-04' or ./born='1952-03-03')]/born", []string{"1950-10-04", "1952-03-03"}}, // Bogus expressions. {"/foo)", cerror(`compiling xml path "/foo)":4: unexpected ')'`)}, {"/foo[", cerror(`compiling xml path "/foo[":5: missing name`)}, {"/foo[@id)]", cerror(`compiling xml path "/foo[@id)]":9: unexpected ')'`)}, {"/foo[(@id]", cerror(`compiling xml path "/foo[(@id]":9: expected ')'`)}, // Whitespace handling. {" / descendant-or-self :: node() // child :: book / child :: * [ contains( . , '083' ) ] ", "0836217462"}, } 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 <i>Google</i> 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) 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 `)