From ae2b67d1b9dbee3cc003b0793c235809758fc697 Mon Sep 17 00:00:00 2001 From: zhengchun Date: Mon, 25 Mar 2024 20:56:30 +0800 Subject: [PATCH] update new test cases --- xpath_axes_test.go | 68 +++ xpath_expression_test.go | 55 +++ xpath_function_test.go | 212 ++++++++++ xpath_node_test.go | 327 --------------- xpath_predicate_test.go | 177 +++----- xpath_test.go | 873 +++++++++++++-------------------------- 6 files changed, 675 insertions(+), 1037 deletions(-) create mode 100644 xpath_axes_test.go create mode 100644 xpath_expression_test.go create mode 100644 xpath_function_test.go delete mode 100644 xpath_node_test.go diff --git a/xpath_axes_test.go b/xpath_axes_test.go new file mode 100644 index 0000000..b573638 --- /dev/null +++ b/xpath_axes_test.go @@ -0,0 +1,68 @@ +package xpath + +import "testing" + +func Test_self(t *testing.T) { + test_xpath_elements(t, employee_example, `//name/self::*`, 4, 9, 14) +} + +func Test_child(t *testing.T) { + test_xpath_elements(t, employee_example, `/empinfo/child::*`, 3, 8, 13) + test_xpath_elements(t, employee_example, `/empinfo/child::node()`, 3, 8, 13) + test_xpath_values(t, employee_example, `//name/child::text()`, "Opal Kole", "Max Miller", "Beccaa Moss") + test_xpath_elements(t, employee_example, `//child::employee/child::email`, 6, 11, 16) +} + +func Test_descendant(t *testing.T) { + test_xpath_elements(t, employee_example, `//employee/descendant::*`, 4, 5, 6, 9, 10, 11, 14, 15, 16) + test_xpath_count(t, employee_example, `//descendant::employee`, 3) +} + +func Test_descendant_or_self(t *testing.T) { + test_xpath_tags(t, employee_example.FirstChild, `self::*`, "empinfo") + test_xpath_elements(t, employee_example, `//employee/descendant-or-self::*`, 3, 4, 5, 6, 8, 9, 10, 11, 13, 14, 15, 16) + test_xpath_count(t, employee_example, `//descendant-or-self::employee`, 3) +} + +func Test_ancestor(t *testing.T) { + test_xpath_tags(t, employee_example, `//employee/ancestor::*`, "empinfo") // + test_xpath_elements(t, employee_example, `//ancestor::name`, 4, 9, 14) +} + +func Test_ancestor_or_self(t *testing.T) { + test_xpath_elements(t, employee_example, `//employee/ancestor-or-self::*`, 2, 3, 8, 13) + test_xpath_elements(t, employee_example, `//name/ancestor-or-self::employee`, 2, 3, 8, 13) +} + +func Test_parent(t *testing.T) { + test_xpath_elements(t, employee_example, `//name/parent::*`, 3, 8, 13) + test_xpath_elements(t, employee_example, `//name/parent::employee`, 3, 8, 13) +} + +func Test_attribute(t *testing.T) { + test_xpath_values(t, employee_example, `//attribute::id`, "1", "2", "3") + test_xpath_tags(t, employee_example, `//attribute::*`, "id", "discipline", "experience", "id", "from", "discipline", "experience", "id", "discipline") +} + +func Test_following(t *testing.T) { + test_xpath_elements(t, employee_example, `//employee[@id=1]/following::*`, 8, 9, 10, 11, 13, 14, 15, 16) +} + +func Test_following_sibling(t *testing.T) { + test_xpath_elements(t, employee_example, `//employee[@id=1]/following-sibling::*`, 8, 13) + test_xpath_elements(t, employee_example, `//employee[@id=1]/following-sibling::employee`, 8, 13) +} + +func Test_preceding(t *testing.T) { + //testXPath3(t, html, "//li[last()]/preceding-sibling::*[2]", selectNode(html, "//li[position()=2]")) + //testXPath3(t, html, "//li/preceding::*[1]", selectNode(html, "//h1")) + test_xpath_elements(t, employee_example, `//employee[@id=3]/preceding::*`, 8, 9, 10, 11, 3, 4, 5, 6) +} + +func Test_preceding_sibling(t *testing.T) { + test_xpath_elements(t, employee_example, `//employee[@id=3]/preceding-sibling::*`, 8, 3) +} + +func Test_namespace(t *testing.T) { + // TODO +} diff --git a/xpath_expression_test.go b/xpath_expression_test.go new file mode 100644 index 0000000..11b700b --- /dev/null +++ b/xpath_expression_test.go @@ -0,0 +1,55 @@ +package xpath + +import ( + "testing" +) + +// `*/employee` [Not supported] + +func TestRelativePaths(t *testing.T) { + test_xpath_elements(t, book_example, `//book`, 3, 9, 15, 25) + test_xpath_elements(t, book_example, `//bookstore/book`, 3, 9, 15, 25) + test_xpath_tags(t, book_example, `//book/..`, "bookstore") + test_xpath_elements(t, book_example, `//book[@category="cooking"]/..`, 2) + test_xpath_elements(t, book_example, `//book/year[text() = 2005]/../..`, 2) // bookstore + test_xpath_elements(t, book_example, `//book/year/../following-sibling::*`, 9, 15, 25) + test_xpath_count(t, book_example, `//bookstore/book/*`, 20) + test_xpath_tags(t, html_example, "//title/../..", "html") + test_xpath_elements(t, html_example, "//ul/../p", 19) +} + +func TestAbsolutePaths(t *testing.T) { + test_xpath_elements(t, book_example, `bookstore`, 2) + test_xpath_elements(t, book_example, `bookstore/book`, 3, 9, 15, 25) + test_xpath_elements(t, book_example, `(bookstore/book)`, 3, 9, 15, 25) + test_xpath_elements(t, book_example, `bookstore/book[2]`, 9) + test_xpath_elements(t, book_example, `bookstore/book[last()]`, 25) + test_xpath_elements(t, book_example, `bookstore/book[last()]/title`, 26) + test_xpath_values(t, book_example, `/bookstore/book[last()]/title/text()`, "Learning XML") + test_xpath_values(t, book_example, `/bookstore/book[@category = "children"]/year`, "2005") + test_xpath_elements(t, book_example, `bookstore/book/..`, 2) + test_xpath_elements(t, book_example, `/bookstore/*`, 3, 9, 15, 25) + test_xpath_elements(t, book_example, `/bookstore/*/title`, 4, 10, 16, 26) +} + +func TestAttributes(t *testing.T) { + test_xpath_tags(t, html_example.FirstChild, "@*", "lang") + test_xpath_tags(t, employee_example, `//@*`, "id", "discipline", "experience", "id", "from", "discipline", "experience", "id", "discipline") + test_xpath_values(t, employee_example, `//@discipline`, "web", "DBA", "appdev") + test_xpath_count(t, employee_example, `//employee/@id`, 3) +} + +func TestExpressions(t *testing.T) { + test_xpath_elements(t, book_example, `//book[@category = "cooking"] | //book[@category = "children"]`, 3, 9) + test_xpath_count(t, html_example, `//ul/*`, 3) + test_xpath_count(t, html_example, `//ul/*/a`, 3) + // Sequence + // + // table/tbody/tr/td/(para, .[not(para)], ..) +} + +func TestSequence(t *testing.T) { + // `//table/tbody/tr/td/(para, .[not(para)],..)` + test_xpath_count(t, html_example, `//body/(h1, h2, p)`, 2) + test_xpath_count(t, html_example, `//body/(h1, h2, p, ..)`, 3) +} diff --git a/xpath_function_test.go b/xpath_function_test.go new file mode 100644 index 0000000..d9153e9 --- /dev/null +++ b/xpath_function_test.go @@ -0,0 +1,212 @@ +package xpath + +import ( + "math" + "testing" +) + +// Some test examples from http://zvon.org/comp/r/ref-XPath_2.html + +func Test_func_boolean(t *testing.T) { + test_xpath_eval(t, empty_example, `true()`, true) + test_xpath_eval(t, empty_example, `false()`, false) + test_xpath_eval(t, empty_example, `boolean(0)`, false) + test_xpath_eval(t, empty_example, `boolean(1)`, true) + test_xpath_eval(t, empty_example, `boolean(2)`, true) + test_xpath_eval(t, empty_example, `boolean(true)`, false) + test_xpath_eval(t, empty_example, `boolean(1 > 2)`, false) + test_xpath_eval(t, book_example, `boolean(//*[@lang])`, true) + test_xpath_eval(t, book_example, `boolean(//*[@x])`, false) +} + +func Test_func_name(t *testing.T) { + test_xpath_eval(t, html_example, `name(//html/@lang)`, "lang") + test_xpath_eval(t, html_example, `name(html/head/title)`, "title") + test_xpath_count(t, html_example, `//*[name() = "li"]`, 3) +} + +func Test_func_not(t *testing.T) { + //test_xpath_eval(t, empty_example, `not(0)`, true) + //test_xpath_eval(t, empty_example, `not(1)`, false) + test_xpath_elements(t, employee_example, `//employee[not(@id = "1")]`, 8, 13) + test_xpath_elements(t, book_example, `//book[not(year = 2005)]`, 15, 25) + test_xpath_count(t, book_example, `//book[not(title)]`, 0) +} + +func Test_func_ceiling_floor(t *testing.T) { + test_xpath_eval(t, empty_example, "ceiling(5.2)", float64(6)) + test_xpath_eval(t, empty_example, "floor(5.2)", float64(5)) +} + +func Test_func_concat(t *testing.T) { + test_xpath_eval(t, empty_example, `concat("1", "2", "3")`, "123") + //test_xpath_eval(t, empty_example, `concat("Ciao!", ())`, "Ciao!") + test_xpath_eval(t, book_example, `concat(//book[1]/title, ", ", //book[1]/year)`, "Everyday Italian, 2005") +} + +func Test_func_contains(t *testing.T) { + test_xpath_eval(t, empty_example, `contains("tattoo", "t")`, true) + test_xpath_eval(t, empty_example, `contains("tattoo", "T")`, false) + test_xpath_eval(t, empty_example, `contains("tattoo", "ttt")`, false) + //test_xpath_eval(t, empty_example, `contains("", ())`, true) + test_xpath_elements(t, book_example, `//book[contains(title, "Potter")]`, 9) + test_xpath_elements(t, book_example, `//book[contains(year, "2005")]`, 3, 9) + assertPanic(t, func() { selectNode(html_example, "//*[contains(0, 0)]") }) +} + +func Test_func_count(t *testing.T) { + test_xpath_eval(t, book_example, `count(//book)`, float64(4)) + test_xpath_eval(t, book_example, `count(//book[3]/author)`, float64(5)) +} + +func Test_func_ends_with(t *testing.T) { + test_xpath_eval(t, empty_example, `ends-with("tattoo", "tattoo")`, true) + test_xpath_eval(t, empty_example, `ends-with("tattoo", "atto")`, false) + test_xpath_elements(t, book_example, `//book[ends-with(@category,'ing')]`, 3) + test_xpath_elements(t, book_example, `//book[ends-with(./price,'.99')]`, 9, 15) + assertPanic(t, func() { selectNode(html_example, `//*[ends-with(0, 0)]`) }) // arg must be start with string + assertPanic(t, func() { selectNode(html_example, `//*[ends-with(name(), 0)]`) }) +} + +func Test_func_last(t *testing.T) { + test_xpath_elements(t, book_example, `//bookstore[last()]`, 2) + test_xpath_elements(t, book_example, `//bookstore/book[last()]`, 25) + test_xpath_elements(t, html_example, `//ul/li[last()]`, 15) + test_xpath_elements(t, html_example, `(//ul/li)[last()]`, 15) +} + +func Test_func_local_name(t *testing.T) { + // TODO +} + +func Test_func_starts_with(t *testing.T) { + test_xpath_eval(t, employee_example, `starts-with("tattoo", "tat")`, true) + test_xpath_eval(t, employee_example, `starts-with("tattoo", "att")`, false) + test_xpath_elements(t, book_example, `//book[starts-with(title,'Everyday')]`, 3) + assertPanic(t, func() { selectNode(html_example, `//*[starts-with(0, 0)]`) }) + assertPanic(t, func() { selectNode(html_example, `//*[starts-with(name(), 0)]`) }) +} + +func Test_func_string(t *testing.T) { + test_xpath_eval(t, empty_example, `string(1.23)`, "1.23") + test_xpath_eval(t, empty_example, `string(3)`, "3") +} + +func Test_func_string_join(t *testing.T) { + //test_xpath_eval(t, empty_example, `string-join(('Now', 'is', 'the', 'time', '...'), '')`, "Now is the time ...") + test_xpath_eval(t, empty_example, `string-join("some text", ";")`, "some text") + test_xpath_eval(t, book_example, `string-join(//book/@category, ";")`, "cooking;children;web;web") +} + +func Test_func_string_length(t *testing.T) { + test_xpath_eval(t, empty_example, `string-length("Harp not on that string, madam; that is past.")`, float64(45)) + test_xpath_eval(t, empty_example, `string-length(normalize-space(' abc '))`, float64(3)) + test_xpath_eval(t, html_example, `string-length(//title/text())`, float64(len("My page"))) + test_xpath_eval(t, html_example, `string-length(//html/@lang)`, float64(len("en"))) + test_xpath_count(t, employee_example, `//employee[string-length(@id) > 0]`, 3) // = //employee[@id] +} + +func Test_func_substring(t *testing.T) { + test_xpath_eval(t, empty_example, `substring("motor car", 6)`, " car") + test_xpath_eval(t, empty_example, `substring("metadata", 4, 3)`, "ada") + //test_xpath_eval(t, empty_example, `substring("12345", 5, -3)`, "") ?? it should be 1 ?? + //test_xpath_eval(t, empty_example, `substring("12345", 1.5, 2.6)`, "234") + //test_xpath_eval(t, empty_example, `substring("12345", 0, 3)`, "12") // panic?? + //test_xpath_eval(t, empty_example, `substring("12345", 5, -3)`, "1") + test_xpath_eval(t, html_example, `substring(//title/child::node(), 1)`, "My page") +} + +func Test_func_substring_after(t *testing.T) { + test_xpath_eval(t, empty_example, `substring-after("tattoo", "tat")`, "too") + test_xpath_eval(t, empty_example, `substring-after("tattoo", "tattoo")`, "") +} + +func Test_func_substring_before(t *testing.T) { + test_xpath_eval(t, empty_example, `substring-before("tattoo", "attoo")`, "t") + test_xpath_eval(t, empty_example, `substring-before("tattoo", "tatto")`, "") +} + +func Test_func_sum(t *testing.T) { + test_xpath_eval(t, empty_example, `sum(1 + 2)`, float64(3)) + test_xpath_eval(t, empty_example, `sum(1.1 + 2)`, float64(3.1)) + test_xpath_eval(t, book_example, `sum(//book/price)`, float64(149.93)) + assertPanic(t, func() { selectNode(html_example, `//title[sum('Hello') = 0]`) }) +} + +func Test_func_translate(t *testing.T) { + test_xpath_eval(t, empty_example, `translate("bar","abc","ABC")`, "BAr") + test_xpath_eval(t, empty_example, `translate("--aaa--","abc-","ABC")`, "AAA") + test_xpath_eval(t, empty_example, `translate("abcdabc", "abc", "AB")`, "ABdAB") + test_xpath_eval(t, empty_example, `translate('The quick brown fox', 'brown', 'red')`, "The quick red fdx") +} + +func Test_func_matches(t *testing.T) { + test_xpath_eval(t, empty_example, `matches("abracadabra", "bra")`, true) + test_xpath_eval(t, empty_example, `matches("abracadabra", "(?i)^A.*A$")`, true) + test_xpath_eval(t, empty_example, `matches("abracadabra", "^a.*a$")`, true) + test_xpath_eval(t, empty_example, `matches("abracadabra", "^bra")`, false) + assertPanic(t, func() { selectNode(html_example, `//*[matches()]`) }) // arg len check failure + assertPanic(t, func() { selectNode(html_example, "//*[matches(substring(), 0)]") }) // first arg processing failure + assertPanic(t, func() { selectNode(html_example, "//*[matches(@href, substring())]") }) // second arg processing failure + assertPanic(t, func() { selectNode(html_example, "//*[matches(@href, 0)]") }) // second arg not string + assertPanic(t, func() { selectNode(html_example, "//*[matches(@href, '[invalid')]") }) // second arg invalid regexp + // testing unexpected the regular expression. + _, err := Compile(`//*[matches(., '^[\u0621-\u064AA-Za-z\-]+')]`) + assertErr(t, err) + _, err = Compile(`//*[matches(., '//*[matches(., '\w+`) + assertErr(t, err) +} + +func Test_func_number(t *testing.T) { + test_xpath_eval(t, empty_example, `number(10)`, float64(10)) + test_xpath_eval(t, empty_example, `number(1.11)`, float64(1.11)) + test_xpath_eval(t, empty_example, `number("10") > 10`, false) + test_xpath_eval(t, empty_example, `number("10") = 10`, true) + test_xpath_eval(t, empty_example, `number("123") < 1000`, true) + assertTrue(t, math.IsNaN(MustCompile(`number("123a")`).Evaluate(createNavigator(empty_example)).(float64))) +} + +func Test_func_position(t *testing.T) { + test_xpath_elements(t, book_example, `//book[position() = 1]`, 3) + //test_xpath_elements(t, book_example, `//book[(position() mod 2) = 0]`, 9, 25) + //test_xpath_elements(t, book_example, `//book[position() = last()]`, 25) + //test_xpath_elements(t, book_example, `//book/*[position() = 1]`, 4, 10, 16, 26) + test_xpath_elements(t, book_example, `(//book/title)[position() = 1]`, 3) +} + +func Test_func_replace(t *testing.T) { + test_xpath_eval(t, empty_example, `replace('aa-bb-cc','bb','ee')`, "aa-ee-cc") + test_xpath_eval(t, empty_example, `replace("abracadabra", "bra", "*")`, "a*cada*") + test_xpath_eval(t, empty_example, `replace("abracadabra", "a", "")`, "brcdbr") + // The below xpath expressions is not supported yet + // + //test_xpath_eval(t, empty_example, `replace("abracadabra", "a.*a", "*")`, "*") + //test_xpath_eval(t, empty_example, `replace("abracadabra", "a.*?a", "*")`, "*c*bra") + //test_xpath_eval(t, empty_example, `replace("abracadabra", ".*?", "$1")`, "*c*bra") // error, because the pattern matches the zero-length string + //test_xpath_eval(t, empty_example, `replace("AAAA", "A+", "b")`, "b") + //test_xpath_eval(t, empty_example, `replace("AAAA", "A+?", "b")`, "bbb") + //test_xpath_eval(t, empty_example, `replace("darted", "^(.*?)d(.*)$", "$1c$2")`, "carted") + //test_xpath_eval(t, empty_example, `replace("abracadabra", "a(.)", "a$1$1")`, "abbraccaddabbra") +} + +func Test_func_reverse(t *testing.T) { + //test_xpath_eval(t, employee_example, `reverse(("hello"))`, "hello") // Not passed + test_xpath_elements(t, employee_example, `reverse(//employee)`, 13, 8, 3) + test_xpath_elements(t, employee_example, `//employee[reverse(.) = reverse(.)]`, 3, 8, 13) + assertPanic(t, func() { selectNode(html_example, "reverse(concat())") }) // invalid node-sets argument. + assertPanic(t, func() { selectNode(html_example, "reverse()") }) // missing node-sets argument. +} + +func Test_func_round(t *testing.T) { + test_xpath_eval(t, employee_example, `round(2.5)`, 3) // int + test_xpath_eval(t, employee_example, `round(2.5)`, 3) + test_xpath_eval(t, employee_example, `round(2.4999)`, 2) +} + +func Test_func_namespace_uri(t *testing.T) { + // TODO +} + +func Test_func_normalize_space(t *testing.T) { + // TODO +} diff --git a/xpath_node_test.go b/xpath_node_test.go deleted file mode 100644 index a34dfd4..0000000 --- a/xpath_node_test.go +++ /dev/null @@ -1,327 +0,0 @@ -package xpath - -import "testing" - -func TestXPathNode_self(t *testing.T) { - s := `//name/self::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - - expecteds := []struct { - lines int - value string - }{ - {lines: 4, value: "Opal Kole"}, - {lines: 9, value: "Max Miller"}, - {lines: 14, value: "Beccaa Moss"}, - } - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, "name", n.Data) - e := expecteds[i] - assertEqual(t, e.lines, n.lines) - assertEqual(t, e.value, n.Value()) - } -} - -func TestXPathNode_child(t *testing.T) { - s := `/empinfo/child::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - expected_1 := []struct { - lines int - value string - }{{lines: 3}, {lines: 8}, {lines: 13}} - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, "employee", n.Data) - assertEqual(t, expected_1[i].lines, n.lines) - } - - s = `/empinfo/child::node()` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - for _, n := range nodes { - assertEqual(t, ElementNode, n.Type) - assertEqual(t, "employee", n.Data) - } - - s = `//name/child::text()` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - expected_2 := []string{"Opal Kole", "Max Miller", "Beccaa Moss"} - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, TextNode, n.Type) - assertEqual(t, expected_2[i], n.Value()) - } - - s = `//child::employee/child::email` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, "email", n.Data) - } -} - -func TestXPathNode_descendant(t *testing.T) { - s := `//employee/descendant::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 9, len(nodes)) - expecteds := []struct { - lines int - tag string - }{ - {4, "name"}, - {5, "designation"}, - {6, "email"}, - {9, "name"}, - {10, "designation"}, - {11, "email"}, - {14, "name"}, - {15, "designation"}, - {16, "email"}, - } - for i := 0; i < 9; i++ { - n := nodes[i] - assertEqual(t, expecteds[i].lines, n.lines) - assertEqual(t, expecteds[i].tag, n.Data) - } - - s = `//descendant::employee` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - for _, n := range nodes { - assertEqual(t, "employee", n.Data) - } -} - -func TestXPathNode_descendant_or_self(t *testing.T) { - s := `//employee/descendant-or-self::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 12, len(nodes)) - expected_1 := []struct { - lines int - tag string - }{ - {3, "employee"}, - {4, "name"}, - {5, "designation"}, - {6, "email"}, - {8, "employee"}, - {9, "name"}, - {10, "designation"}, - {11, "email"}, - {13, "employee"}, - {14, "name"}, - {15, "designation"}, - {16, "email"}, - } - for i := 0; i < 12; i++ { - n := nodes[i] - assertEqual(t, expected_1[i].lines, n.lines) - assertEqual(t, expected_1[i].tag, n.Data) - } - - s = `//descendant-or-self::employee` - nodes = selectNodes(employee_example, s) - // Not Passed - assertEqual(t, 3, len(nodes)) - for _, n := range nodes { - assertEqual(t, "employee", n.Data) - } -} - -func TestXPathNode_ancestor(t *testing.T) { - s := `//employee/ancestor::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - n := nodes[0] - assertEqual(t, "empinfo", n.Data) - - s = `//ancestor::name` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - for _, n := range nodes { - assertEqual(t, "name", n.Data) - } -} - -func TestXPathNode_ancestor_or_self(t *testing.T) { - s := `//employee/ancestor-or-self::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 4, len(nodes)) - - expecteds := []struct { - lines int - tag string - }{ - {2, "empinfo"}, - {3, "employee"}, - {8, "employee"}, - {13, "employee"}, - } - - for i := 0; i < 4; i++ { - n := nodes[i] - assertEqual(t, expecteds[i].lines, n.lines) - assertEqual(t, expecteds[i].tag, n.Data) - } - - s = `//name/ancestor-or-self::employee` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, expecteds[i+1].lines, n.lines) - assertEqual(t, expecteds[i+1].tag, n.Data) - } -} - -func TestXPathNode_parent(t *testing.T) { - s := `//name/parent::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - - expecteds := []struct { - lines int - tag string - }{ - {3, "employee"}, - {8, "employee"}, - {13, "employee"}, - } - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, expecteds[i].lines, n.lines) - assertEqual(t, expecteds[i].tag, n.Data) - } - - s = `//name/parent::employee` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, expecteds[i].lines, n.lines) - assertEqual(t, expecteds[i].tag, n.Data) - } -} - -func TestXPathNode_attribute(t *testing.T) { - s := `//attribute::id` - nodes := selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - - expecteds := []string{"1", "2", "3"} - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, AttributeNode.String(), n.Type.String()) - assertEqual(t, expecteds[i], n.Data) - } - - s = `//attribute::*` - nodes = selectNodes(employee_example, s) - assertEqual(t, 9, len(nodes)) - expected_attributes := []string{"id", "discipline", "experience", "id", "from", "discipline", "experience", "id", "discipline"} - for i := 0; i < 9; i++ { - n := nodes[i] - assertEqual(t, expected_attributes[i], n.Data) - } -} - -func TestXPathNode_following(t *testing.T) { - s := `//employee[@id=1]/following::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 8, len(nodes)) - - expecteds := []struct { - lines int - tag string - }{ - {8, "employee"}, - {9, "name"}, - {10, "designation"}, - {11, "email"}, - {13, "employee"}, - {14, "name"}, - {15, "designation"}, - {16, "email"}, - } - - for i := 0; i < 8; i++ { - n := nodes[i] - assertEqual(t, expecteds[i].lines, n.lines) - assertEqual(t, expecteds[i].tag, n.Data) - } -} - -func TestXPathNode_following_sibling(t *testing.T) { - s := `//employee[@id=1]/following-sibling::*` - nodes := selectNodes(employee_example, s) - assertEqual(t, 2, len(nodes)) - - expecteds := []struct { - lines int - tag string - }{ - {8, "employee"}, - {13, "employee"}, - } - - for i := 0; i < 2; i++ { - n := nodes[i] - assertEqual(t, expecteds[i].lines, n.lines) - assertEqual(t, expecteds[i].tag, n.Data) - } -} - -func TestXPathNode_preceding(t *testing.T) { - s := `//employee[@id=3]/preceding::*` - nodes := selectNodes(employee_example, s) - // Warning: The sorted result nodes is incorrect. [3, 4, ..] should be at first. - assertEqual(t, 8, len(nodes)) - - expecteds := []struct { - lines int - tag string - }{ - {8, "employee"}, - {9, "name"}, - {10, "designation"}, - {11, "email"}, - - {3, "employee"}, - {4, "name"}, - {5, "designation"}, - {6, "email"}, - } - - for i := 0; i < 8; i++ { - n := nodes[i] - assertEqual(t, expecteds[i].lines, n.lines) - assertEqual(t, expecteds[i].tag, n.Data) - } -} - -func TestXPathNode_preceding_sibling(t *testing.T) { - s := `//employee[@id=3]/preceding-sibling::*` - nodes := selectNodes(employee_example, s) - // Warning: The sorted result nodes is incorrect. [3, 8] should be at first. - assertEqual(t, 2, len(nodes)) - - expecteds := []struct { - lines int - tag string - }{ - {8, "employee"}, - {3, "employee"}, - } - - for i := 0; i < 8; i++ { - n := nodes[i] - assertEqual(t, expecteds[i].lines, n.lines) - assertEqual(t, expecteds[i].tag, n.Data) - } - -} diff --git a/xpath_predicate_test.go b/xpath_predicate_test.go index 48fbc53..30126e1 100644 --- a/xpath_predicate_test.go +++ b/xpath_predicate_test.go @@ -1,140 +1,57 @@ package xpath -import "testing" - -func TestXPathPredicate_Positions(t *testing.T) { - s := `/empinfo/employee[2]` - nodes := selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 8, nodes[0].lines) - - s = `/empinfo/employee[2]/name` - nodes = selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 9, nodes[0].lines) - - s = `//employee[position()=2]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 8, nodes[0].lines) - - s = `//employee[position()>1]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 2, len(nodes)) - - expecteds := []int{8, 13} - for i := 0; i < 2; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } - - s = `//employee[position()<=3]` - nodes = selectNodes(employee_example, s) - expecteds = []int{3, 8, 13} - for i := 0; i < 2; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } +import ( + "testing" +) + +func TestLogicals(t *testing.T) { + test_xpath_elements(t, book_example, `//book[1 + 1]`, 9) + test_xpath_elements(t, book_example, `//book[1 * 2]`, 9) + test_xpath_elements(t, book_example, `//book[5 div 2]`, 9) // equal to `//book[2]` + test_xpath_elements(t, book_example, `//book[3 div 2]`, 3) + test_xpath_elements(t, book_example, `//book[3 - 2]`, 3) + test_xpath_elements(t, book_example, `//book[price > 35]`, 15, 25) + test_xpath_elements(t, book_example, `//book[price >= 30]`, 3, 15, 25) + test_xpath_elements(t, book_example, `//book[price < 30]`, 9) + test_xpath_elements(t, book_example, `//book[price <= 30]`, 3, 9) + test_xpath_elements(t, book_example, `//book[count(author) > 1]`, 15) + test_xpath_elements(t, book_example, `//book[position() mod 2 = 0]`, 9, 25) } -func TestXPathPredicate_Nodes(t *testing.T) { - s := `//employee[name]` - nodes := selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - expecteds := []int{3, 8, 13} - for i := 0; i < 2; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } +func TestPositions(t *testing.T) { + test_xpath_elements(t, employee_example, `/empinfo/employee[2]`, 8) + test_xpath_elements(t, employee_example, `//employee[position() = 2]`, 8) + test_xpath_elements(t, employee_example, `/empinfo/employee[2]/name`, 9) + test_xpath_elements(t, employee_example, `//employee[position() > 1]`, 8, 13) + test_xpath_elements(t, employee_example, `//employee[position() <= 2]`, 3, 8) + test_xpath_elements(t, employee_example, `//employee[last()]`, 13) + test_xpath_elements(t, employee_example, `//employee[position() = last()]`, 13) + test_xpath_elements(t, book_example, `//book[@category = "web"][2]`, 25) + test_xpath_elements(t, book_example, `(//book[@category = "web"])[2]`, 25) } -func TestXPathPredicate_Operators(t *testing.T) { - s := `/empinfo/employee[@id]` - nodes := selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - - s = `/empinfo/employee[@id = 2]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 8, nodes[0].lines) - - s = `/empinfo/employee[1][@id=1]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 3, nodes[0].lines) - - s = `/empinfo/employee[@id][2]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 8, nodes[0].lines) - - s = `//designation[@discipline and @experience]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 2, len(nodes)) - expecteds := []int{5, 10} - for i := 0; i < 2; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } - - s = `//designation[@discipline or @experience]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - expecteds = []int{5, 10, 15} - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } - - s = `//designation[@discipline | @experience]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 3, len(nodes)) - expecteds = []int{5, 10, 15} - for i := 0; i < 3; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } - - s = `/empinfo/employee[@id != "2" ]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 2, len(nodes)) - expecteds = []int{3, 13} - for i := 0; i < 2; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } - - s = `/empinfo/employee[@id > 1 ]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 2, len(nodes)) - expecteds = []int{8, 13} - for i := 0; i < 2; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } - - s = `/empinfo/employee[@id and @id = "2"]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 8, nodes[0].lines) - - s = `/empinfo/employee[@id = "1" or @id = "2"]` - nodes = selectNodes(employee_example, s) - expecteds = []int{3, 8} - for i := 0; i < 2; i++ { - n := nodes[i] - assertEqual(t, expecteds[i], n.lines) - } +func TestPredicates(t *testing.T) { + test_xpath_elements(t, employee_example, `//employee[name]`, 3, 8, 13) + test_xpath_elements(t, employee_example, `/empinfo/employee[@id]`, 3, 8, 13) + test_xpath_elements(t, book_example, `//book[@category = "web"]`, 15, 25) + test_xpath_elements(t, book_example, `//book[author = "J K. Rowling"]`, 9) + test_xpath_elements(t, book_example, `//book[./author/text() = "J K. Rowling"]`, 9) + test_xpath_elements(t, book_example, `//book[year = 2005]`, 3, 9) + test_xpath_elements(t, book_example, `//year[text() = 2005]`, 6, 12) + test_xpath_elements(t, employee_example, `/empinfo/employee[1][@id=1]`, 3) + test_xpath_elements(t, employee_example, `/empinfo/employee[@id][2]`, 8) } -func TestXPathPredicate_NestedPredicate(t *testing.T) { - s := `//employee[./name[@from]]` - nodes := selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 8, nodes[0].lines) +func TestOperators(t *testing.T) { + test_xpath_elements(t, employee_example, `//designation[@discipline and @experience]`, 5, 10) + test_xpath_elements(t, employee_example, `//designation[@discipline or @experience]`, 5, 10, 15) + test_xpath_elements(t, employee_example, `//designation[@discipline | @experience]`, 5, 10, 15) + test_xpath_elements(t, employee_example, `/empinfo/employee[@id != "2" ]`, 3, 13) + test_xpath_elements(t, employee_example, `/empinfo/employee[@id and @id = "2"]`, 8) + test_xpath_elements(t, employee_example, `/empinfo/employee[@id = "1" or @id = "2"]`, 3, 8) +} - s = `//employee[.//name[@from = "CA"]]` - nodes = selectNodes(employee_example, s) - assertEqual(t, 1, len(nodes)) - assertEqual(t, 8, nodes[0].lines) +func TestNestedPredicates(t *testing.T) { + test_xpath_elements(t, employee_example, `//employee[./name[@from]]`, 8) + test_xpath_elements(t, employee_example, `//employee[.//name[@from = "CA"]]`, 8) } diff --git a/xpath_test.go b/xpath_test.go index a5a7b5d..9761d70 100644 --- a/xpath_test.go +++ b/xpath_test.go @@ -7,516 +7,156 @@ import ( "testing" ) -func (n NodeType) String() string { - switch n { - case RootNode: - return "RootNode" - case ElementNode: - return "ElementNode" - case AttributeNode: - return "AttributeNode" - case TextNode: - return "TextNode" - case CommentNode: - return "CommentNode" - } - return "allNode" -} - var ( - html = example() - html2 = example2() employee_example = createEmployeeExample() + book_example = createBookExample() + html_example = createHtmlExample() + empty_example = createNode("", RootNode) ) +func test_xpath_elements(t *testing.T, root *TNode, expr string, expected ...int) { + result := selectNodes(root, expr) + assertEqual(t, len(expected), len(result)) + + for i := 0; i < len(expected); i++ { + assertEqual(t, expected[i], result[i].lines) + } +} + +func test_xpath_values(t *testing.T, root *TNode, expr string, expected ...string) { + result := selectNodes(root, expr) + assertEqual(t, len(expected), len(result)) + + for i := 0; i < len(expected); i++ { + assertEqual(t, expected[i], result[i].Value()) + } +} + +func test_xpath_tags(t *testing.T, root *TNode, expr string, expected ...string) { + result := selectNodes(root, expr) + assertEqual(t, len(expected), len(result)) + + for i := 0; i < len(expected); i++ { + assertEqual(t, expected[i], result[i].Data) + } +} + +func test_xpath_count(t *testing.T, root *TNode, expr string, expected int) { + result := selectNodes(root, expr) + assertEqual(t, expected, len(result)) +} + +func test_xpath_eval(t *testing.T, root *TNode, expr string, expected ...interface{}) { + e, err := Compile(expr) + assertNoErr(t, err) + + v := e.Evaluate(createNavigator(root)) + // if is a node-set + if iter, ok := v.(*NodeIterator); ok { + got := iterateNavs(iter) + assertEqual(t, len(expected), len(got)) + for i := 0; i < len(expected); i++ { + assertEqual(t, expected[i], got[i]) + } + return + } + assertEqual(t, expected[0], v) +} + 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) - } + assertNil(t, err) _, err = Compile("//a[id=']/span") - if err == nil { - t.Fatal("//a[id=] should be got correct but is nil") - } + assertErr(t, err) _, err = Compile("//ul/li/@class") - if err != nil { - t.Fatalf("//ul/li/@class should be correct but got error %s", err) - } + assertNil(t, err) _, err = Compile("/a/b/(c, .[not(c)])") - if err != nil { - t.Fatalf("/a/b/(c, .[not(c)]) should be correct but got error %s", err) - } + assertNil(t, err) _, err = Compile("/pre:foo") - if err != nil { - t.Fatalf("/pre:foo should be correct but got error %s", err) - } + assertNil(t, err) } func TestCompileWithNS(t *testing.T) { _, err := CompileWithNS("/foo", nil) - if err != nil { - t.Fatalf("/foo {nil} should be correct but got error %s", err) - } + assertNil(t, err) _, err = CompileWithNS("/foo", map[string]string{}) - if err != nil { - t.Fatalf("/foo {} should be correct but got error %s", err) - } + assertNil(t, err) _, err = CompileWithNS("/foo", map[string]string{"a": "b"}) - if err != nil { - t.Fatalf("/foo {a:b} should be correct but got error %s", err) - } + assertNil(t, err) _, err = CompileWithNS("/a:foo", map[string]string{"a": "b"}) - if err != nil { - t.Fatalf("/a:foo should be correct but got error %s", err) - } + assertNil(t, err) _, err = CompileWithNS("/u:foo", map[string]string{"a": "b"}) - msg := fmt.Sprintf("%v", err) - if msg != "prefix u not defined." { - t.Fatalf("expected 'prefix u not defined' but got: %s", msg) - } + assertErr(t, err) } -func TestNamespace(t *testing.T) { +func TestNamespacePrefixQuery(t *testing.T) { + /* + + + book1 + book2 + book3 + + */ doc := createNode("", RootNode) books := doc.createChildNode("books", ElementNode) + books.lines = 2 book1 := books.createChildNode("book", ElementNode) + book1.lines = 3 book1.createChildNode("book1", TextNode) book2 := books.createChildNode("b:book", ElementNode) + book2.lines = 4 book2.addAttribute("xmlns:b", "ns") book2.createChildNode("book2", TextNode) book3 := books.createChildNode("c:book", ElementNode) + book3.lines = 5 book3.addAttribute("xmlns:c", "ns") book3.createChildNode("book3", TextNode) - // Existing behaviour: - v := joinValues(selectNodes(doc, "//b:book")) - if v != "book2" { - t.Fatalf("expected book2 but got %s", v) - } + test_xpath_elements(t, doc, `//b:book`, 4) // expected [4 , 5] // With namespace bindings: exp, _ := CompileWithNS("//x:book", map[string]string{"x": "ns"}) - v = joinValues(iterateNodes(exp.Select(createNavigator(doc)))) - if v != "book2,book3" { - t.Fatalf("expected 'book2,book3' but got %s", v) - } + nodes := iterateNodes(exp.Select(createNavigator(doc))) + assertEqual(t, 2, len(nodes)) + assertEqual(t, "book2", nodes[0].Value()) + assertEqual(t, "book3", nodes[1].Value()) } func TestMustCompile(t *testing.T) { expr := MustCompile("//") - if expr == nil { - t.Fatal("// should be compiled but got nil object") - } + assertTrue(t, expr != nil) if wanted := (nopQuery{}); expr.q != wanted { t.Fatalf("wanted nopQuery object but got %s", expr) } - iter := expr.Select(createNavigator(html)) + iter := expr.Select(createNavigator(html_example)) if iter.MoveNext() { t.Fatal("should be an empty node list but got one") } } -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 TestLastFunc(t *testing.T) { - testXPath3(t, html, "/head[last()]", html.FirstChild) - ul := selectNode(html, "//ul") - testXPath3(t, html, "//li[last()]", ul.LastChild) - list := selectNodes(html, "//li/a[last()]") - if got := len(list); got != 3 { - t.Fatalf("expected %d, but got %d", 3, got) - } - testXPath3(t, html, "(//ul/li)[last()]", ul.LastChild) - - n := selectNode(html, "//meta[@name][last()]") - if n == nil { - t.Fatal("should found one, but got nil") - } - if expected, value := "description", n.getAttribute("name"); expected != value { - t.Fatalf("expected, %s but got %s", expected, value) - } - -} - -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 TestSequence(t *testing.T) { - testXPath2(t, html2, "//table/tbody/tr/td/(para, .[not(para)])", 9) - testXPath2(t, html2, "//table/tbody/tr/td/(para, .[not(para)], ..)", 12) -} - -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::*", 16) - testXPath2(t, html, "/head/descendant::*", 3) - 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(html2, "//h1/span/following-sibling::text()") - for _, n := range list { - if n.Type != TextNode { - t.Errorf("expected node is text but got:%s nodes %d", n.Data, len(list)) - } - } - 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()", 3) - 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, "//li[1]", ul.FirstChild) - testXPath3(t, html, "//li[4]", ul.LastChild) - testXPath3(t, html, "//li[last()]", ul.LastChild) - testXPath2(t, html2, "//td[2]", 3) -} - -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` - testXPath2(t, html, "//*[matches(@href,'(?i)^.*OU[A-Z]?T$')]", 2) // a links: `/account`,`/about`. Note use of `(?i)` - 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, `replace('aa-bb-cc','bb','ee')`, "aa-ee-cc") - 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.", - ) - testEval(t, html, `string-join(//li/a,';')`, "Home;about;login") - testEval(t, html, `string-join('some text',';')`, "some text") - // preceding-sibling::* - testXPath3(t, html, "//li[last()]/preceding-sibling::*[2]", selectNode(html, "//li[position()=2]")) - // preceding:: - testXPath3(t, html, "//li/preceding::*[1]", selectNode(html, "//h1")) -} - -func TestFunction_matches(t *testing.T) { - // testing unexpected the regular expression. - if _, err := build(`//*[matches(., '^[\u0621-\u064AA-Za-z\-]+')]`, nil); err == nil { - t.Fatal("matches() should got error, but nil") - } - if _, err := build(`//*[matches(., '//*[matches(., '\w+`, nil); err == nil { - t.Fatal("matches() should got error, but nil") - } -} - -func TestTransformFunctionReverse(t *testing.T) { - nodes := selectNodes(html, "reverse(//li)") - expectedReversedNodeValues := []string{"", "login", "about", "Home"} - if len(nodes) != len(expectedReversedNodeValues) { - t.Fatalf("reverse(//li) should return %d
  • nodes", len(expectedReversedNodeValues)) - } - for i := 0; i < len(expectedReversedNodeValues); i++ { - if nodes[i].Value() != expectedReversedNodeValues[i] { - t.Fatalf("reverse(//li)[%d].Value() should be '%s', instead, got '%s'", - i, expectedReversedNodeValues[i], nodes[i].Value()) - } - } - - // Although this xpath itself doesn't make much sense, it does exercise the call path to provide coverage - // for transformFunctionQuery.Evaluate() - testXPath2(t, html, "//h1[reverse(.) = reverse(.)]", 1) - - // Test reverse() parsing error: missing node-sets argument. - assertPanic(t, func() { testXPath2(t, html, "reverse()", 0) }) - // Test reverse() parsing error: invalid node-sets argument. - assertPanic(t, func() { testXPath2(t, html, "reverse(concat())", 0) }) -} - -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) }) - // matches - assertPanic(t, func() { testXPath2(t, html, "//*[matches()]", 0) }) // arg len check failure - assertPanic(t, func() { testXPath2(t, html, "//*[matches(substring(), 0)]", 0) }) // first arg processing failure - assertPanic(t, func() { testXPath2(t, html, "//*[matches(@href, substring())]", 0) }) // second arg processing failure - assertPanic(t, func() { testXPath2(t, html, "//*[matches(@href, 0)]", 0) }) // second arg not string - assertPanic(t, func() { testXPath2(t, html, "//*[matches(@href, '[invalid')]", 0) }) // second arg invalid regexp - // 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? - //string-join - assertPanic(t, func() { testXPath3(t, html, "string-join()", nil) }) - assertPanic(t, func() { testXPath3(t, html, "string-join(//li/a)", nil) }) -} - -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) +func TestNodeType(t *testing.T) { + tests := []struct { + expr string + expected NodeType + }{ + {`//employee`, ElementNode}, + {`//name[text()]`, ElementNode}, + {`//name/text()`, TextNode}, + {`//employee/@id`, AttributeNode}, } - if node.Data != expected { - t.Fatalf("`%s` expected node is %s,but got %s", expr, expected, node.Data) + for _, test := range tests { + v := selectNode(employee_example, test.expr) + assertTrue(t, v != nil) + assertEqual(t, test.expected, test.expected) } -} -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 testXPath4(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 got := node.Value(); got != expected { - t.Fatalf("`%s` expected \n%s,but got\n%s", expr, expected, got) - } + doc := createNode("", CommentNode) + n := selectNode(doc, "//comment()") + assertTrue(t, n != nil) + assertEqual(t, CommentNode, n.Type) } func iterateNavs(t *NodeIterator) []*TNodeNavigator { @@ -812,73 +452,123 @@ func (n *TNode) getAttribute(key string) string { } return "" } -func example2() *TNode { + +func createBookExample() *TNode { /* - - - Hello - - - -

    SPANAnchor This is a H1

    - - - - - - - - - - - - - - - - - - -
    row1-val1row1-val2row1-val3
    row2-val1row2-val2row2-val3
    row3-val1row3-val2row3-val3
    - - + + + + Everyday Italian + Giada De Laurentiis + 2005 + 30.00 + + + Harry Potter + J K. Rowling + 2005 + 29.99 + + + XQuery Kick Start + James McGovern + Per Bothner + Kurt Cagle + James Linn + Vaidyanathan Nagarajan + 2003 + 49.99 + + + Learning XML + Erik T. Ray + 2003 + 39.95 + + */ - 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) - h1 := body.createChildNode("h1", ElementNode) - n = h1.createChildNode("span", ElementNode) - n = n.createChildNode("SPAN", TextNode) - n = h1.createChildNode("a", ElementNode) - n = n.createChildNode("Anchor", TextNode) - h1.createChildNode(" This is a H1 ", TextNode) + type Element struct { + Data string + Attributes map[string]string + } + books := []struct { + category string + title Element + year int + price float64 + authors []string + }{ + { + category: "cooking", + title: Element{"Everyday Italian", map[string]string{"lang": "en"}}, + year: 2005, + price: 30.00, + authors: []string{"Giada De Laurentiis"}, + }, + { + category: "children", + title: Element{"Harry Potter", map[string]string{"lang": "en"}}, + year: 2005, + price: 29.99, + authors: []string{"J K. Rowling"}, + }, + { + category: "web", + title: Element{"XQuery Kick Start", map[string]string{"lang": "en"}}, + year: 2003, + price: 49.99, + authors: []string{"James McGovern", "Per Bothner", "Kurt Cagle", "James Linn", "Vaidyanathan Nagarajan"}, + }, + { + category: "web", + title: Element{"Learning XML", map[string]string{"lang": "en"}}, + year: 2003, + price: 39.95, + authors: []string{"JErik T. Ray"}, + }, + } - n = body.createChildNode("table", ElementNode) - tbody := n.createChildNode("tbody", ElementNode) - n = tbody.createChildNode("tr", ElementNode) - n.createChildNode("td", ElementNode).createChildNode("row1-val1", TextNode) - n.createChildNode("td", ElementNode).createChildNode("row1-val2", TextNode) - n.createChildNode("td", ElementNode).createChildNode("row1-val3", TextNode) - n = tbody.createChildNode("tr", ElementNode) - n.createChildNode("td", ElementNode).createChildNode("para", ElementNode).createChildNode("row2-val1", TextNode) - n.createChildNode("td", ElementNode).createChildNode("para", ElementNode).createChildNode("row2-val2", TextNode) - n.createChildNode("td", ElementNode).createChildNode("para", ElementNode).createChildNode("row2-val3", TextNode) - n = tbody.createChildNode("tr", ElementNode) - n.createChildNode("td", ElementNode).createChildNode("row3-val1", TextNode) - n.createChildNode("td", ElementNode).createChildNode("para", ElementNode).createChildNode("row3-val2", TextNode) - n.createChildNode("td", ElementNode).createChildNode("row3-val3", TextNode) + var lines = 0 + doc := createNode("", RootNode) + lines++ + bookstore := doc.createChildNode("bookstore", ElementNode) + lines++ + bookstore.lines = lines + for i := 0; i < len(books); i++ { + v := books[i] + lines++ - return xhtml + book := bookstore.createChildNode("book", ElementNode) + book.lines = lines + lines++ + book.addAttribute("category", v.category) + // title + title := book.createChildNode("title", ElementNode) + title.lines = lines + lines++ + for k, v := range v.title.Attributes { + title.addAttribute(k, v) + } + title.createChildNode(v.title.Data, TextNode) + // authors + for j := 0; j < len(v.authors); j++ { + author := book.createChildNode("author", ElementNode) + author.lines = lines + lines++ + author.createChildNode(v.authors[j], TextNode) + } + // year + year := book.createChildNode("year", ElementNode) + year.lines = lines + lines++ + year.createChildNode(fmt.Sprintf("%d", v.year), TextNode) + // price + price := book.createChildNode("price", ElementNode) + price.lines = lines + lines++ + price.createChildNode(fmt.Sprintf("%.2f", v.price), TextNode) + } + return doc } // The example document from https://way2tutorial.com/xml/xpath-node-test.php @@ -980,72 +670,95 @@ func createEmployeeExample() *TNode { return doc } -func example() *TNode { +func createHtmlExample() *TNode { /* - - Hello - - - - -

    - This is a H1 -

    - -

    - Hello,This is an example for gxpath. -

    -
    footer script
    - + + My page + + + +

    Welcome to my page

    + +

    This is the first paragraph.

    + + */ + lines := 0 doc := createNode("", RootNode) + lines++ xhtml := doc.createChildNode("html", ElementNode) + xhtml.lines = lines xhtml.addAttribute("lang", "en") - - // The HTML head section. + lines++ + // head container 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") - n = head.createChildNode("meta", ElementNode) - n.addAttribute("name", "description") - n.addAttribute("content", "some description") - // The HTML body section. + head.lines = lines + lines++ + title := head.createChildNode("title", ElementNode) + title.lines = lines + title.createChildNode("My page", TextNode) + lines++ + meta := head.createChildNode("meta", ElementNode) + meta.lines = lines + meta.addAttribute("name", "language") + meta.addAttribute("content", "en") + // skip the head + lines++ + lines++ body := xhtml.createChildNode("body", ElementNode) - n = body.createChildNode("h1", ElementNode) - n = n.createChildNode("\nThis is a H1\n", 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) + body.lines = lines + lines++ + h2 := body.createChildNode("h2", ElementNode) + h2.lines = lines + h2.createChildNode("Welcome to my page", TextNode) + lines++ + links := []struct { + text string + href string + }{ + {text: "Home", href: "/"}, + {text: "About", href: "/About"}, + {text: "Login", href: "/account"}, + } - return xhtml + ul := body.createChildNode("ul", ElementNode) + ul.lines = lines + lines++ + for i := 0; i < len(links); i++ { + link := links[i] + li := ul.createChildNode("li", ElementNode) + li.lines = lines + lines++ + a := li.createChildNode("a", ElementNode) + a.lines = lines + a.addAttribute("href", link.href) + a.createChildNode(link.text, TextNode) + lines++ + // skip the
  • + lines++ + } + // skip the last ul + lines++ + p := body.createChildNode("p", ElementNode) + lines++ + p.lines = lines + p.createChildNode("This is the first paragraph.", TextNode) + lines++ + comment := body.createChildNode("", CommentNode) + comment.lines = lines + lines++ + return doc }