package xpath import ( "bytes" "strings" "testing" ) var html *TNode = example() func TestCompile(t *testing.T) { var err error _, err = Compile("//a") if err != nil { t.Fatalf("//a should be correct but got error %s", err) } _, err = Compile("//a[id=']/span") if err == nil { t.Fatal("//a[id=] should be got correct but is nil") } _, err = Compile("//ul/li/@class") if err != nil { t.Fatalf("//ul/li/@class should be correct but got error %s", err) } } func TestSelf(t *testing.T) { testXPath(t, html, ".", "html") testXPath(t, html.FirstChild, ".", "head") testXPath(t, html, "self::*", "html") testXPath(t, html.LastChild, "self::body", "body") testXPath2(t, html, "//body/./ul/li/a", 3) } func TestParent(t *testing.T) { testXPath(t, html.LastChild, "..", "html") testXPath(t, html.LastChild, "parent::*", "html") a := selectNode(html, "//li/a") testXPath(t, a, "parent::*", "li") testXPath(t, html, "//title/parent::head", "head") } func TestAttribute(t *testing.T) { testXPath(t, html, "@lang='en'", "html") testXPath2(t, html, "@lang='zh'", 0) testXPath2(t, html, "//@href", 3) testXPath2(t, html, "//a[@*]", 3) } func TestRelativePath(t *testing.T) { testXPath(t, html, "head", "head") testXPath(t, html, "/head", "head") testXPath(t, html, "body//li", "li") testXPath(t, html, "/head/title", "title") testXPath2(t, html, "/body/ul/li/a", 3) testXPath(t, html, "//title", "title") testXPath(t, html, "//title/..", "head") testXPath(t, html, "//title/../..", "html") testXPath2(t, html, "//a[@href]", 3) testXPath(t, html, "//ul/../footer", "footer") } func TestChild(t *testing.T) { testXPath(t, html, "/child::head", "head") testXPath(t, html, "/child::head/child::title", "title") testXPath(t, html, "//title/../child::title", "title") testXPath(t, html.Parent, "//child::*", "html") } func TestDescendant(t *testing.T) { testXPath2(t, html, "descendant::*", 15) testXPath2(t, html, "/head/descendant::*", 2) testXPath2(t, html, "//ul/descendant::*", 7) //
  • + testXPath2(t, html, "//ul/descendant::li", 4) //
  • } func TestAncestor(t *testing.T) { testXPath2(t, html, "/body/footer/ancestor::*", 2) // body>html testXPath2(t, html, "/body/ul/li/a/ancestor::li", 3) testXPath2(t, html, "/body/ul/li/a/ancestor-or-self::li", 3) } func TestFollowingSibling(t *testing.T) { var list []*TNode list = selectNodes(html, "//li/following-sibling::*") for _, n := range list { if n.Data != "li" { t.Fatalf("expected node is li,but got:%s", n.Data) } } list = selectNodes(html, "//ul/following-sibling::*") // p,footer for _, n := range list { if n.Data != "p" && n.Data != "footer" { t.Fatal("expected node is not one of the following nodes: [p,footer]") } } testXPath(t, html, "//ul/following-sibling::footer", "footer") list = selectNodes(html, "//h1/following::*") // ul>li>a,p,footer if list[0].Data != "ul" { t.Fatal("expected node is not ul") } if list[1].Data != "li" { t.Fatal("expected node is not li") } if list[len(list)-1].Data != "footer" { t.Fatal("expected node is not footer") } } func TestPrecedingSibling(t *testing.T) { testXPath(t, html, "/body/footer/preceding-sibling::*", "p") testXPath2(t, html, "/body/footer/preceding-sibling::*", 3) // p,ul,h1 list := selectNodes(html, "//h1/preceding::*") // head>title>meta if list[0].Data != "head" { t.Fatal("expected is not head") } if list[1].Data != "title" { t.Fatal("expected is not title") } if list[2].Data != "meta" { t.Fatal("expected is not meta") } } func TestStarWide(t *testing.T) { testXPath(t, html, "/head/*", "title") testXPath2(t, html, "//ul/*", 4) testXPath(t, html, "@*", "html") testXPath2(t, html, "/body/h1/*", 0) testXPath2(t, html, `//ul/*/a`, 3) } func TestNodeTestType(t *testing.T) { testXPath(t, html, "//title/text()", "Hello") testXPath(t, html, "//a[@href='/']/text()", "Home") testXPath2(t, html, "//head/node()", 2) testXPath2(t, html, "//ul/node()", 4) } func TestPosition(t *testing.T) { testXPath3(t, html, "/head[1]", html.FirstChild) // compare to 'head' element ul := selectNode(html, "//ul") testXPath3(t, html, "/head[last()]", html.FirstChild) testXPath3(t, html, "//li[1]", ul.FirstChild) testXPath3(t, html, "//li[4]", ul.LastChild) testXPath3(t, html, "//li[last()]", ul.LastChild) } func TestPredicate(t *testing.T) { testXPath(t, html.Parent, "html[@lang='en']", "html") testXPath(t, html, "//a[@href='/']", "a") testXPath(t, html, "//meta[@name]", "meta") ul := selectNode(html, "//ul") testXPath3(t, html, "//li[position()=4]", ul.LastChild) testXPath3(t, html, "//li[position()=1]", ul.FirstChild) testXPath2(t, html, "//li[position()>0]", 4) testXPath3(t, html, "//a[text()='Home']", selectNode(html, "//a[1]")) } func TestOr_And(t *testing.T) { list := selectNodes(html, "//h1|//footer") if len(list) == 0 { t.Fatal("//h1|//footer no any node found") } if list[0].Data != "h1" { t.Fatalf("expected first node of node-set is h1,but got %s", list[0].Data) } if list[1].Data != "footer" { t.Fatalf("expected first node of node-set is footer,but got %s", list[1].Data) } list = selectNodes(html, "//a[@id=1 or @id=2]") if list[0] != selectNode(html, "//a[@id=1]") { t.Fatal("node is not equal") } if list[1] != selectNode(html, "//a[@id=2]") { t.Fatal("node is not equal") } list = selectNodes(html, "//a[@id or @href]") if list[0] != selectNode(html, "//a[@id=1]") { t.Fatal("node is not equal") } if list[1] != selectNode(html, "//a[@id=2]") { t.Fatal("node is not equal") } testXPath3(t, html, "//a[@id=1 and @href='/']", selectNode(html, "//a[1]")) testXPath3(t, html, "//a[text()='Home' and @id='1']", selectNode(html, "//a[1]")) } func TestFunction(t *testing.T) { testEval(t, html, "boolean(//*[@id])", true) testEval(t, html, "boolean(//*[@x])", false) testEval(t, html, "name(//title)", "title") testXPath2(t, html, "//*[name()='a']", 3) testXPath(t, html, "//*[starts-with(name(),'h1')]", "h1") testXPath(t, html, "//*[ends-with(name(),'itle')]", "title") // Head title testXPath2(t, html, "//*[contains(@href,'a')]", 2) testXPath2(t, html, "//*[starts-with(@href,'/a')]", 2) // a links: `/account`,`/about` testXPath2(t, html, "//*[ends-with(@href,'t')]", 2) // a links: `/account`,`/about` testXPath3(t, html, "//h1[normalize-space(text())='This is a H1']", selectNode(html, "//h1")) testXPath3(t, html, "//title[substring(.,1)='Hello']", selectNode(html, "//title")) testXPath3(t, html, "//title[substring(text(),1,4)='Hell']", selectNode(html, "//title")) testXPath3(t, html, "//title[substring(self::*,1,4)='Hell']", selectNode(html, "//title")) testXPath2(t, html, "//title[substring(child::*,1)]", 0) // Here substring return boolen (false), should it? testXPath2(t, html, "//title[substring(child::*,1) = '']", 1) testXPath3(t, html, "//li[not(a)]", selectNode(html, "//ul/li[4]")) testXPath2(t, html, "//li/a[not(@id='1')]", 2) // //li/a[@id!=1] testXPath2(t, html, "//h1[string-length(normalize-space(' abc ')) = 3]", 1) testXPath2(t, html, "//h1[string-length(normalize-space(self::text())) = 12]", 1) testXPath2(t, html, "//title[string-length(normalize-space(child::*)) = 0]", 1) testXPath2(t, html, "//title[string-length(self::text()) = 5]", 1) // Hello = 5 testXPath2(t, html, "//title[string-length(child::*) = 5]", 0) testXPath2(t, html, "//ul[count(li)=4]", 1) testEval(t, html, "true()", true) testEval(t, html, "false()", false) testEval(t, html, "boolean(0)", false) testEval(t, html, "boolean(1)", true) testEval(t, html, "sum(1+2)", float64(3)) testEval(t, html, "string(sum(1+2))", "3") testEval(t, html, "sum(1.1+2)", float64(3.1)) testEval(t, html, "sum(//a/@id)", float64(6)) // 1+2+3 testEval(t, html, `concat("1","2","3")`, "123") testEval(t, html, `concat(" ",//a[@id='1']/@href," ")`, " / ") testEval(t, html, "ceiling(5.2)", float64(6)) testEval(t, html, "floor(5.2)", float64(5)) testEval(t, html, `substring-before('aa-bb','-')`, "aa") testEval(t, html, `substring-before('aa-bb','a')`, "") testEval(t, html, `substring-before('aa-bb','b')`, "aa-") testEval(t, html, `substring-before('aa-bb','q')`, "") testEval(t, html, `substring-after('aa-bb','-')`, "bb") testEval(t, html, `substring-after('aa-bb','a')`, "a-bb") testEval(t, html, `substring-after('aa-bb','b')`, "b") testEval(t, html, `substring-after('aa-bb','q')`, "") testEval(t, html, `translate('The quick brown fox.', 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')`, "THE QUICK BROWN FOX.", ) testEval(t, html, `translate('The quick brown fox.', 'brown', 'red')`, "The quick red fdx.", ) } func TestPanic(t *testing.T) { // starts-with assertPanic(t, func() { testXPath(t, html, "//*[starts-with(0, 0)]", "") }) assertPanic(t, func() { testXPath(t, html, "//*[starts-with(name(), 0)]", "") }) //ends-with assertPanic(t, func() { testXPath(t, html, "//*[ends-with(0, 0)]", "") }) assertPanic(t, func() { testXPath(t, html, "//*[ends-with(name(), 0)]", "") }) // contains assertPanic(t, func() { testXPath2(t, html, "//*[contains(0, 0)]", 0) }) assertPanic(t, func() { testXPath2(t, html, "//*[contains(@href, 0)]", 0) }) // sum assertPanic(t, func() { testXPath3(t, html, "//title[sum('Hello') = 0]", nil) }) // substring assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,'')=0]", nil) }) assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,4,'')=0]", nil) }) assertPanic(t, func() { testXPath3(t, html, "//title[substring(.,4,4)=0]", nil) }) //assertPanic(t, func() { testXPath2(t, html, "//title[substring(child::*,0) = '']", 0) }) // Here substring return boolen (false), should it? } func assertPanic(t *testing.T, f func()) { defer func() { if r := recover(); r == nil { t.Errorf("The code did not panic") } }() f() } func TestEvaluate(t *testing.T) { testEval(t, html, "count(//ul/li)", float64(4)) testEval(t, html, "//html/@lang", []string{"en"}) testEval(t, html, "//title/text()", []string{"Hello"}) } func TestOperationOrLogical(t *testing.T) { testXPath3(t, html, "//li[1+1]", selectNode(html, "//li[2]")) testXPath3(t, html, "//li[5 div 2]", selectNode(html, "//li[2]")) testXPath3(t, html, "//li[3 mod 2]", selectNode(html, "//li[1]")) testXPath3(t, html, "//li[3 - 2]", selectNode(html, "//li[1]")) testXPath2(t, html, "//li[position() mod 2 = 0 ]", 2) // //li[2],li[4] testXPath2(t, html, "//a[@id>=1]", 3) // //a[@id>=1] == a[1],a[2],a[3] testXPath2(t, html, "//a[@id<=2]", 2) // //a[@id<=2] == a[1],a[1] testXPath2(t, html, "//a[@id<2]", 1) // //a[@id>=1] == a[1] testXPath2(t, html, "//a[@id!=2]", 2) // //a[@id>=1] == a[1],a[3] testXPath2(t, html, "//a[@id=1 or @id=3]", 2) // //a[@id>=1] == a[1],a[3] testXPath3(t, html, "//a[@id=1 and @href='/']", selectNode(html, "//a[1]")) } func testEval(t *testing.T, root *TNode, expr string, expected interface{}) { v := MustCompile(expr).Evaluate(createNavigator(root)) if it, ok := v.(*NodeIterator); ok { exp, ok := expected.([]string) if !ok { t.Fatalf("expected value, got: %#v", v) } got := iterateNavs(it) if len(exp) != len(got) { t.Fatalf("expected: %#v, got: %#v", exp, got) } for i, n1 := range exp { n2 := got[i] if n1 != n2.Value() { t.Fatalf("expected: %#v, got: %#v", n1, n2) } } return } if v != expected { t.Fatalf("expected: %#v, got: %#v", expected, v) } } func testXPath(t *testing.T, root *TNode, expr string, expected string) { node := selectNode(root, expr) if node == nil { t.Fatalf("`%s` returns node is nil", expr) } if node.Data != expected { t.Fatalf("`%s` expected node is %s,but got %s", expr, expected, node.Data) } } func testXPath2(t *testing.T, root *TNode, expr string, expected int) { list := selectNodes(root, expr) if len(list) != expected { t.Fatalf("`%s` expected node numbers is %d,but got %d", expr, expected, len(list)) } } func testXPath3(t *testing.T, root *TNode, expr string, expected *TNode) { node := selectNode(root, expr) if node == nil { t.Fatalf("`%s` returns node is nil", expr) } if node != expected { t.Fatalf("`%s` %s != %s", expr, node.Value(), expected.Value()) } } func iterateNavs(t *NodeIterator) []*TNodeNavigator { var nodes []*TNodeNavigator for t.MoveNext() { node := t.Current().(*TNodeNavigator) nodes = append(nodes, node) } return nodes } func iterateNodes(t *NodeIterator) []*TNode { var nodes []*TNode for t.MoveNext() { node := (t.Current().(*TNodeNavigator)).curr nodes = append(nodes, node) } return nodes } func selectNode(root *TNode, expr string) (n *TNode) { t := Select(createNavigator(root), expr) if t.MoveNext() { n = (t.Current().(*TNodeNavigator)).curr } return n } func selectNodes(root *TNode, expr string) []*TNode { t := Select(createNavigator(root), expr) return iterateNodes(t) } func createNavigator(n *TNode) *TNodeNavigator { return &TNodeNavigator{curr: n, root: n, attr: -1} } type Attribute struct { Key, Value string } type TNode struct { Parent, FirstChild, LastChild, PrevSibling, NextSibling *TNode Type NodeType Data string Attr []Attribute } func (n *TNode) Value() string { if n.Type == TextNode { return n.Data } var buff bytes.Buffer var output func(*TNode) output = func(node *TNode) { if node.Type == TextNode { buff.WriteString(node.Data) } for child := node.FirstChild; child != nil; child = child.NextSibling { output(child) } } output(n) return buff.String() } // TNodeNavigator is for navigating TNode. type TNodeNavigator struct { curr, root *TNode attr int } func (n *TNodeNavigator) NodeType() NodeType { if n.curr.Type == ElementNode && n.attr != -1 { return AttributeNode } return n.curr.Type } func (n *TNodeNavigator) LocalName() string { if n.attr != -1 { return n.curr.Attr[n.attr].Key } return n.curr.Data } func (n *TNodeNavigator) Prefix() string { return "" } func (n *TNodeNavigator) Value() string { switch n.curr.Type { case CommentNode: return n.curr.Data case ElementNode: if n.attr != -1 { return n.curr.Attr[n.attr].Value } var buf bytes.Buffer node := n.curr.FirstChild for node != nil { if node.Type == TextNode { buf.WriteString(strings.TrimSpace(node.Data)) } node = node.NextSibling } return buf.String() case TextNode: return n.curr.Data } return "" } func (n *TNodeNavigator) Copy() NodeNavigator { n2 := *n return &n2 } func (n *TNodeNavigator) MoveToRoot() { n.curr = n.root } func (n *TNodeNavigator) MoveToParent() bool { if node := n.curr.Parent; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveToNextAttribute() bool { if n.attr >= len(n.curr.Attr)-1 { return false } n.attr++ return true } func (n *TNodeNavigator) MoveToChild() bool { if node := n.curr.FirstChild; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveToFirst() bool { if n.curr.PrevSibling == nil { return false } for { node := n.curr.PrevSibling if node == nil { break } n.curr = node } return true } func (n *TNodeNavigator) String() string { return n.Value() } func (n *TNodeNavigator) MoveToNext() bool { if node := n.curr.NextSibling; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveToPrevious() bool { if node := n.curr.PrevSibling; node != nil { n.curr = node return true } return false } func (n *TNodeNavigator) MoveTo(other NodeNavigator) bool { node, ok := other.(*TNodeNavigator) if !ok || node.root != n.root { return false } n.curr = node.curr n.attr = node.attr return true } func createNode(data string, typ NodeType) *TNode { return &TNode{Data: data, Type: typ, Attr: make([]Attribute, 0)} } func (n *TNode) createChildNode(data string, typ NodeType) *TNode { m := createNode(data, typ) m.Parent = n if n.FirstChild == nil { n.FirstChild = m } else { n.LastChild.NextSibling = m m.PrevSibling = n.LastChild } n.LastChild = m return m } func (n *TNode) appendNode(data string, typ NodeType) *TNode { m := createNode(data, typ) m.Parent = n.Parent n.NextSibling = m m.PrevSibling = n if n.Parent != nil { n.Parent.LastChild = m } return m } func (n *TNode) addAttribute(k, v string) { n.Attr = append(n.Attr, Attribute{k, v}) } func example() *TNode { /* Hello

    This is a H1

    Hello,This is an example for gxpath.

    */ doc := createNode("", RootNode) xhtml := doc.createChildNode("html", ElementNode) xhtml.addAttribute("lang", "en") // The HTML head section. head := xhtml.createChildNode("head", ElementNode) n := head.createChildNode("title", ElementNode) n = n.createChildNode("Hello", TextNode) n = head.createChildNode("meta", ElementNode) n.addAttribute("name", "language") n.addAttribute("content", "en") // The HTML body section. body := xhtml.createChildNode("body", ElementNode) n = body.createChildNode("h1", ElementNode) n = n.createChildNode(" This is a H1 ", TextNode) ul := body.createChildNode("ul", ElementNode) n = ul.createChildNode("li", ElementNode) n = n.createChildNode("a", ElementNode) n.addAttribute("id", "1") n.addAttribute("href", "/") n = n.createChildNode("Home", TextNode) n = ul.createChildNode("li", ElementNode) n = n.createChildNode("a", ElementNode) n.addAttribute("id", "2") n.addAttribute("href", "/about") n = n.createChildNode("about", TextNode) n = ul.createChildNode("li", ElementNode) n = n.createChildNode("a", ElementNode) n.addAttribute("id", "3") n.addAttribute("href", "/account") n = n.createChildNode("login", TextNode) n = ul.createChildNode("li", ElementNode) n = body.createChildNode("p", ElementNode) n = n.createChildNode("Hello,This is an example for gxpath.", TextNode) n = body.createChildNode("footer", ElementNode) n = n.createChildNode("footer script", TextNode) return xhtml }