Reviewed by Darin.
http://bugs.webkit.org/show_bug.cgi?id=12497
Implement XPath result ordering.
WebCore:
XPath::NodeVector typedef is replaced with a real XPath::NodeSet class that knows how
to sort itself, and can remember whether it has been already sorted.
* CMakeLists.txt:
* WebCore.pro:
* WebCore.xcodeproj/project.pbxproj:
* WebCoreSources.bkl:
Added XPathNodeSet files.
* xml/XPathExpression.cpp: Fixed includes.
* xml/XPathFunctions.cpp:
(WebCore::XPath::FunId::evaluate): Mark the resulting node-set as unsorted.
(WebCore::XPath::FunLocalName::evaluate): Replacing NodeVector with NodeSet.
(WebCore::XPath::FunNamespaceURI::evaluate): Ditto.
(WebCore::XPath::FunName::evaluate): Ditto.
(WebCore::XPath::FunCount::evaluate): Ditto.
(WebCore::XPath::FunSum::evaluate): Ditto.
* xml/XPathNodeSet.cpp: Added.
(WebCore::XPath::parentWithDepth):
(WebCore::XPath::sortBlock):
(WebCore::XPath::NodeSet::sort): Sort the node-set in document order.
(WebCore::XPath::NodeSet::reverse): Reverse the order (useful for making axes such as
parent or ancestor sorted).
(WebCore::XPath::NodeSet::firstNode): Returns the first node in document order; currently
implemented via fully sorting the node-set, but this can obviously be optimized.
(WebCore::XPath::NodeSet::anyNode): Added for symmetry with firstNode().
* xml/XPathNodeSet.h: Added.
(WebCore::XPath::NodeSet::NodeSet):
(WebCore::XPath::NodeSet::operator=):
(WebCore::XPath::NodeSet::size):
(WebCore::XPath::NodeSet::isEmpty):
(WebCore::XPath::NodeSet::operator[]):
(WebCore::XPath::NodeSet::reserveCapacity):
(WebCore::XPath::NodeSet::clear):
(WebCore::XPath::NodeSet::swap):
(WebCore::XPath::NodeSet::append):
(WebCore::XPath::NodeSet::markSorted):
(WebCore::XPath::NodeSet::isSorted):
Most of these methods just call Vector counterparts.
* xml/XPathParser.cpp: Updated the copyright notice.
* xml/XPathPath.cpp:
(WebCore::XPath::Filter::evaluate): Replacing NodeVector with NodeSet.
(WebCore::XPath::Path::evaluate): Ditto.
(WebCore::XPath::LocationPath::evaluate): Replacing NodeVector with NodeSet. This function
always marks the result as unsorted, because it is hard to tell whether a step breaks node order.
Identifying and implementing special cases when it is not necessary to do so is an important
future optimization.
* xml/XPathPath.h: Replacing NodeVector with NodeSet.
* xml/XPathPredicate.cpp:
(WebCore::XPath::EqTestOp::compare): Replacing NodeVector with NodeSet.
(WebCore::XPath::Union::evaluate): Replacing NodeVector with NodeSet. Currently, Union just
marks the result as unordered; we can consider using merge sort to avoid this.
* xml/XPathResult.cpp:
(WebCore::XPathResult::XPathResult): Replacing NodeVector with NodeSet.
(WebCore::XPathResult::singleNodeValue): Ditto.
(WebCore::XPathResult::snapshotLength): Ditto.
(WebCore::XPathResult::iterateNext): Ditto.
(WebCore::XPathResult::snapshotItem): Ditto.
(WebCore::XPathResult::convertTo): Ditto. Sort the result when requested to.
* xml/XPathResult.h: Replacing NodeVector with NodeSet.
* xml/XPathStep.cpp:
(WebCore::XPath::Step::evaluate): If the input is not sorted, mark the output as such, too.
(WebCore::XPath::Step::nodesInAxis): Fixed a number of bugs when enumerating with an
attribute context node.
(WebCore::XPath::Step::nodeTestMatches): Replacing NodeVector with NodeSet.
* xml/XPathStep.h: Ditto.
* xml/XPathUtil.cpp:
(WebCore::XPath::isValidContextNode): XPath data model doesn't put attribute data into child
nodes, so passing such node as a context could cause problems.
* xml/XPathUtil.h: Removed NodeVector typedef.
* xml/XPathValue.cpp:
(WebCore::XPath::Value::Value):
(WebCore::XPath::Value::toNodeSet):
(WebCore::XPath::Value::toBoolean):
(WebCore::XPath::Value::toNumber):
(WebCore::XPath::Value::toString):
* xml/XPathValue.h:
(WebCore::XPath::Value::):
(WebCore::XPath::Value::isNodeSet):
Replacing NodeVector with NodeSet.
LayoutTests:
* fast/xpath/document-order-expected.txt: Added.
* fast/xpath/document-order.html: Added.
* fast/xpath/text-nodes-expected.txt:
* fast/xpath/text-nodes.html:
* fast/xpath/4XPath/Core/test_step-expected.txt:
Updated results for tests that now pass.
* fast/xpath/xpath-test-pre.js: Added.
* fast/xpath/4XPath/Core/test.js:
Moved checkSnapshot() to a separate file.
* fast/xpath/4XPath/Core/test_core_functions.html:
* fast/xpath/4XPath/Core/test_location_path.html:
* fast/xpath/4XPath/Core/test_nodeset_expr.html:
* fast/xpath/4XPath/Core/test_parser.html:
* fast/xpath/4XPath/Core/test_predicate_list.html:
* fast/xpath/4XPath/Core/test_step.html:
Load xpath-test-pre.js for checkSnapshot().
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@20345 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog
index 1abffc7..59737fc 100644
--- a/LayoutTests/ChangeLog
+++ b/LayoutTests/ChangeLog
@@ -1,3 +1,30 @@
+2007-03-20 Alexey Proskuryakov <ap@webkit.org>
+
+ Reviewed by Darin.
+
+ http://bugs.webkit.org/show_bug.cgi?id=12497
+ Implement XPath result ordering.
+
+ * fast/xpath/document-order-expected.txt: Added.
+ * fast/xpath/document-order.html: Added.
+
+ * fast/xpath/text-nodes-expected.txt:
+ * fast/xpath/text-nodes.html:
+ * fast/xpath/4XPath/Core/test_step-expected.txt:
+ Updated results for tests that now pass.
+
+ * fast/xpath/xpath-test-pre.js: Added.
+ * fast/xpath/4XPath/Core/test.js:
+ Moved checkSnapshot() to a separate file.
+
+ * fast/xpath/4XPath/Core/test_core_functions.html:
+ * fast/xpath/4XPath/Core/test_location_path.html:
+ * fast/xpath/4XPath/Core/test_nodeset_expr.html:
+ * fast/xpath/4XPath/Core/test_parser.html:
+ * fast/xpath/4XPath/Core/test_predicate_list.html:
+ * fast/xpath/4XPath/Core/test_step.html:
+ Load xpath-test-pre.js for checkSnapshot().
+
2007-03-19 Alexey Proskuryakov <ap@webkit.org>
Reviewed by Darin.
diff --git a/LayoutTests/fast/xpath/4XPath/Core/test.js b/LayoutTests/fast/xpath/4XPath/Core/test.js
index a0d7bea..194ee44 100644
--- a/LayoutTests/fast/xpath/4XPath/Core/test.js
+++ b/LayoutTests/fast/xpath/4XPath/Core/test.js
@@ -60,19 +60,3 @@
LCHILDREN = [LANG.getElementsByTagName("foo")[0], LANG.getElementsByTagName("foo")[1], LANG.getElementsByTagName("f\xf6\xf8")[0]];
LCHILD1 = LCHILDREN[0];
LCHILD2 = LCHILDREN[1];
-
-function checkSnapshot(comment, actual, expected) {
- if (actual.snapshotLength != expected.length) {
- testFailed(comment + " incorrect length (expected " + expected.length + ", actual " + actual.snapshotLength + ")");
- return;
- }
-
- for (i = 0; i < actual.snapshotLength; ++i) {
- if (actual.snapshotItem(i) != expected[i]) {
- testFailed(comment + " item " + i + " incorrect (expected " + expected[i].nodeName + ", actual " + actual.snapshotItem(i).nodeName + ")");
- return;
- }
- }
-
- testPassed(comment);
-}
diff --git a/LayoutTests/fast/xpath/4XPath/Core/test_core_functions.html b/LayoutTests/fast/xpath/4XPath/Core/test_core_functions.html
index 739b8fc..ff0f83b 100644
--- a/LayoutTests/fast/xpath/4XPath/Core/test_core_functions.html
+++ b/LayoutTests/fast/xpath/4XPath/Core/test_core_functions.html
@@ -4,6 +4,7 @@
<link rel="stylesheet" href="../../../js/resources/js-test-style.css">
<script src="../../../js/resources/js-test-pre.js"></script>
<script src="test.js"></script>
+<script src="../../xpath-test-pre.js"></script>
</head>
<body>
<div id="console"></div>
diff --git a/LayoutTests/fast/xpath/4XPath/Core/test_location_path.html b/LayoutTests/fast/xpath/4XPath/Core/test_location_path.html
index 38634d2..d06d1c6 100644
--- a/LayoutTests/fast/xpath/4XPath/Core/test_location_path.html
+++ b/LayoutTests/fast/xpath/4XPath/Core/test_location_path.html
@@ -4,6 +4,7 @@
<link rel="stylesheet" href="../../../js/resources/js-test-style.css">
<script src="../../../js/resources/js-test-pre.js"></script>
<script src="test.js"></script>
+<script src="../../xpath-test-pre.js"></script>
</head>
<body>
<div id="console"></div>
diff --git a/LayoutTests/fast/xpath/4XPath/Core/test_nodeset_expr.html b/LayoutTests/fast/xpath/4XPath/Core/test_nodeset_expr.html
index e94a32fe..bd0c447 100644
--- a/LayoutTests/fast/xpath/4XPath/Core/test_nodeset_expr.html
+++ b/LayoutTests/fast/xpath/4XPath/Core/test_nodeset_expr.html
@@ -4,6 +4,7 @@
<link rel="stylesheet" href="../../../js/resources/js-test-style.css">
<script src="../../../js/resources/js-test-pre.js"></script>
<script src="test.js"></script>
+<script src="../../xpath-test-pre.js"></script>
</head>
<body>
<div id="console"></div>
diff --git a/LayoutTests/fast/xpath/4XPath/Core/test_parser.html b/LayoutTests/fast/xpath/4XPath/Core/test_parser.html
index 3c49923..82339e5 100644
--- a/LayoutTests/fast/xpath/4XPath/Core/test_parser.html
+++ b/LayoutTests/fast/xpath/4XPath/Core/test_parser.html
@@ -4,6 +4,7 @@
<link rel="stylesheet" href="../../../js/resources/js-test-style.css">
<script src="../../../js/resources/js-test-pre.js"></script>
<script src="test.js"></script>
+<script src="../../xpath-test-pre.js"></script>
</head>
<body>
<div id="console"></div>
diff --git a/LayoutTests/fast/xpath/4XPath/Core/test_predicate_list.html b/LayoutTests/fast/xpath/4XPath/Core/test_predicate_list.html
index a8c1caf..5852b04 100644
--- a/LayoutTests/fast/xpath/4XPath/Core/test_predicate_list.html
+++ b/LayoutTests/fast/xpath/4XPath/Core/test_predicate_list.html
@@ -4,6 +4,7 @@
<link rel="stylesheet" href="../../../js/resources/js-test-style.css">
<script src="../../../js/resources/js-test-pre.js"></script>
<script src="test.js"></script>
+<script src="../../xpath-test-pre.js"></script>
</head>
<body>
<div id="console"></div>
diff --git a/LayoutTests/fast/xpath/4XPath/Core/test_step-expected.txt b/LayoutTests/fast/xpath/4XPath/Core/test_step-expected.txt
index 9c94b2a..feb2e80 100644
--- a/LayoutTests/fast/xpath/4XPath/Core/test_step-expected.txt
+++ b/LayoutTests/fast/xpath/4XPath/Core/test_step-expected.txt
@@ -1,5 +1,5 @@
PASS ancestor::*
-FAIL ancestor-or-self::* item 0 incorrect (expected ROOT, actual CHILD1)
+PASS ancestor-or-self::*
PASS descendant-or-self::*
PASS child::GCHILD[position()=1]
PASS child::GCHILD[1]
diff --git a/LayoutTests/fast/xpath/4XPath/Core/test_step.html b/LayoutTests/fast/xpath/4XPath/Core/test_step.html
index 62f3c34..e90cb1d 100644
--- a/LayoutTests/fast/xpath/4XPath/Core/test_step.html
+++ b/LayoutTests/fast/xpath/4XPath/Core/test_step.html
@@ -4,6 +4,7 @@
<link rel="stylesheet" href="../../../js/resources/js-test-style.css">
<script src="../../../js/resources/js-test-pre.js"></script>
<script src="test.js"></script>
+<script src="../../xpath-test-pre.js"></script>
</head>
<body>
<div id="console"></div>
diff --git a/LayoutTests/fast/xpath/document-order-expected.txt b/LayoutTests/fast/xpath/document-order-expected.txt
new file mode 100644
index 0000000..5fe3954
--- /dev/null
+++ b/LayoutTests/fast/xpath/document-order-expected.txt
@@ -0,0 +1,31 @@
+PASS ancestor::* (context = attr1)
+PASS ancestor::* (context = GCHILD11)
+PASS ancestor::node() (context = attr1)
+PASS ancestor::node() (context = GCHILD11)
+PASS preceding::node() (context = attr4)
+PASS preceding::node() (context = CHILD2)
+PASS preceding::node() (context = GCHILD12)
+PASS following::node() (context = attr1)
+PASS following::node() (context = CHILD1)
+PASS following::node() (context = GCHILD11)
+PASS following::node() (context = CHILD2)
+PASS //CHILD | //@attr1
+PASS //CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31
+PASS (//CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31)[3]
+PASS (//CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31)[position() = last()]
+PASS //CHILD[2]/GCHILD | //CHILD[1]/GCHILD
+PASS doc.evaluate("string(//*[@name='GCHILD12'] | //CHILD | //@attr1 | //@attr31)", doc, null, XPathResult.STRING_TYPE, null).stringValue is "TEXT1"
+PASS descendant::node() (context = attr1)
+PASS child::node() (context = attr1)
+PASS parent::node() (context = attr1)
+PASS following-sibling::node() (context = attr1)
+PASS preceding-sibling::node() (context = attr4)
+PASS attribute::node() (context = attr1)
+PASS self::node() (context = attr1)
+PASS self::* (context = attr1)
+PASS descendant-or-self::node() (context = attr1)
+PASS ancestor-or-self::node() (context = attr1)
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/xpath/document-order.html b/LayoutTests/fast/xpath/document-order.html
new file mode 100644
index 0000000..10f89fb
--- /dev/null
+++ b/LayoutTests/fast/xpath/document-order.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../js/resources/js-test-style.css">
+<script src="../js/resources/js-test-pre.js"></script>
+<script src="xpath-test-pre.js"></script>
+</head>
+<body>
+<div id="console"></div>
+
+<script>
+doc = (new DOMParser).parseFromString(
+ '<doc>' +
+ '<CHILD attr1="val1" attr31="31">' +
+ '<GCHILD name="GCHILD11"/>' +
+ '<GCHILD name="GCHILD12"/>' +
+ 'TEXT1' +
+ '</CHILD>' +
+ '<CHILD attr4="4">' +
+ '<GCHILD name="GCHILD21"/>' +
+ '</CHILD>' +
+ '</doc>',
+ 'application/xml');
+
+CHILD1 = doc.getElementsByTagName("CHILD")[0];
+CHILD2 = doc.getElementsByTagName("CHILD")[1];
+attr1 = CHILD1.getAttributeNode("attr1");
+attr31 = CHILD1.getAttributeNode("attr31");
+attr4 = CHILD2.getAttributeNode("attr4");
+GCHILD11 = doc.getElementsByTagName("GCHILD")[0];
+GCHILD12 = doc.getElementsByTagName("GCHILD")[1];
+GCHILD21 = doc.getElementsByTagName("GCHILD")[2];
+TEXT1 = GCHILD12.nextSibling;
+
+
+result = doc.evaluate("ancestor::*", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("ancestor::* (context = attr1)",
+ result,
+ [doc.documentElement, CHILD1]);
+
+result = doc.evaluate("ancestor::*", GCHILD11, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("ancestor::* (context = GCHILD11)",
+ result,
+ [doc.documentElement, CHILD1]);
+
+result = doc.evaluate("ancestor::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("ancestor::node() (context = attr1)",
+ result,
+ [doc, doc.documentElement, CHILD1]);
+
+result = doc.evaluate("ancestor::node()", GCHILD11, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("ancestor::node() (context = GCHILD11)",
+ result,
+ [doc, doc.documentElement, CHILD1]);
+
+result = doc.evaluate("preceding::node()", attr4, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("preceding::node() (context = attr4)",
+ result,
+ [CHILD1, GCHILD11, GCHILD12, TEXT1]);
+
+result = doc.evaluate("preceding::node()", CHILD2, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("preceding::node() (context = CHILD2)",
+ result,
+ [CHILD1, GCHILD11, GCHILD12, TEXT1]);
+
+result = doc.evaluate("preceding::node()", GCHILD12, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("preceding::node() (context = GCHILD12)",
+ result,
+ [GCHILD11]);
+
+result = doc.evaluate("following::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("following::node() (context = attr1)",
+ result,
+ [GCHILD11, GCHILD12, TEXT1, CHILD2, GCHILD21]);
+
+result = doc.evaluate("following::node()", CHILD1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("following::node() (context = CHILD1)",
+ result,
+ [CHILD2, GCHILD21]);
+
+result = doc.evaluate("following::node()", GCHILD11, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("following::node() (context = GCHILD11)",
+ result,
+ [GCHILD12, TEXT1, CHILD2, GCHILD21]);
+
+result = doc.evaluate("following::node()", CHILD2, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("following::node() (context = CHILD2)",
+ result,
+ []);
+
+result = doc.evaluate("//CHILD | //@attr1", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("//CHILD | //@attr1",
+ result,
+ [CHILD1, attr1, CHILD2]);
+
+result = doc.evaluate("//CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("//CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31",
+ result,
+ [CHILD1, attr1, attr31, GCHILD12, CHILD2]);
+
+result = doc.evaluate("(//CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31)[3]", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("(//CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31)[3]",
+ result,
+ [attr31]);
+
+result = doc.evaluate("(//CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31)[position() = last()]", doc, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("(//CHILD | //@attr1 | //*[@name='GCHILD12'] | //@attr31)[position() = last()]",
+ result,
+ [CHILD2]);
+
+result = doc.evaluate("//CHILD[2]/GCHILD | //CHILD[1]/GCHILD", doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("//CHILD[2]/GCHILD | //CHILD[1]/GCHILD",
+ result,
+ [GCHILD11, GCHILD12, GCHILD21]);
+
+shouldBe('doc.evaluate("string(//*[@name=\'GCHILD12\'] | //CHILD | //@attr1 | //@attr31)", doc, null, XPathResult.STRING_TYPE, null).stringValue', '"TEXT1"');
+
+result = doc.evaluate("descendant::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("descendant::node() (context = attr1)",
+ result,
+ []);
+
+result = doc.evaluate("child::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("child::node() (context = attr1)",
+ result,
+ []);
+
+result = doc.evaluate("parent::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("parent::node() (context = attr1)",
+ result,
+ [CHILD1]);
+
+result = doc.evaluate("following-sibling::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("following-sibling::node() (context = attr1)",
+ result,
+ []);
+
+result = doc.evaluate("preceding-sibling::node()", attr4, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("preceding-sibling::node() (context = attr4)",
+ result,
+ []);
+
+result = doc.evaluate("attribute::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("attribute::node() (context = attr1)",
+ result,
+ []);
+
+result = doc.evaluate("self::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("self::node() (context = attr1)",
+ result,
+ [attr1]);
+
+result = doc.evaluate("self::*", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("self::* (context = attr1)",
+ result,
+ []);
+
+result = doc.evaluate("descendant-or-self::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("descendant-or-self::node() (context = attr1)",
+ result,
+ [attr1]);
+
+result = doc.evaluate("ancestor-or-self::node()", attr1, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+checkSnapshot("ancestor-or-self::node() (context = attr1)",
+ result,
+ [doc, doc.documentElement, CHILD1, attr1]);
+
+var successfullyParsed = true;
+
+</script>
+<script src="../js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/xpath/text-nodes-expected.txt b/LayoutTests/fast/xpath/text-nodes-expected.txt
index 49880e8..a3cb54d 100644
--- a/LayoutTests/fast/xpath/text-nodes-expected.txt
+++ b/LayoutTests/fast/xpath/text-nodes-expected.txt
@@ -4,9 +4,9 @@
descendant::*, [object Element]: ""
descendant::node(), [object Element]: "a b c"
descendant::node()[2], [object Element]: "b"
-ancestor-or-self::node(), b: "b null null null"
+ancestor-or-self::node(), b: "null null null b"
ancestor-or-self::*, b: "null null"
-ancestor-or-self::node(), a: "a null null null"
+ancestor-or-self::node(), a: "null null null a"
ancestor-or-self::*, a: "null null"
following::node(), [object Element]: ""
following::node(), a: "b c"
@@ -18,9 +18,9 @@
following-sibling::node(), b: "c"
preceding::node(), b: "a"
preceding-sibling::node(), b: "a"
-preceding::node(), c: "b a"
-preceding::text(), c: "b a"
-preceding-sibling::node(), c: "b a"
-preceding-sibling::text(), c: "b a"
+preceding::node(), c: "a b"
+preceding::text(), c: "a b"
+preceding-sibling::node(), c: "a b"
+preceding-sibling::text(), c: "a b"
self::node(), b: "b"
diff --git a/LayoutTests/fast/xpath/text-nodes.html b/LayoutTests/fast/xpath/text-nodes.html
index 10ce9f3..96a9e38 100644
--- a/LayoutTests/fast/xpath/text-nodes.html
+++ b/LayoutTests/fast/xpath/text-nodes.html
@@ -30,9 +30,9 @@
test("descendant::*", elem);
test("descendant::node()", elem);
test("descendant::node()[2]", elem);
- test("ancestor-or-self::node()", bText); // the order of result nodes is incorrect in this test
+ test("ancestor-or-self::node()", bText);
test("ancestor-or-self::*", bText);
- test("ancestor-or-self::node()", aText); // the order of result nodes is incorrect in this test
+ test("ancestor-or-self::node()", aText);
test("ancestor-or-self::*", aText);
test("following::node()", elem);
test("following::node()", aText);
@@ -44,10 +44,10 @@
test("following-sibling::node()", bText);
test("preceding::node()", bText);
test("preceding-sibling::node()", bText);
- test("preceding::node()", cText); // the order of result nodes is incorrect in this test
- test("preceding::text()", cText); // the order of result nodes is incorrect in this test
- test("preceding-sibling::node()", cText); // the order of result nodes is incorrect in this test
- test("preceding-sibling::text()", cText); // the order of result nodes is incorrect in this test
+ test("preceding::node()", cText);
+ test("preceding::text()", cText);
+ test("preceding-sibling::node()", cText);
+ test("preceding-sibling::text()", cText);
test("self::node()", bText);
var successfullyParsed = true;
diff --git a/LayoutTests/fast/xpath/xpath-test-pre.js b/LayoutTests/fast/xpath/xpath-test-pre.js
new file mode 100644
index 0000000..604965e
--- /dev/null
+++ b/LayoutTests/fast/xpath/xpath-test-pre.js
@@ -0,0 +1,15 @@
+function checkSnapshot(comment, actual, expected) {
+ if (actual.snapshotLength != expected.length) {
+ testFailed(comment + " incorrect length (expected " + expected.length + ", actual " + actual.snapshotLength + ")");
+ return;
+ }
+
+ for (i = 0; i < actual.snapshotLength; ++i) {
+ if (actual.snapshotItem(i) != expected[i]) {
+ testFailed(comment + " item " + i + " incorrect (expected " + expected[i].nodeName + ", actual " + actual.snapshotItem(i).nodeName + ")");
+ return;
+ }
+ }
+
+ testPassed(comment);
+}
diff --git a/WebCore/CMakeLists.txt b/WebCore/CMakeLists.txt
index 7479b30..b50c66e 100644
--- a/WebCore/CMakeLists.txt
+++ b/WebCore/CMakeLists.txt
@@ -1206,6 +1206,7 @@
xml/XPathExpressionNode.cpp
xml/XPathFunctions.cpp
xml/XPathNamespace.cpp
+ xml/XPathNodeSet.cpp
xml/XPathNSResolver.cpp
xml/XPathParser.cpp
xml/XPathPath.cpp
diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog
index def9b3f..5b9eb02 100644
--- a/WebCore/ChangeLog
+++ b/WebCore/ChangeLog
@@ -1,3 +1,103 @@
+2007-03-20 Alexey Proskuryakov <ap@webkit.org>
+
+ Reviewed by Darin.
+
+ http://bugs.webkit.org/show_bug.cgi?id=12497
+ Implement XPath result ordering.
+
+ XPath::NodeVector typedef is replaced with a real XPath::NodeSet class that knows how
+ to sort itself, and can remember whether it has been already sorted.
+
+ * CMakeLists.txt:
+ * WebCore.pro:
+ * WebCore.xcodeproj/project.pbxproj:
+ * WebCoreSources.bkl:
+ Added XPathNodeSet files.
+
+ * xml/XPathExpression.cpp: Fixed includes.
+
+ * xml/XPathFunctions.cpp:
+ (WebCore::XPath::FunId::evaluate): Mark the resulting node-set as unsorted.
+ (WebCore::XPath::FunLocalName::evaluate): Replacing NodeVector with NodeSet.
+ (WebCore::XPath::FunNamespaceURI::evaluate): Ditto.
+ (WebCore::XPath::FunName::evaluate): Ditto.
+ (WebCore::XPath::FunCount::evaluate): Ditto.
+ (WebCore::XPath::FunSum::evaluate): Ditto.
+
+ * xml/XPathNodeSet.cpp: Added.
+ (WebCore::XPath::parentWithDepth):
+ (WebCore::XPath::sortBlock):
+ (WebCore::XPath::NodeSet::sort): Sort the node-set in document order.
+ (WebCore::XPath::NodeSet::reverse): Reverse the order (useful for making axes such as
+ parent or ancestor sorted).
+ (WebCore::XPath::NodeSet::firstNode): Returns the first node in document order; currently
+ implemented via fully sorting the node-set, but this can obviously be optimized.
+ (WebCore::XPath::NodeSet::anyNode): Added for symmetry with firstNode().
+
+ * xml/XPathNodeSet.h: Added.
+ (WebCore::XPath::NodeSet::NodeSet):
+ (WebCore::XPath::NodeSet::operator=):
+ (WebCore::XPath::NodeSet::size):
+ (WebCore::XPath::NodeSet::isEmpty):
+ (WebCore::XPath::NodeSet::operator[]):
+ (WebCore::XPath::NodeSet::reserveCapacity):
+ (WebCore::XPath::NodeSet::clear):
+ (WebCore::XPath::NodeSet::swap):
+ (WebCore::XPath::NodeSet::append):
+ (WebCore::XPath::NodeSet::markSorted):
+ (WebCore::XPath::NodeSet::isSorted):
+ Most of these methods just call Vector counterparts.
+
+ * xml/XPathParser.cpp: Updated the copyright notice.
+
+ * xml/XPathPath.cpp:
+ (WebCore::XPath::Filter::evaluate): Replacing NodeVector with NodeSet.
+ (WebCore::XPath::Path::evaluate): Ditto.
+ (WebCore::XPath::LocationPath::evaluate): Replacing NodeVector with NodeSet. This function
+ always marks the result as unsorted, because it is hard to tell whether a step breaks node order.
+ Identifying and implementing special cases when it is not necessary to do so is an important
+ future optimization.
+
+ * xml/XPathPath.h: Replacing NodeVector with NodeSet.
+ * xml/XPathPredicate.cpp:
+ (WebCore::XPath::EqTestOp::compare): Replacing NodeVector with NodeSet.
+ (WebCore::XPath::Union::evaluate): Replacing NodeVector with NodeSet. Currently, Union just
+ marks the result as unordered; we can consider using merge sort to avoid this.
+
+ * xml/XPathResult.cpp:
+ (WebCore::XPathResult::XPathResult): Replacing NodeVector with NodeSet.
+ (WebCore::XPathResult::singleNodeValue): Ditto.
+ (WebCore::XPathResult::snapshotLength): Ditto.
+ (WebCore::XPathResult::iterateNext): Ditto.
+ (WebCore::XPathResult::snapshotItem): Ditto.
+ (WebCore::XPathResult::convertTo): Ditto. Sort the result when requested to.
+
+ * xml/XPathResult.h: Replacing NodeVector with NodeSet.
+
+ * xml/XPathStep.cpp:
+ (WebCore::XPath::Step::evaluate): If the input is not sorted, mark the output as such, too.
+ (WebCore::XPath::Step::nodesInAxis): Fixed a number of bugs when enumerating with an
+ attribute context node.
+ (WebCore::XPath::Step::nodeTestMatches): Replacing NodeVector with NodeSet.
+ * xml/XPathStep.h: Ditto.
+
+ * xml/XPathUtil.cpp:
+ (WebCore::XPath::isValidContextNode): XPath data model doesn't put attribute data into child
+ nodes, so passing such node as a context could cause problems.
+
+ * xml/XPathUtil.h: Removed NodeVector typedef.
+
+ * xml/XPathValue.cpp:
+ (WebCore::XPath::Value::Value):
+ (WebCore::XPath::Value::toNodeSet):
+ (WebCore::XPath::Value::toBoolean):
+ (WebCore::XPath::Value::toNumber):
+ (WebCore::XPath::Value::toString):
+ * xml/XPathValue.h:
+ (WebCore::XPath::Value::):
+ (WebCore::XPath::Value::isNodeSet):
+ Replacing NodeVector with NodeSet.
+
2007-03-21 Mark Rowe <mrowe@apple.com>
Build fix.
diff --git a/WebCore/WebCore.pro b/WebCore/WebCore.pro
index c835955..4ecd6b1 100644
--- a/WebCore/WebCore.pro
+++ b/WebCore/WebCore.pro
@@ -694,6 +694,7 @@
xml/XPathExpressionNode.cpp \
xml/XPathFunctions.cpp \
xml/XPathNamespace.cpp \
+ xml/XPathNodeSet.cpp \
xml/XPathNSResolver.cpp \
xml/XPathParser.cpp \
xml/XPathPath.cpp \
diff --git a/WebCore/WebCore.xcodeproj/project.pbxproj b/WebCore/WebCore.xcodeproj/project.pbxproj
index 0c1c2c3..26c1992 100644
--- a/WebCore/WebCore.xcodeproj/project.pbxproj
+++ b/WebCore/WebCore.xcodeproj/project.pbxproj
@@ -2898,6 +2898,8 @@
E1E6EEA40B628DA8005F2F70 /* JSHTMLSelectElement.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1E6EEA30B628DA8005F2F70 /* JSHTMLSelectElement.cpp */; };
E1E6EEA80B628DB3005F2F70 /* JSHTMLSelectElement.h in Headers */ = {isa = PBXBuildFile; fileRef = E1E6EEA70B628DB3005F2F70 /* JSHTMLSelectElement.h */; };
E1EBBBD40AAC9B87001FE8E2 /* CSSCharsetRule.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1EBBBD30AAC9B87001FE8E2 /* CSSCharsetRule.cpp */; };
+ E1EC299F0BB04C6B00EA187B /* XPathNodeSet.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1EC299D0BB04C6B00EA187B /* XPathNodeSet.cpp */; };
+ E1EC29A00BB04C6B00EA187B /* XPathNodeSet.h in Headers */ = {isa = PBXBuildFile; fileRef = E1EC299E0BB04C6B00EA187B /* XPathNodeSet.h */; };
E1F0424609839389006694EA /* xmlhttprequest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E1F0424409839389006694EA /* xmlhttprequest.cpp */; };
E1F0424709839389006694EA /* xmlhttprequest.h in Headers */ = {isa = PBXBuildFile; fileRef = E1F0424509839389006694EA /* xmlhttprequest.h */; };
ED048ABC0833F132006E1E67 /* textAreaResizeCorner.tiff in Resources */ = {isa = PBXBuildFile; fileRef = ED048ABB0833F132006E1E67 /* textAreaResizeCorner.tiff */; };
@@ -6109,6 +6111,8 @@
E1E6EEA30B628DA8005F2F70 /* JSHTMLSelectElement.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSHTMLSelectElement.cpp; sourceTree = "<group>"; };
E1E6EEA70B628DB3005F2F70 /* JSHTMLSelectElement.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = JSHTMLSelectElement.h; sourceTree = "<group>"; };
E1EBBBD30AAC9B87001FE8E2 /* CSSCharsetRule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSCharsetRule.cpp; sourceTree = "<group>"; };
+ E1EC299D0BB04C6B00EA187B /* XPathNodeSet.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = XPathNodeSet.cpp; sourceTree = "<group>"; };
+ E1EC299E0BB04C6B00EA187B /* XPathNodeSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = XPathNodeSet.h; sourceTree = "<group>"; };
E1F0424409839389006694EA /* xmlhttprequest.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = xmlhttprequest.cpp; sourceTree = "<group>"; };
E1F0424509839389006694EA /* xmlhttprequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = xmlhttprequest.h; sourceTree = "<group>"; };
ED048ABB0833F132006E1E67 /* textAreaResizeCorner.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = textAreaResizeCorner.tiff; sourceTree = "<group>"; };
@@ -9278,6 +9282,8 @@
1AB7FC510A8B92EC00D9D37B /* XPathGrammar.y */,
1AB7FC520A8B92EC00D9D37B /* XPathNamespace.cpp */,
1AB7FC530A8B92EC00D9D37B /* XPathNamespace.h */,
+ E1EC299D0BB04C6B00EA187B /* XPathNodeSet.cpp */,
+ E1EC299E0BB04C6B00EA187B /* XPathNodeSet.h */,
1AB7FC540A8B92EC00D9D37B /* XPathNSResolver.cpp */,
1AB7FC550A8B92EC00D9D37B /* XPathNSResolver.h */,
1AB7FC560A8B92EC00D9D37B /* XPathNSResolver.idl */,
@@ -11244,6 +11250,7 @@
93F9B7750BA5FDDD00854064 /* JSEntityReference.h in Headers */,
93F9B7A10BA6032600854064 /* JSCDATASection.h in Headers */,
06FC442E0BAE5A9E0090EDE1 /* JavaScriptStatistics.h in Headers */,
+ E1EC29A00BB04C6B00EA187B /* XPathNodeSet.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -12613,6 +12620,7 @@
93F9B7740BA5FDDC00854064 /* JSEntityReference.cpp in Sources */,
93F9B7A00BA6032600854064 /* JSCDATASection.cpp in Sources */,
06FC442D0BAE5A9E0090EDE1 /* JavaScriptStatistics.cpp in Sources */,
+ E1EC299F0BB04C6B00EA187B /* XPathNodeSet.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/WebCore/WebCoreSources.bkl b/WebCore/WebCoreSources.bkl
index 0f88a03..17ebafe 100644
--- a/WebCore/WebCoreSources.bkl
+++ b/WebCore/WebCoreSources.bkl
@@ -502,8 +502,9 @@
xml/XPathExpression.cpp
xml/XPathExpressionNode.cpp
xml/XPathFunctions.cpp
- xml/XPathNSResolver.cpp
xml/XPathNamespace.cpp
+ xml/XPathNodeSet.cpp
+ xml/XPathNSResolver.cpp
xml/XPathParser.cpp
xml/XPathPath.cpp
xml/XPathPredicate.cpp
diff --git a/WebCore/xml/XPathExpression.cpp b/WebCore/xml/XPathExpression.cpp
index 9231001..e59a2c6 100644
--- a/WebCore/xml/XPathExpression.cpp
+++ b/WebCore/xml/XPathExpression.cpp
@@ -29,15 +29,15 @@
#if ENABLE(XPATH)
-#include "PlatformString.h"
#include "Document.h"
+#include "ExceptionCode.h"
#include "Node.h"
+#include "PlatformString.h"
+#include "XPathExpressionNode.h"
#include "XPathNSResolver.h"
#include "XPathParser.h"
#include "XPathResult.h"
-#include "ExceptionCode.h"
-
-#include "XPathExpressionNode.h"
+#include "XPathUtil.h"
namespace WebCore {
diff --git a/WebCore/xml/XPathFunctions.cpp b/WebCore/xml/XPathFunctions.cpp
index c5003d7..0fd6365 100644
--- a/WebCore/xml/XPathFunctions.cpp
+++ b/WebCore/xml/XPathFunctions.cpp
@@ -33,6 +33,7 @@
#include "Document.h"
#include "NamedAttrMap.h"
#include "XMLNames.h"
+#include "XPathUtil.h"
#include "XPathValue.h"
#include <wtf/MathExtras.h>
@@ -262,15 +263,13 @@
Value FunId::evaluate() const
{
- // FIXME: this algorithm does not produce an ordered node-set, as it should.
-
Value a = arg(0)->evaluate();
Vector<UChar> idList; // A whitespace-separated list of IDs
- if (a.isNodeVector()) {
- const NodeVector& nodes = a.toNodeVector();
+ if (a.isNodeSet()) {
+ const NodeSet& nodes = a.toNodeSet();
for (size_t i = 0; i < nodes.size(); ++i) {
- String str = stringValue(nodes[i].get());
+ String str = stringValue(nodes[i]);
idList.append(str.characters(), str.length());
idList.append(' ');
}
@@ -280,7 +279,7 @@
}
Document* contextDocument = evaluationContext().node->document();
- NodeVector result;
+ NodeSet result;
HashSet<Node*> resultSet;
size_t startPos = 0;
@@ -305,6 +304,8 @@
startPos = endPos;
}
+ result.markSorted(false);
+
return result;
}
@@ -313,10 +314,12 @@
Node* node = 0;
if (argCount() > 0) {
Value a = arg(0)->evaluate();
- if (!a.isNodeVector() || a.toNodeVector().size() == 0)
+ if (!a.isNodeSet())
return "";
-
- node = a.toNodeVector()[0].get();
+
+ node = a.toNodeSet().firstNode();
+ if (!node)
+ return "";
}
if (!node)
@@ -330,10 +333,12 @@
Node* node = 0;
if (argCount() > 0) {
Value a = arg(0)->evaluate();
- if (!a.isNodeVector() || a.toNodeVector().size() == 0)
+ if (!a.isNodeSet())
return "";
- node = a.toNodeVector()[0].get();
+ node = a.toNodeSet().firstNode();
+ if (!node)
+ return "";
}
if (!node)
@@ -347,10 +352,12 @@
Node* node = 0;
if (argCount() > 0) {
Value a = arg(0)->evaluate();
- if (!a.isNodeVector() || a.toNodeVector().size() == 0)
+ if (!a.isNodeSet())
return "";
- node = a.toNodeVector()[0].get();
+ node = a.toNodeSet().firstNode();
+ if (!node)
+ return "";
}
if (!node)
@@ -364,10 +371,10 @@
{
Value a = arg(0)->evaluate();
- if (!a.isNodeVector())
+ if (!a.isNodeSet())
return 0.0;
- return a.toNodeVector().size();
+ return a.toNodeSet().size();
}
Value FunString::evaluate() const
@@ -570,14 +577,16 @@
Value FunSum::evaluate() const
{
Value a = arg(0)->evaluate();
- if (!a.isNodeVector())
+ if (!a.isNodeSet())
return 0.0;
double sum = 0.0;
- NodeVector nodes = a.toNodeVector();
-
+ const NodeSet& nodes = a.toNodeSet();
+ // To be really compliant, we should sort the node-set, as floating point addition is not associative.
+ // However, this is unlikely to ever become a practical issue, and sorting is slow.
+
for (unsigned i = 0; i < nodes.size(); i++)
- sum += Value(stringValue(nodes[i].get())).toNumber();
+ sum += Value(stringValue(nodes[i])).toNumber();
return sum;
}
diff --git a/WebCore/xml/XPathNodeSet.cpp b/WebCore/xml/XPathNodeSet.cpp
new file mode 100644
index 0000000..e29c096
--- /dev/null
+++ b/WebCore/xml/XPathNodeSet.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+
+#if ENABLE(XPATH)
+#include "XPathNodeSet.h"
+
+#include "Attr.h"
+#include "Element.h"
+#include "Node.h"
+
+namespace WebCore {
+namespace XPath {
+
+static inline Node* parentWithDepth(unsigned depth, const Vector<Node*>& parents)
+{
+ ASSERT(parents.size() >= depth + 1);
+ return parents[parents.size() - 1 - depth];
+}
+
+static void sortBlock(unsigned from, unsigned to, Vector<Vector<Node*> >& parentMatrix, bool mayContainAttributeNodes)
+{
+ ASSERT(from + 1 < to); // Should not call this function with less that two nodes to sort.
+ unsigned minDepth = UINT_MAX;
+ for (unsigned i = from; i < to; ++i) {
+ unsigned depth = parentMatrix[i].size() - 1;
+ if (minDepth > depth)
+ minDepth = depth;
+ }
+
+ // Find the common ancestor.
+ unsigned commonAncestorDepth = minDepth;
+ Node* commonAncestor;
+ while (true) {
+ commonAncestor = parentWithDepth(commonAncestorDepth, parentMatrix[from]);
+ if (commonAncestorDepth == 0)
+ break;
+
+ bool allEqual = true;
+ for (unsigned i = from + 1; i < to; ++i) {
+ if (commonAncestor != parentWithDepth(commonAncestorDepth, parentMatrix[i])) {
+ allEqual = false;
+ break;
+ }
+ }
+ if (allEqual)
+ break;
+
+ --commonAncestorDepth;
+ }
+
+ if (commonAncestorDepth == minDepth) {
+ // One of the nodes is the common ancestor => it is the first in document order.
+ // Find it and move it to the beginning.
+ for (unsigned i = from; i < to; ++i)
+ if (commonAncestor == parentMatrix[i][0]) {
+ parentMatrix[i].swap(parentMatrix[from]);
+ if (from + 2 < to)
+ sortBlock(from + 1, to, parentMatrix, mayContainAttributeNodes);
+ return;
+ }
+ }
+
+ if (mayContainAttributeNodes && commonAncestor->isElementNode()) {
+ // The attribute nodes and namespace nodes of an element occur before the children of the element.
+ // The namespace nodes are defined to occur before the attribute nodes.
+ // The relative order of namespace nodes is implementation-dependent.
+ // The relative order of attribute nodes is implementation-dependent.
+ unsigned sortedEnd = from;
+ // FIXME: namespace nodes are not implemented.
+ for (unsigned i = sortedEnd; i < to; ++i) {
+ Node* n = parentMatrix[i][0];
+ if (n->isAttributeNode() && static_cast<Attr*>(n)->ownerElement() == commonAncestor)
+ parentMatrix[i].swap(parentMatrix[sortedEnd++]);
+ }
+ if (sortedEnd != from) {
+ if (to - sortedEnd > 1)
+ sortBlock(sortedEnd, to, parentMatrix, mayContainAttributeNodes);
+ return;
+ }
+ }
+
+ // Children nodes of the common ancestor induce a subdivision of our node-set.
+ // Sort it according to this subdivision, and recursively sort each group.
+ HashSet<Node*> parentNodes;
+ for (unsigned i = from; i < to; ++i)
+ parentNodes.add(parentWithDepth(commonAncestorDepth + 1, parentMatrix[i]));
+
+ unsigned previousGroupEnd = from;
+ unsigned groupEnd = from;
+ for (Node* n = commonAncestor->firstChild(); n; n = n->nextSibling()) {
+ // If parentNodes contains the node, perform a linear search to move its children in the node-set to the beginning.
+ if (parentNodes.contains(n)) {
+ for (unsigned i = groupEnd; i < to; ++i)
+ if (parentWithDepth(commonAncestorDepth + 1, parentMatrix[i]) == n)
+ parentMatrix[i].swap(parentMatrix[groupEnd++]);
+
+ if (groupEnd - previousGroupEnd > 1)
+ sortBlock(previousGroupEnd, groupEnd, parentMatrix, mayContainAttributeNodes);
+
+ ASSERT(previousGroupEnd != groupEnd);
+ previousGroupEnd = groupEnd;
+#ifndef NDEBUG
+ parentNodes.remove(n);
+#endif
+ }
+ }
+
+ ASSERT(parentNodes.isEmpty());
+}
+
+void NodeSet::sort() const
+{
+ if (m_isSorted)
+ return;
+
+ unsigned nodeCount = m_nodes.size();
+ if (nodeCount < 2) {
+ const_cast<bool&>(m_isSorted) = true;
+ return;
+ }
+
+ bool containsAttributeNodes = false;
+
+ Vector<Vector<Node*> > parentMatrix(nodeCount);
+ for (unsigned i = 0; i < nodeCount; ++i) {
+ Vector<Node*>& parentsVector = parentMatrix[i];
+ Node* n = m_nodes[i].get();
+ parentsVector.append(n);
+ if (n->isAttributeNode()) {
+ n = static_cast<Attr*>(n)->ownerElement();
+ parentsVector.append(n);
+ containsAttributeNodes = true;
+ }
+ while ((n = n->parent()))
+ parentsVector.append(n);
+ }
+ sortBlock(0, nodeCount, parentMatrix, containsAttributeNodes);
+
+ // It is not possible to just assign the result to m_nodes, because some nodes may get dereferenced and destroyed.
+ Vector<RefPtr<Node> > sortedNodes;
+ sortedNodes.reserveCapacity(nodeCount);
+ for (unsigned i = 0; i < nodeCount; ++i)
+ sortedNodes.append(parentMatrix[i][0]);
+
+ const_cast<Vector<RefPtr<Node> >& >(m_nodes).swap(sortedNodes);
+}
+
+void NodeSet::reverse()
+{
+ if (m_nodes.isEmpty())
+ return;
+
+ unsigned from = 0;
+ unsigned to = m_nodes.size() - 1;
+ while (from < to) {
+ m_nodes[from].swap(m_nodes[to]);
+ ++from;
+ --to;
+ }
+}
+
+Node* NodeSet::firstNode() const
+{
+ if (isEmpty())
+ return 0;
+
+ sort(); // FIXME: fully sorting the node-set just to find its first node is wasteful.
+ return m_nodes.at(0).get();
+}
+
+Node* NodeSet::anyNode() const
+{
+ if (isEmpty())
+ return 0;
+
+ return m_nodes.at(0).get();
+}
+
+}
+}
+
+#endif // ENABLE(XPATH)
diff --git a/WebCore/xml/XPathNodeSet.h b/WebCore/xml/XPathNodeSet.h
new file mode 100644
index 0000000..e24b844
--- /dev/null
+++ b/WebCore/xml/XPathNodeSet.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef XPathNodeSet_h
+#define XPathNodeSet_h
+
+#if ENABLE(XPATH)
+
+#include <wtf/Vector.h>
+#include <wtf/Forward.h>
+
+#include "Node.h"
+
+namespace WebCore {
+
+ namespace XPath {
+
+ class NodeSet {
+ public:
+
+ NodeSet() : m_isSorted(true) {}
+ NodeSet(const NodeSet& other) : m_isSorted(other.m_isSorted), m_nodes(other.m_nodes) {}
+ NodeSet& operator=(const NodeSet& other) { m_isSorted = other.m_isSorted; m_nodes = other.m_nodes; return *this; }
+
+ size_t size() const { return m_nodes.size(); }
+ bool isEmpty() const { return !m_nodes.size(); }
+ Node* operator[](unsigned i) const { return m_nodes.at(i).get(); }
+ void reserveCapacity(size_t newCapacity) { m_nodes.reserveCapacity(newCapacity); }
+ void clear() { m_nodes.resize(0); }
+ void swap(NodeSet& other) { std::swap(m_isSorted, other.m_isSorted); m_nodes.swap(other.m_nodes); }
+
+ // NodeSet itself does not verify that nodes in it are unique.
+ void append(Node* node) { m_nodes.append(node); }
+ void append(PassRefPtr<Node> node) { m_nodes.append(node); }
+ void append(const NodeSet& nodeSet) { m_nodes.append(nodeSet.m_nodes); }
+
+ // Returns the set's first node in document order, or 0 if the set is empty.
+ Node* firstNode() const;
+
+ // Returns 0 if the set is empty.
+ Node* anyNode() const;
+
+ // NodeSet itself doesn't check if it is contains sorted data - the caller should tell it if it does not.
+ void markSorted(bool isSorted) { m_isSorted = isSorted; }
+ bool isSorted() const { return m_isSorted; }
+
+ void sort() const;
+
+ void reverse();
+
+ private:
+ bool m_isSorted;
+ Vector<RefPtr<Node> > m_nodes;
+ };
+
+ }
+}
+
+#endif // ENABLE(XPATH)
+#endif // XPathNodeSet_h
diff --git a/WebCore/xml/XPathParser.cpp b/WebCore/xml/XPathParser.cpp
index 96e1b8b..0e1fbec 100644
--- a/WebCore/xml/XPathParser.cpp
+++ b/WebCore/xml/XPathParser.cpp
@@ -1,6 +1,7 @@
/*
* Copyright 2005 Maksim Orlovich <maksim@kde.org>
* Copyright (C) 2006 Apple Computer, Inc.
+ * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
diff --git a/WebCore/xml/XPathPath.cpp b/WebCore/xml/XPathPath.cpp
index 162e676..35445ba 100644
--- a/WebCore/xml/XPathPath.cpp
+++ b/WebCore/xml/XPathPath.cpp
@@ -1,6 +1,7 @@
/*
* Copyright 2005 Frerich Raabe <raabe@kde.org>
* Copyright (C) 2006 Apple Computer, Inc.
+ * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -52,19 +53,20 @@
{
Value v = m_expr->evaluate();
- if (!v.isNodeVector())
+ if (!v.isNodeSet())
return v;
- NodeVector nodes = v.toNodeVector();
+ NodeSet nodes = v.toNodeSet();
+ nodes.sort();
EvaluationContext& evaluationContext = Expression::evaluationContext();
for (unsigned i = 0; i < m_predicates.size(); i++) {
- NodeVector newNodes;
+ NodeSet newNodes;
evaluationContext.size = nodes.size();
evaluationContext.position = 0;
for (unsigned j = 0; j < nodes.size(); j++) {
- Node* node = nodes[j].get();
+ Node* node = nodes[j];
evaluationContext.node = node;
++evaluationContext.position;
@@ -97,35 +99,36 @@
if (m_absolute && context->nodeType() != Node::DOCUMENT_NODE)
context = context->ownerDocument();
- NodeVector startNodes;
+ NodeSet startNodes;
startNodes.append(context);
return evaluate(startNodes);
}
-Value LocationPath::evaluate(const NodeVector& startNodes) const
+Value LocationPath::evaluate(const NodeSet& startNodes) const
{
- NodeVector inDOMNodes = startNodes;
+ NodeSet nodes = startNodes;
for (unsigned i = 0; i < m_steps.size(); i++) {
Step* step = m_steps[i];
- NodeVector outDOMNodes;
- HashSet<Node*> outDOMNodesSet;
+ NodeSet newNodes;
+ HashSet<Node*> newNodesSet;
- for (unsigned j = 0; j < inDOMNodes.size(); j++) {
- NodeVector matches = step->evaluate(inDOMNodes[j].get());
+ for (unsigned j = 0; j < nodes.size(); j++) {
+ NodeSet matches = step->evaluate(nodes[j]);
for (size_t nodeIndex = 0; nodeIndex < matches.size(); ++nodeIndex) {
- Node* node = matches[nodeIndex].get();
- if (outDOMNodesSet.add(node).second)
- outDOMNodes.append(node);
+ Node* node = matches[nodeIndex];
+ if (newNodesSet.add(node).second)
+ newNodes.append(node);
}
}
- inDOMNodes.swap(outDOMNodes);
+ nodes.swap(newNodes);
}
- return inDOMNodes;
+ nodes.markSorted(false);
+ return nodes;
}
void LocationPath::optimizeStepPair(unsigned index)
@@ -182,7 +185,7 @@
Value Path::evaluate() const
{
- return m_path->evaluate(m_filter->evaluate().toNodeVector());
+ return m_path->evaluate(m_filter->evaluate().toNodeSet());
}
}
diff --git a/WebCore/xml/XPathPath.h b/WebCore/xml/XPathPath.h
index b19130a..d12d562 100644
--- a/WebCore/xml/XPathPath.h
+++ b/WebCore/xml/XPathPath.h
@@ -30,7 +30,7 @@
#if ENABLE(XPATH)
#include "XPathExpressionNode.h"
-#include "XPathUtil.h"
+#include "XPathNodeSet.h"
int xpathyyparse(void*);
@@ -60,7 +60,7 @@
void setAbsolute(bool value) { m_absolute = value; }
virtual Value evaluate() const;
- Value evaluate(const NodeVector& startNodes) const;
+ Value evaluate(const NodeSet& startNodes) const;
void appendStep(Step* step);
void insertFirstStep(Step* step);
diff --git a/WebCore/xml/XPathPredicate.cpp b/WebCore/xml/XPathPredicate.cpp
index b31d2dd..8e819ed 100644
--- a/WebCore/xml/XPathPredicate.cpp
+++ b/WebCore/xml/XPathPredicate.cpp
@@ -1,6 +1,7 @@
/*
* Copyright 2005 Frerich Raabe <raabe@kde.org>
* Copyright (C) 2006 Apple Computer, Inc.
+ * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -32,6 +33,7 @@
#include "Node.h"
#include "XPathFunctions.h"
+#include "XPathUtil.h"
#include "XPathValue.h"
#include <math.h>
@@ -108,16 +110,16 @@
bool EqTestOp::compare(const Value& lhs, const Value& rhs) const
{
- if (lhs.isNodeVector()) {
- const NodeVector& lhsVector = lhs.toNodeVector();
- if (rhs.isNodeVector()) {
+ if (lhs.isNodeSet()) {
+ const NodeSet& lhsSet = lhs.toNodeSet();
+ if (rhs.isNodeSet()) {
// If both objects to be compared are node-sets, then the comparison will be true if and only if
// there is a node in the first node-set and a node in the second node-set such that the result of
// performing the comparison on the string-values of the two nodes is true.
- const NodeVector& rhsVector = rhs.toNodeVector();
- for (unsigned lindex = 0; lindex < lhsVector.size(); ++lindex)
- for (unsigned rindex = 0; rindex < rhsVector.size(); ++rindex)
- if (compare(stringValue(lhsVector[lindex].get()), stringValue(rhsVector[rindex].get())))
+ const NodeSet& rhsSet = rhs.toNodeSet();
+ for (unsigned lindex = 0; lindex < lhsSet.size(); ++lindex)
+ for (unsigned rindex = 0; rindex < rhsSet.size(); ++rindex)
+ if (compare(stringValue(lhsSet[lindex]), stringValue(rhsSet[rindex])))
return true;
return false;
}
@@ -125,8 +127,8 @@
// If one object to be compared is a node-set and the other is a number, then the comparison will be true
// if and only if there is a node in the node-set such that the result of performing the comparison on the number
// to be compared and on the result of converting the string-value of that node to a number using the number function is true.
- for (unsigned lindex = 0; lindex < lhsVector.size(); ++lindex)
- if (compare(Value(stringValue(lhsVector[lindex].get())).toNumber(), rhs))
+ for (unsigned lindex = 0; lindex < lhsSet.size(); ++lindex)
+ if (compare(Value(stringValue(lhsSet[lindex])).toNumber(), rhs))
return true;
return false;
}
@@ -134,8 +136,8 @@
// If one object to be compared is a node-set and the other is a string, then the comparison will be true
// if and only if there is a node in the node-set such that the result of performing the comparison on
// the string-value of the node and the other string is true.
- for (unsigned lindex = 0; lindex < lhsVector.size(); ++lindex)
- if (compare(stringValue(lhsVector[lindex].get()), rhs))
+ for (unsigned lindex = 0; lindex < lhsSet.size(); ++lindex)
+ if (compare(stringValue(lhsSet[lindex]), rhs))
return true;
return false;
}
@@ -147,17 +149,17 @@
}
ASSERT(0);
}
- if (rhs.isNodeVector()) {
- const NodeVector& rhsVector = rhs.toNodeVector();
+ if (rhs.isNodeSet()) {
+ const NodeSet& rhsSet = rhs.toNodeSet();
if (lhs.isNumber()) {
- for (unsigned rindex = 0; rindex < rhsVector.size(); ++rindex)
- if (compare(lhs, Value(stringValue(rhsVector[rindex].get())).toNumber()))
+ for (unsigned rindex = 0; rindex < rhsSet.size(); ++rindex)
+ if (compare(lhs, Value(stringValue(rhsSet[rindex])).toNumber()))
return true;
return false;
}
if (lhs.isString()) {
- for (unsigned rindex = 0; rindex < rhsVector.size(); ++rindex)
- if (compare(lhs, stringValue(rhsVector[rindex].get())))
+ for (unsigned rindex = 0; rindex < rhsSet.size(); ++rindex)
+ if (compare(lhs, stringValue(rhsSet[rindex])))
return true;
return false;
}
@@ -166,7 +168,7 @@
ASSERT(0);
}
- // Neither side is a NodeVector.
+ // Neither side is a NodeSet.
switch (m_opcode) {
case OP_EQ:
case OP_NE:
@@ -232,26 +234,27 @@
Value Union::evaluate() const
{
- // FIXME: This algorithm doesn't return nodes in document order, as it should.
Value lhs = subExpr(0)->evaluate();
Value rhs = subExpr(1)->evaluate();
- if (!lhs.isNodeVector() || !rhs.isNodeVector())
- return NodeVector();
+ if (!lhs.isNodeSet() || !rhs.isNodeSet())
+ return NodeSet();
- NodeVector lhsNodes = lhs.toNodeVector();
- NodeVector rhsNodes = rhs.toNodeVector();
- NodeVector result = lhsNodes;
+ NodeSet result = lhs.toNodeSet();
+ const NodeSet& rhsNodes = rhs.toNodeSet();
HashSet<Node*> nodes;
for (size_t i = 0; i < result.size(); ++i)
- nodes.add(result[i].get());
+ nodes.add(result[i]);
for (size_t i = 0; i < rhsNodes.size(); ++i) {
- Node* node = rhsNodes[i].get();
+ Node* node = rhsNodes[i];
if (nodes.add(node).second)
result.append(node);
}
-
+
+ // It is also possible to use merge sort to avoid making the result unsorted;
+ // but this would waste the time in cases when order is not important.
+ result.markSorted(false);
return result;
}
diff --git a/WebCore/xml/XPathResult.cpp b/WebCore/xml/XPathResult.cpp
index 9a5e8d7..daea517 100644
--- a/WebCore/xml/XPathResult.cpp
+++ b/WebCore/xml/XPathResult.cpp
@@ -63,10 +63,10 @@
case Value::StringValue:
m_resultType = STRING_TYPE;
return;
- case Value::NodeVectorValue:
+ case Value::NodeSetValue:
m_resultType = UNORDERED_NODE_ITERATOR_TYPE;
m_nodeSetPosition = 0;
- m_nodeSet = m_value.toNodeVector();
+ m_nodeSet = m_value.toNodeSet();
m_invalidIteratorState = false;
return;
}
@@ -97,16 +97,31 @@
m_value = m_value.toBoolean();
break;
case UNORDERED_NODE_ITERATOR_TYPE:
- case ORDERED_NODE_ITERATOR_TYPE:
case UNORDERED_NODE_SNAPSHOT_TYPE:
- case ORDERED_NODE_SNAPSHOT_TYPE:
case ANY_UNORDERED_NODE_TYPE:
- case FIRST_ORDERED_NODE_TYPE:
- if (!m_value.isNodeVector()) {
+ case FIRST_ORDERED_NODE_TYPE: // This is correct - singleNodeValue() will take care of ordering.
+ if (!m_value.isNodeSet()) {
ec = TYPE_ERR;
return;
}
m_resultType = type;
+ break;
+ case ORDERED_NODE_ITERATOR_TYPE:
+ if (!m_value.isNodeSet()) {
+ ec = TYPE_ERR;
+ return;
+ }
+ m_nodeSet.sort();
+ m_resultType = type;
+ break;
+ case ORDERED_NODE_SNAPSHOT_TYPE:
+ if (!m_value.isNodeSet()) {
+ ec = TYPE_ERR;
+ return;
+ }
+ m_value.toNodeSet().sort();
+ m_resultType = type;
+ break;
}
}
@@ -149,11 +164,11 @@
return 0;
}
- NodeVector nodes = m_value.toNodeVector();
- if (nodes.size () == 0)
- return 0;
-
- return nodes[0].get();
+ const NodeSet& nodes = m_value.toNodeSet();
+ if (resultType() == FIRST_ORDERED_NODE_TYPE)
+ return nodes.firstNode();
+ else
+ return nodes.anyNode();
}
void XPathResult::invalidateIteratorState()
@@ -183,7 +198,7 @@
return 0;
}
- return m_value.toNodeVector().size();
+ return m_value.toNodeSet().size();
}
Node* XPathResult::iterateNext(ExceptionCode& ec)
@@ -201,7 +216,7 @@
if (m_nodeSetPosition + 1 > m_nodeSet.size())
return 0;
- Node* node = m_nodeSet[m_nodeSetPosition].get();
+ Node* node = m_nodeSet[m_nodeSetPosition];
m_nodeSetPosition++;
@@ -215,11 +230,11 @@
return 0;
}
- NodeVector nodes = m_value.toNodeVector();
+ NodeSet nodes = m_value.toNodeSet();
if (index >= nodes.size())
return 0;
- return nodes[index].get();
+ return nodes[index];
}
}
diff --git a/WebCore/xml/XPathResult.h b/WebCore/xml/XPathResult.h
index b825082..fdea41a 100644
--- a/WebCore/xml/XPathResult.h
+++ b/WebCore/xml/XPathResult.h
@@ -78,7 +78,7 @@
private:
XPath::Value m_value;
unsigned m_nodeSetPosition;
- XPath::NodeVector m_nodeSet;
+ XPath::NodeSet m_nodeSet; // FIXME: why duplicate the node set stored in m_value?
unsigned short m_resultType;
bool m_invalidIteratorState;
RefPtr<EventTargetNode> m_eventTarget;
diff --git a/WebCore/xml/XPathStep.cpp b/WebCore/xml/XPathStep.cpp
index d03cac6..57f5335 100644
--- a/WebCore/xml/XPathStep.cpp
+++ b/WebCore/xml/XPathStep.cpp
@@ -34,6 +34,7 @@
#include "NamedAttrMap.h"
#include "XPathNSResolver.h"
#include "XPathParser.h"
+#include "XPathUtil.h"
namespace WebCore {
namespace XPath {
@@ -58,9 +59,9 @@
deleteAllValues(m_predicates);
}
-NodeVector Step::evaluate(Node* context) const
+NodeSet Step::evaluate(Node* context) const
{
- NodeVector nodes = nodesInAxis(context);
+ NodeSet nodes = nodesInAxis(context);
nodes = nodeTestMatches(nodes);
EvaluationContext& evaluationContext = Expression::evaluationContext();
@@ -68,11 +69,14 @@
for (unsigned i = 0; i < m_predicates.size(); i++) {
Predicate* predicate = m_predicates[i];
- NodeVector newNodes;
+ NodeSet newNodes;
+ if (!nodes.isSorted())
+ newNodes.markSorted(false);
+
evaluationContext.size = nodes.size();
evaluationContext.position = 1;
for (unsigned j = 0; j < nodes.size(); j++) {
- Node* node = nodes[j].get();
+ Node* node = nodes[j];
Expression::evaluationContext().node = node;
EvaluationContext backupCtx = evaluationContext;
@@ -88,32 +92,48 @@
return nodes;
}
-NodeVector Step::nodesInAxis(Node* context) const
+NodeSet Step::nodesInAxis(Node* context) const
{
- NodeVector nodes;
+ NodeSet nodes;
switch (m_axis) {
case ChildAxis:
+ if (context->isAttributeNode()) // In XPath model, attribute nodes do not have children.
+ return nodes;
+
for (Node* n = context->firstChild(); n; n = n->nextSibling())
nodes.append(n);
return nodes;
- case DescendantAxis:
+ case DescendantAxis:
+ if (context->isAttributeNode()) // In XPath model, attribute nodes do not have children.
+ return nodes;
+
for (Node* n = context->firstChild(); n; n = n->traverseNextNode(context))
nodes.append(n);
return nodes;
- case ParentAxis: {
- Node* parent = context->parentNode();
- if (parent)
- nodes.append(parent);
+ case ParentAxis:
+ if (context->isAttributeNode())
+ nodes.append(static_cast<Attr*>(context)->ownerElement());
+ else {
+ Node* parent = context->parentNode();
+ if (parent)
+ nodes.append(parent);
+ }
+ return nodes;
+ case AncestorAxis: {
+ Node* n = context;
+ if (context->isAttributeNode()) {
+ n = static_cast<Attr*>(context)->ownerElement();
+ nodes.append(n);
+ }
+ for (n = n->parentNode(); n; n = n->parentNode())
+ nodes.append(n);
+ nodes.reverse();
return nodes;
}
- case AncestorAxis:
- for (Node* n = context->parentNode(); n; n = n->parentNode())
- nodes.append(n);
- return nodes;
case FollowingSiblingAxis:
if (context->nodeType() == Node::ATTRIBUTE_NODE ||
context->nodeType() == Node::XPATH_NAMESPACE_NODE)
- return NodeVector();
+ return nodes;
for (Node* n = context->nextSibling(); n; n = n->nextSibling())
nodes.append(n);
@@ -121,21 +141,32 @@
case PrecedingSiblingAxis:
if (context->nodeType() == Node::ATTRIBUTE_NODE ||
context->nodeType() == Node::XPATH_NAMESPACE_NODE)
- return NodeVector();
+ return nodes;
for (Node* n = context->previousSibling(); n; n = n->previousSibling())
nodes.append(n);
+
+ nodes.reverse();
return nodes;
- case FollowingAxis:
- for (Node *p = context; !isRootDomNode(p); p = p->parentNode()) {
- for (Node* n = p->nextSibling(); n; n = n->nextSibling()) {
- nodes.append(n);
- for (Node* c = n->firstChild(); c; c = c->traverseNextNode(n))
- nodes.append(c);
+ case FollowingAxis:
+ if (context->isAttributeNode()) {
+ Node* p = static_cast<Attr*>(context)->ownerElement();
+ while ((p = p->traverseNextNode()))
+ nodes.append(p);
+ } else {
+ for (Node* p = context; !isRootDomNode(p); p = p->parentNode()) {
+ for (Node* n = p->nextSibling(); n; n = n->nextSibling()) {
+ nodes.append(n);
+ for (Node* c = n->firstChild(); c; c = c->traverseNextNode(n))
+ nodes.append(c);
+ }
}
}
return nodes;
case PrecedingAxis:
+ if (context->isAttributeNode())
+ context = static_cast<Attr*>(context)->ownerElement();
+
for (Node* p = context; !isRootDomNode(p); p = p->parentNode()) {
for (Node* n = p->previousSibling(); n ; n = n->previousSibling()) {
nodes.append(n);
@@ -143,63 +174,77 @@
nodes.append(c);
}
}
+ nodes.markSorted(false);
return nodes;
case AttributeAxis: {
if (context->nodeType() != Node::ELEMENT_NODE)
- return NodeVector();
+ return nodes;
NamedAttrMap* attrs = context->attributes();
if (!attrs)
return nodes;
for (unsigned long i = 0; i < attrs->length(); ++i)
- nodes.append (attrs->item(i));
+ nodes.append(attrs->item(i));
return nodes;
}
case NamespaceAxis:
// XPath namespace nodes are not implemented yet.
- return NodeVector();
+ return nodes;
case SelfAxis:
nodes.append(context);
return nodes;
case DescendantOrSelfAxis:
nodes.append(context);
+ if (context->isAttributeNode()) // In XPath model, attribute nodes do not have children.
+ return nodes;
+
for (Node* n = context->firstChild(); n; n = n->traverseNextNode(context))
nodes.append(n);
return nodes;
- case AncestorOrSelfAxis:
+ case AncestorOrSelfAxis: {
nodes.append(context);
- for (Node* n = context->parentNode(); n; n = n->parentNode())
+ Node* n = context;
+ if (context->isAttributeNode()) {
+ n = static_cast<Attr*>(context)->ownerElement();
nodes.append(n);
+ }
+ for (n = n->parentNode(); n; n = n->parentNode())
+ nodes.append(n);
+
+ nodes.reverse();
return nodes;
+ }
}
ASSERT_NOT_REACHED();
- return NodeVector();
+ return nodes;
}
-NodeVector Step::nodeTestMatches(const NodeVector& nodes) const
+NodeSet Step::nodeTestMatches(const NodeSet& nodes) const
{
- NodeVector matches;
+ NodeSet matches;
+ if (!nodes.isSorted())
+ matches.markSorted(false);
switch (m_nodeTest.kind()) {
case NodeTest::TextNodeTest:
for (unsigned i = 0; i < nodes.size(); i++) {
- Node* node = nodes[i].get();
+ Node* node = nodes[i];
if ((node->nodeType() == Node::TEXT_NODE || node->nodeType() == Node::CDATA_SECTION_NODE))
matches.append(node);
}
return matches;
case NodeTest::CommentNodeTest:
for (unsigned i = 0; i < nodes.size(); i++) {
- Node* node = nodes[i].get();
+ Node* node = nodes[i];
if (node->nodeType() == Node::COMMENT_NODE)
matches.append(node);
}
return matches;
case NodeTest::ProcessingInstructionNodeTest:
for (unsigned i = 0; i < nodes.size(); i++) {
- Node* node = nodes[i].get();
+ Node* node = nodes[i];
const String& name = m_nodeTest.data();
if (node->nodeType() == Node::PROCESSING_INSTRUCTION_NODE && (name.isEmpty() || node->nodeName() == name))
matches.append(node);
@@ -207,7 +252,7 @@
return matches;
case NodeTest::ElementNodeTest:
for (unsigned i = 0; i < nodes.size(); i++) {
- Node* node = nodes[i].get();
+ Node* node = nodes[i];
if (node->isElementNode())
matches.append(node);
}
@@ -218,7 +263,7 @@
const String& name = m_nodeTest.data();
if (name == "*") {
for (unsigned i = 0; i < nodes.size(); i++) {
- Node* node = nodes[i].get();
+ Node* node = nodes[i];
if (node->nodeType() == primaryNodeType(m_axis) &&
(m_namespaceURI.isEmpty() || m_namespaceURI == node->namespaceURI()))
matches.append(node);
@@ -232,7 +277,7 @@
return matches;
for (unsigned i = 0; i < nodes.size(); i++) {
- Node* node = nodes[i].get();
+ Node* node = nodes[i];
if (node->nodeName() == name) {
matches.append(node);
@@ -245,7 +290,7 @@
// Node test on the namespace axis is not implemented yet
} else {
for (unsigned i = 0; i < nodes.size(); i++) {
- Node* node = nodes[i].get();
+ Node* node = nodes[i];
// We use tagQName here because we don't want the element name in uppercase
// like we get with HTML elements.
@@ -261,7 +306,7 @@
}
}
ASSERT_NOT_REACHED();
- return NodeVector();
+ return matches;
}
Node::NodeType Step::primaryNodeType(Axis axis) const
diff --git a/WebCore/xml/XPathStep.h b/WebCore/xml/XPathStep.h
index 8758867..59b3b0d 100644
--- a/WebCore/xml/XPathStep.h
+++ b/WebCore/xml/XPathStep.h
@@ -31,7 +31,7 @@
#include "Node.h"
#include "XPathExpressionNode.h"
-#include "XPathUtil.h"
+#include "XPathNodeSet.h"
namespace WebCore {
@@ -70,7 +70,7 @@
Step(Axis, const NodeTest& nodeTest, const String& namespaceURI, const Vector<Predicate*>& predicates = Vector<Predicate*>());
~Step();
- NodeVector evaluate(Node* context) const;
+ NodeSet evaluate(Node* context) const;
Axis axis() const { return m_axis; }
NodeTest nodeTest() const { return m_nodeTest; }
@@ -86,8 +86,8 @@
private:
void parseNodeTest(const String&);
- NodeVector nodesInAxis(Node* context) const;
- NodeVector nodeTestMatches(const NodeVector& nodes) const;
+ NodeSet nodesInAxis(Node* context) const;
+ NodeSet nodeTestMatches(const NodeSet& nodes) const;
String namespaceFromNodetest(const String& nodeTest) const;
Node::NodeType primaryNodeType(Axis) const;
diff --git a/WebCore/xml/XPathUtil.cpp b/WebCore/xml/XPathUtil.cpp
index c0cb812..a7cd21f 100644
--- a/WebCore/xml/XPathUtil.cpp
+++ b/WebCore/xml/XPathUtil.cpp
@@ -74,7 +74,8 @@
node->nodeType() == Node::PROCESSING_INSTRUCTION_NODE ||
node->nodeType() == Node::COMMENT_NODE ||
node->nodeType() == Node::DOCUMENT_NODE ||
- node->nodeType() == Node::XPATH_NAMESPACE_NODE);
+ node->nodeType() == Node::XPATH_NAMESPACE_NODE) &&
+ !(node->nodeType() == Node::TEXT_NODE && node->parentNode() && node->parentNode()->isAttributeNode());
}
}
diff --git a/WebCore/xml/XPathUtil.h b/WebCore/xml/XPathUtil.h
index bbab9fe..30f21ae 100644
--- a/WebCore/xml/XPathUtil.h
+++ b/WebCore/xml/XPathUtil.h
@@ -38,8 +38,6 @@
namespace XPath {
- typedef Vector<RefPtr<Node> > NodeVector;
-
/* @return whether the given node is the root node */
bool isRootDomNode(Node*);
diff --git a/WebCore/xml/XPathValue.cpp b/WebCore/xml/XPathValue.cpp
index 0a06773..d35231c 100644
--- a/WebCore/xml/XPathValue.cpp
+++ b/WebCore/xml/XPathValue.cpp
@@ -25,11 +25,13 @@
*/
#include "config.h"
+#include "XPathValue.h"
#if ENABLE(XPATH)
-#include "XPathValue.h"
#include "Node.h"
+#include "XPathUtil.h"
+
#include <wtf/MathExtras.h>
#include <math.h>
@@ -42,13 +44,13 @@
}
Value::Value(Node* value)
- : m_type(NodeVectorValue)
+ : m_type(NodeSetValue)
{
- m_nodeVector.append(value);
+ m_nodeSet.append(value);
}
-Value::Value(const NodeVector& value)
- : m_type(NodeVectorValue), m_nodeVector(value)
+Value::Value(const NodeSet& value)
+ : m_type(NodeSetValue), m_nodeSet(value)
{
}
@@ -82,16 +84,16 @@
{
}
-const NodeVector &Value::toNodeVector() const
+const NodeSet& Value::toNodeSet() const
{
- return m_nodeVector;
+ return m_nodeSet;
}
bool Value::toBoolean() const
{
switch (m_type) {
- case NodeVectorValue:
- return !m_nodeVector.isEmpty();
+ case NodeSetValue:
+ return !m_nodeSet.isEmpty();
case BooleanValue:
return m_bool;
case NumberValue:
@@ -106,7 +108,7 @@
double Value::toNumber() const
{
switch (m_type) {
- case NodeVectorValue:
+ case NodeSetValue:
return Value(toString()).toNumber();
case NumberValue:
return m_number;
@@ -127,10 +129,10 @@
String Value::toString() const
{
switch (m_type) {
- case NodeVectorValue:
- if (m_nodeVector.isEmpty())
+ case NodeSetValue:
+ if (m_nodeSet.isEmpty())
return "";
- return stringValue(m_nodeVector[0].get());
+ return stringValue(m_nodeSet.firstNode());
case StringValue:
return m_string;
case NumberValue:
diff --git a/WebCore/xml/XPathValue.h b/WebCore/xml/XPathValue.h
index aeeb021..1076ba3 100644
--- a/WebCore/xml/XPathValue.h
+++ b/WebCore/xml/XPathValue.h
@@ -30,7 +30,7 @@
#if ENABLE(XPATH)
#include "PlatformString.h"
-#include "XPathUtil.h"
+#include "XPathNodeSet.h"
namespace WebCore {
@@ -38,11 +38,11 @@
class Value {
public:
- enum Type { NodeVectorValue, BooleanValue, NumberValue, StringValue };
+ enum Type { NodeSetValue, BooleanValue, NumberValue, StringValue };
Value();
Value(Node*);
- Value(const NodeVector&);
+ Value(const NodeSet&);
Value(bool);
Value(unsigned);
Value(unsigned long);
@@ -52,19 +52,19 @@
Type type() const { return m_type; }
- bool isNodeVector() const { return m_type == NodeVectorValue; }
+ bool isNodeSet() const { return m_type == NodeSetValue; }
bool isBoolean() const { return m_type == BooleanValue; }
bool isNumber() const { return m_type == NumberValue; }
bool isString() const { return m_type == StringValue; }
- const NodeVector& toNodeVector() const;
+ const NodeSet& toNodeSet() const;
bool toBoolean() const;
double toNumber() const;
String toString() const;
private:
Type m_type;
- NodeVector m_nodeVector;
+ NodeSet m_nodeSet;
bool m_bool;
double m_number;
String m_string;