summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/manual.qbk26
-rw-r--r--docs/samples/xpath_select.cpp2
-rw-r--r--docs/samples/xpath_variables.cpp2
-rw-r--r--src/pugixml.cpp372
-rw-r--r--src/pugixml.hpp15
-rw-r--r--tests/allocator.cpp3
-rw-r--r--tests/main.cpp1
-rw-r--r--tests/test_dom_traverse.cpp16
-rw-r--r--tests/test_write.cpp23
-rw-r--r--tests/test_xpath.cpp22
-rw-r--r--tests/test_xpath_api.cpp65
-rw-r--r--tests/test_xpath_functions.cpp4
-rw-r--r--tests/test_xpath_paths.cpp84
-rw-r--r--tests/test_xpath_variables.cpp2
-rw-r--r--tests/test_xpath_xalan_3.cpp16
15 files changed, 500 insertions, 153 deletions
diff --git a/docs/manual.qbk b/docs/manual.qbk
index 2d13465..833962d 100644
--- a/docs/manual.qbk
+++ b/docs/manual.qbk
@@ -1665,20 +1665,20 @@ The constructor copies the specified range and sets the specified type. The obje
[section:select Selecting nodes via XPath expression]
-[#xml_node::select_single_node][#xml_node::select_nodes]
+[#xml_node::select_node][#xml_node::select_nodes]
If you want to select nodes that match some XPath expression, you can do it with the following functions:
- xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables = 0) const;
+ xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables = 0) const;
xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables = 0) const;
-`select_nodes` function compiles the expression and then executes it with the node as a context node, and returns the resulting node set. `select_single_node` returns only the first node in document order from the result, and is equivalent to calling `select_nodes(query).first()`. If the XPath expression does not match anything, or the node handle is null, `select_nodes` returns an empty set, and `select_single_node` returns null XPath node.
+`select_nodes` function compiles the expression and then executes it with the node as a context node, and returns the resulting node set. `select_node` returns only the first node in document order from the result, and is equivalent to calling `select_nodes(query).first()`. If the XPath expression does not match anything, or the node handle is null, `select_nodes` returns an empty set, and `select_node` returns null XPath node.
If exception handling is not disabled, both functions throw [link xpath_exception] if the query can not be compiled or if it returns a value with type other than node set; see [sref manual.xpath.errors] for details.
-[#xml_node::select_single_node_precomp][#xml_node::select_nodes_precomp]
+[#xml_node::select_node_precomp][#xml_node::select_nodes_precomp]
While compiling expressions is fast, the compilation time can introduce a significant overhead if the same expression is used many times on small subtrees. If you're doing many similar queries, consider compiling them into query objects (see [sref manual.xpath.query] for further reference). Once you get a compiled query object, you can pass it to select functions instead of an expression string:
- xpath_node xml_node::select_single_node(const xpath_query& query) const;
+ xpath_node xml_node::select_node(const xpath_query& query) const;
xpath_node_set xml_node::select_nodes(const xpath_query& query) const;
If exception handling is not disabled, both functions throw [link xpath_exception] if the query returns a value with type other than node set.
@@ -1711,17 +1711,18 @@ The expression is compiled and the compiled representation is stored in the new
xpath_value_type xpath_query::return_type() const;
-[#xpath_query::evaluate_boolean][#xpath_query::evaluate_number][#xpath_query::evaluate_string][#xpath_query::evaluate_node_set]
+[#xpath_query::evaluate_boolean][#xpath_query::evaluate_number][#xpath_query::evaluate_string][#xpath_query::evaluate_node_set][#xpath_query::evaluate_node]
You can evaluate the query using one of the following functions:
bool xpath_query::evaluate_boolean(const xpath_node& n) const;
double xpath_query::evaluate_number(const xpath_node& n) const;
string_t xpath_query::evaluate_string(const xpath_node& n) const;
xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const;
+ xpath_node xpath_query::evaluate_node(const xpath_node& n) const;
-All functions take the context node as an argument, compute the expression and return the result, converted to the requested type. According to XPath specification, value of any type can be converted to boolean, number or string value, but no type other than node set can be converted to node set. Because of this, `evaluate_boolean`, `evaluate_number` and `evaluate_string` always return a result, but `evaluate_node_set` results in an error if the return type is not node set (see [sref manual.xpath.errors]).
+All functions take the context node as an argument, compute the expression and return the result, converted to the requested type. According to XPath specification, value of any type can be converted to boolean, number or string value, but no type other than node set can be converted to node set. Because of this, `evaluate_boolean`, `evaluate_number` and `evaluate_string` always return a result, but `evaluate_node_set` and `evaluate_node` result in an error if the return type is not node set (see [sref manual.xpath.errors]).
-[note Calling `node.select_nodes("query")` is equivalent to calling `xpath_query("query").evaluate_node_set(node)`.]
+[note Calling `node.select_nodes("query")` is equivalent to calling `xpath_query("query").evaluate_node_set(node)`. Calling `node.select_node("query")` is equivalent to calling `xpath_query("query").evaluate_node(node)`.]
[#xpath_query::evaluate_string_buffer]
Note that `evaluate_string` function returns the STL string; as such, it's not available in [link PUGIXML_NO_STL] mode and also usually allocates memory. There is another string evaluation function:
@@ -1744,10 +1745,10 @@ This is an example of using query objects ([@samples/xpath_query.cpp]):
XPath queries may contain references to variables; this is useful if you want to use queries that depend on some dynamic parameter without manually preparing the complete query string, or if you want to reuse the same query object for similar queries.
-Variable references have the form '''<code><phrase role="identifier">$name</phrase></code>'''; in order to use them, you have to provide a variable set, which includes all variables present in the query with correct types. This set is passed to `xpath_query` constructor or to `select_nodes`/`select_single_node` functions:
+Variable references have the form '''<code><phrase role="identifier">$name</phrase></code>'''; in order to use them, you have to provide a variable set, which includes all variables present in the query with correct types. This set is passed to `xpath_query` constructor or to `select_nodes`/`select_node` functions:
explicit xpath_query::xpath_query(const char_t* query, xpath_variable_set* variables = 0);
- xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables = 0) const;
+ xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables = 0) const;
xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables = 0) const;
If you're using query objects, you can change the variable values before `evaluate`/`select` calls to change the query behavior.
@@ -2481,8 +2482,8 @@ Classes:
* `void `[link xml_node::print_stream print]`(std::wostream& os, const char_t* indent = "\t", unsigned int flags = format_default, unsigned int depth = 0) const;`
[lbr]
- * `xpath_node `[link xml_node::select_single_node select_single_node]`(const char_t* query, xpath_variable_set* variables = 0) const;`
- * `xpath_node `[link xml_node::select_single_node_precomp select_single_node]`(const xpath_query& query) const;`
+ * `xpath_node `[link xml_node::select_node select_node]`(const char_t* query, xpath_variable_set* variables = 0) const;`
+ * `xpath_node `[link xml_node::select_node_precomp select_node]`(const xpath_query& query) const;`
* `xpath_node_set `[link xml_node::select_nodes select_nodes]`(const char_t* query, xpath_variable_set* variables = 0) const;`
* `xpath_node_set `[link xml_node::select_nodes_precomp select_nodes]`(const xpath_query& query) const;`
[lbr]
@@ -2620,6 +2621,7 @@ Classes:
* `string_t `[link xpath_query::evaluate_string evaluate_string]`(const xpath_node& n) const;`
* `size_t `[link xpath_query::evaluate_string_buffer evaluate_string]`(char_t* buffer, size_t capacity, const xpath_node& n) const;`
* `xpath_node_set `[link xpath_query::evaluate_node_set evaluate_node_set]`(const xpath_node& n) const;`
+ * `xpath_node `[link xpath_query::evaluate_node evaluate_node]`(const xpath_node& n) const;`
[lbr]
* `xpath_value_type `[link xpath_query::return_type return_type]`() const;`
diff --git a/docs/samples/xpath_select.cpp b/docs/samples/xpath_select.cpp
index 51ede61..74dad60 100644
--- a/docs/samples/xpath_select.cpp
+++ b/docs/samples/xpath_select.cpp
@@ -18,7 +18,7 @@ int main()
std::cout << node.node().attribute("Filename").value() << "\n";
}
- pugi::xpath_node build_tool = doc.select_single_node("//Tool[contains(Description, 'build system')]");
+ pugi::xpath_node build_tool = doc.select_node("//Tool[contains(Description, 'build system')]");
if (build_tool)
std::cout << "Build tool: " << build_tool.node().attribute("Filename").value() << "\n";
diff --git a/docs/samples/xpath_variables.cpp b/docs/samples/xpath_variables.cpp
index 5404c0b..52313bf 100644
--- a/docs/samples/xpath_variables.cpp
+++ b/docs/samples/xpath_variables.cpp
@@ -27,7 +27,7 @@ int main()
std::cout << "Local tool: ";
tools_local[0].node().print(std::cout);
- // You can pass the context directly to select_nodes/select_single_node
+ // You can pass the context directly to select_nodes/select_node
pugi::xpath_node_set tools_local_imm = doc.select_nodes("/Profile/Tools/Tool[@AllowRemote = string($remote)]", &vars);
std::cout << "Local tool imm: ";
diff --git a/src/pugixml.cpp b/src/pugixml.cpp
index ccc8276..4bb0c86 100644
--- a/src/pugixml.cpp
+++ b/src/pugixml.cpp
@@ -3920,6 +3920,31 @@ PUGI__NS_BEGIN
}
}
+ PUGI__FN void node_output_comment(xml_buffered_writer& writer, const char_t* s)
+ {
+ writer.write('<', '!', '-', '-');
+
+ while (*s)
+ {
+ const char_t* prev = s;
+
+ // look for -\0 or -- sequence - we can't output it since -- is illegal in comment body
+ while (*s && !(s[0] == '-' && (s[1] == '-' || s[1] == 0))) ++s;
+
+ writer.write_buffer(prev, static_cast<size_t>(s - prev));
+
+ if (*s)
+ {
+ assert(*s == '-');
+
+ writer.write('-', ' ');
+ ++s;
+ }
+ }
+
+ writer.write('-', '-', '>');
+ }
+
PUGI__FN void node_output_attributes(xml_buffered_writer& writer, const xml_node node, unsigned int flags)
{
const char_t* default_name = PUGIXML_TEXT(":anonymous");
@@ -4019,22 +4044,15 @@ PUGI__NS_BEGIN
break;
case node_comment:
- writer.write('<', '!', '-', '-');
- writer.write_string(node.value());
- writer.write('-', '-', '>');
+ node_output_comment(writer, node.value());
if ((flags & format_raw) == 0) writer.write('\n');
break;
case node_pi:
- case node_declaration:
writer.write('<', '?');
writer.write_string(node.name()[0] ? node.name() : default_name);
- if (node.type() == node_declaration)
- {
- node_output_attributes(writer, node, flags);
- }
- else if (node.value()[0])
+ if (node.value()[0])
{
writer.write(' ');
writer.write_string(node.value());
@@ -4044,6 +4062,14 @@ PUGI__NS_BEGIN
if ((flags & format_raw) == 0) writer.write('\n');
break;
+ case node_declaration:
+ writer.write('<', '?');
+ writer.write_string(node.name()[0] ? node.name() : default_name);
+ node_output_attributes(writer, node, flags);
+ writer.write('?', '>');
+ if ((flags & format_raw) == 0) writer.write('\n');
+ break;
+
case node_doctype:
writer.write('<', '!', 'D', 'O', 'C');
writer.write('T', 'Y', 'P', 'E');
@@ -7999,6 +8025,11 @@ PUGI__NS_BEGIN
*write = 0;
}
+ inline bool is_xpath_attribute(const char_t* name)
+ {
+ return !(starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':'));
+ }
+
struct xpath_variable_boolean: xpath_variable
{
xpath_variable_boolean(): value(false)
@@ -8743,7 +8774,6 @@ PUGI__NS_BEGIN
ast_func_normalize_space_0, // normalize-space()
ast_func_normalize_space_1, // normalize-space(left)
ast_func_translate, // translate(left, right, third)
- ast_func_translate_table, // translate(left, right, third) where right/third are constants
ast_func_boolean, // boolean(left)
ast_func_not, // not(left)
ast_func_true, // true()
@@ -8756,7 +8786,10 @@ PUGI__NS_BEGIN
ast_func_ceiling, // ceiling(left)
ast_func_round, // round(left)
ast_step, // process set left with step
- ast_step_root // select root node
+ ast_step_root, // select root node
+
+ ast_opt_translate_table, // translate(left, right, third) where right/third are constants
+ ast_opt_compare_attribute // @name = 'string'
};
enum axis_t
@@ -8789,6 +8822,13 @@ PUGI__NS_BEGIN
nodetest_all_in_namespace
};
+ enum nodeset_eval_t
+ {
+ nodeset_eval_all,
+ nodeset_eval_any,
+ nodeset_eval_first
+ };
+
template <axis_t N> struct axis_to_type
{
static const axis_t axis;
@@ -8822,7 +8862,7 @@ PUGI__NS_BEGIN
xpath_variable* variable;
// node test for ast_step (node name/namespace/node type/pi target)
const char_t* nodetest;
- // table for ast_func_translate_table
+ // table for ast_opt_translate_table
const unsigned char* table;
} _data;
@@ -8853,8 +8893,8 @@ PUGI__NS_BEGIN
{
xpath_allocator_capture cr(stack.result);
- xpath_node_set_raw ls = lhs->eval_node_set(c, stack);
- xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+ xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
@@ -8882,7 +8922,7 @@ PUGI__NS_BEGIN
xpath_allocator_capture cr(stack.result);
double l = lhs->eval_number(c, stack);
- xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
{
@@ -8899,7 +8939,7 @@ PUGI__NS_BEGIN
xpath_allocator_capture cr(stack.result);
xpath_string l = lhs->eval_string(c, stack);
- xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
{
@@ -8927,8 +8967,8 @@ PUGI__NS_BEGIN
{
xpath_allocator_capture cr(stack.result);
- xpath_node_set_raw ls = lhs->eval_node_set(c, stack);
- xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+ xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
{
@@ -8952,7 +8992,7 @@ PUGI__NS_BEGIN
xpath_allocator_capture cr(stack.result);
double l = lhs->eval_number(c, stack);
- xpath_node_set_raw rs = rhs->eval_node_set(c, stack);
+ xpath_node_set_raw rs = rhs->eval_node_set(c, stack, nodeset_eval_all);
for (const xpath_node* ri = rs.begin(); ri != rs.end(); ++ri)
{
@@ -8968,7 +9008,7 @@ PUGI__NS_BEGIN
{
xpath_allocator_capture cr(stack.result);
- xpath_node_set_raw ls = lhs->eval_node_set(c, stack);
+ xpath_node_set_raw ls = lhs->eval_node_set(c, stack, nodeset_eval_all);
double r = rhs->eval_number(c, stack);
for (const xpath_node* li = ls.begin(); li != ls.end(); ++li)
@@ -8988,7 +9028,7 @@ PUGI__NS_BEGIN
}
}
- void apply_predicate(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack)
+ static void apply_predicate(xpath_node_set_raw& ns, size_t first, xpath_ast_node* expr, const xpath_stack& stack)
{
assert(ns.size() >= first);
@@ -9024,89 +9064,120 @@ PUGI__NS_BEGIN
}
}
- void step_push(xpath_node_set_raw& ns, const xml_attribute a, const xml_node parent, xpath_allocator* alloc)
+ bool step_push(xpath_node_set_raw& ns, const xml_attribute a, const xml_node parent, xpath_allocator* alloc)
{
- if (!a) return;
+ if (!a) return false;
const char_t* name = a.name();
- // There are no attribute nodes corresponding to attributes that declare namespaces
- // That is, "xmlns:..." or "xmlns"
- if (starts_with(name, PUGIXML_TEXT("xmlns")) && (name[5] == 0 || name[5] == ':')) return;
-
switch (_test)
{
case nodetest_name:
- if (strequal(name, _data.nodetest))
+ if (strequal(name, _data.nodetest) && is_xpath_attribute(name))
+ {
ns.push_back(xpath_node(a, parent), alloc);
+ return true;
+ }
break;
case nodetest_type_node:
case nodetest_all:
- ns.push_back(xpath_node(a, parent), alloc);
+ if (is_xpath_attribute(name))
+ {
+ ns.push_back(xpath_node(a, parent), alloc);
+ return true;
+ }
break;
case nodetest_all_in_namespace:
- if (starts_with(name, _data.nodetest))
+ if (starts_with(name, _data.nodetest) && is_xpath_attribute(name))
+ {
ns.push_back(xpath_node(a, parent), alloc);
+ return true;
+ }
break;
default:
;
}
+
+ return false;
}
- void step_push(xpath_node_set_raw& ns, const xml_node n, xpath_allocator* alloc)
+ bool step_push(xpath_node_set_raw& ns, const xml_node n, xpath_allocator* alloc)
{
- if (!n) return;
+ if (!n) return false;
switch (_test)
{
case nodetest_name:
if (n.type() == node_element && strequal(n.name(), _data.nodetest))
+ {
ns.push_back(n, alloc);
+ return true;
+ }
break;
case nodetest_type_node:
ns.push_back(n, alloc);
- break;
+ return true;
case nodetest_type_comment:
if (n.type() == node_comment)
+ {
ns.push_back(n, alloc);
+ return true;
+ }
break;
case nodetest_type_text:
if (n.type() == node_pcdata || n.type() == node_cdata)
+ {
ns.push_back(n, alloc);
+ return true;
+ }
break;
case nodetest_type_pi:
if (n.type() == node_pi)
+ {
ns.push_back(n, alloc);
+ return true;
+ }
break;
case nodetest_pi:
if (n.type() == node_pi && strequal(n.name(), _data.nodetest))
+ {
ns.push_back(n, alloc);
+ return true;
+ }
break;
case nodetest_all:
if (n.type() == node_element)
+ {
ns.push_back(n, alloc);
+ return true;
+ }
break;
case nodetest_all_in_namespace:
if (n.type() == node_element && starts_with(n.name(), _data.nodetest))
+ {
ns.push_back(n, alloc);
+ return true;
+ }
break;
default:
assert(!"Unknown axis");
- }
+ }
+
+ return false;
}
- template <class T> void step_fill(xpath_node_set_raw& ns, const xml_node n, xpath_allocator* alloc, T)
+ template <class T> void step_fill(xpath_node_set_raw& ns, const xml_node n, xpath_allocator* alloc, bool once, T)
{
const axis_t axis = T::axis;
@@ -9115,7 +9186,8 @@ PUGI__NS_BEGIN
case axis_attribute:
{
for (xml_attribute a = n.first_attribute(); a; a = a.next_attribute())
- step_push(ns, a, n, alloc);
+ if (step_push(ns, a, n, alloc) & once)
+ return;
break;
}
@@ -9123,7 +9195,8 @@ PUGI__NS_BEGIN
case axis_child:
{
for (xml_node c = n.first_child(); c; c = c.next_sibling())
- step_push(ns, c, alloc);
+ if (step_push(ns, c, alloc) & once)
+ return;
break;
}
@@ -9132,13 +9205,15 @@ PUGI__NS_BEGIN
case axis_descendant_or_self:
{
if (axis == axis_descendant_or_self)
- step_push(ns, n, alloc);
+ if (step_push(ns, n, alloc) & once)
+ return;
xml_node cur = n.first_child();
while (cur && cur != n)
{
- step_push(ns, cur, alloc);
+ if (step_push(ns, cur, alloc) & once)
+ return;
if (cur.first_child())
cur = cur.first_child();
@@ -9159,7 +9234,8 @@ PUGI__NS_BEGIN
case axis_following_sibling:
{
for (xml_node c = n.next_sibling(); c; c = c.next_sibling())
- step_push(ns, c, alloc);
+ if (step_push(ns, c, alloc) & once)
+ return;
break;
}
@@ -9167,7 +9243,8 @@ PUGI__NS_BEGIN
case axis_preceding_sibling:
{
for (xml_node c = n.previous_sibling(); c; c = c.previous_sibling())
- step_push(ns, c, alloc);
+ if (step_push(ns, c, alloc) & once)
+ return;
break;
}
@@ -9180,9 +9257,10 @@ PUGI__NS_BEGIN
while (cur && !cur.next_sibling()) cur = cur.parent();
cur = cur.next_sibling();
- for (;;)
+ while (cur)
{
- step_push(ns, cur, alloc);
+ if (step_push(ns, cur, alloc) & once)
+ return;
if (cur.first_child())
cur = cur.first_child();
@@ -9207,14 +9285,15 @@ PUGI__NS_BEGIN
while (cur && !cur.previous_sibling()) cur = cur.parent();
cur = cur.previous_sibling();
- for (;;)
+ while (cur)
{
if (cur.last_child())
cur = cur.last_child();
else
{
// leaf node, can't be ancestor
- step_push(ns, cur, alloc);
+ if (step_push(ns, cur, alloc) & once)
+ return;
if (cur.previous_sibling())
cur = cur.previous_sibling();
@@ -9225,7 +9304,9 @@ PUGI__NS_BEGIN
cur = cur.parent();
if (!cur) break;
- if (!node_is_ancestor(cur, n)) step_push(ns, cur, alloc);
+ if (!node_is_ancestor(cur, n))
+ if (step_push(ns, cur, alloc) & once)
+ return;
}
while (!cur.previous_sibling());
@@ -9243,13 +9324,15 @@ PUGI__NS_BEGIN
case axis_ancestor_or_self:
{
if (axis == axis_ancestor_or_self)
- step_push(ns, n, alloc);
+ if (step_push(ns, n, alloc) & once)
+ return;
xml_node cur = n.parent();
while (cur)
{
- step_push(ns, cur, alloc);
+ if (step_push(ns, cur, alloc) & once)
+ return;
cur = cur.parent();
}
@@ -9276,7 +9359,7 @@ PUGI__NS_BEGIN
}
}
- template <class T> void step_fill(xpath_node_set_raw& ns, const xml_attribute a, const xml_node p, xpath_allocator* alloc, T v)
+ template <class T> void step_fill(xpath_node_set_raw& ns, const xml_attribute a, const xml_node p, xpath_allocator* alloc, bool once, T v)
{
const axis_t axis = T::axis;
@@ -9286,13 +9369,15 @@ PUGI__NS_BEGIN
case axis_ancestor_or_self:
{
if (axis == axis_ancestor_or_self && _test == nodetest_type_node) // reject attributes based on principal node type test
- step_push(ns, a, p, alloc);
+ if (step_push(ns, a, p, alloc) & once)
+ return;
xml_node cur = p;
while (cur)
{
- step_push(ns, cur, alloc);
+ if (step_push(ns, cur, alloc) & once)
+ return;
cur = cur.parent();
}
@@ -9327,7 +9412,8 @@ PUGI__NS_BEGIN
if (!cur) break;
}
- step_push(ns, cur, alloc);
+ if (step_push(ns, cur, alloc) & once)
+ return;
}
break;
@@ -9343,7 +9429,7 @@ PUGI__NS_BEGIN
case axis_preceding:
{
// preceding:: axis does not include attribute nodes and attribute ancestors (they are the same as parent's ancestors), so we can reuse node preceding
- step_fill(ns, p, alloc, v);
+ step_fill(ns, p, alloc, once, v);
break;
}
@@ -9351,18 +9437,24 @@ PUGI__NS_BEGIN
assert(!"Unimplemented axis");
}
}
-
- template <class T> xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, T v)
+
+ template <class T> xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval, T v)
{
const axis_t axis = T::axis;
- bool attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self);
+ bool axis_has_attributes = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_descendant_or_self || axis == axis_following || axis == axis_parent || axis == axis_preceding || axis == axis_self);
+ bool axis_reverse = (axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling);
+
+ bool once =
+ (axis == axis_attribute && _test == nodetest_name)
+ ? true
+ : !_right && (axis_reverse ? eval == nodeset_eval_any : eval != nodeset_eval_all);
xpath_node_set_raw ns;
- ns.set_type((axis == axis_ancestor || axis == axis_ancestor_or_self || axis == axis_preceding || axis == axis_preceding_sibling) ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted);
+ ns.set_type(axis_reverse ? xpath_node_set::type_sorted_reverse : xpath_node_set::type_sorted);
if (_left)
{
- xpath_node_set_raw s = _left->eval_node_set(c, stack);
+ xpath_node_set_raw s = _left->eval_node_set(c, stack, nodeset_eval_all);
// self axis preserves the original order
if (axis == axis_self) ns.set_type(s.type());
@@ -9375,9 +9467,9 @@ PUGI__NS_BEGIN
if (axis != axis_self && size != 0) ns.set_type(xpath_node_set::type_unsorted);
if (it->node())
- step_fill(ns, it->node(), stack.result, v);
- else if (attributes)
- step_fill(ns, it->attribute(), it->parent(), stack.result, v);
+ step_fill(ns, it->node(), stack.result, once, v);
+ else if (axis_has_attributes && it->attribute() && it->parent())
+ step_fill(ns, it->attribute(), it->parent(), stack.result, once, v);
apply_predicates(ns, size, stack);
}
@@ -9385,9 +9477,9 @@ PUGI__NS_BEGIN
else
{
if (c.n.node())
- step_fill(ns, c.n.node(), stack.result, v);
- else if (attributes)
- step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, v);
+ step_fill(ns, c.n.node(), stack.result, once, v);
+ else if (axis_has_attributes && c.n.attribute() && c.n.parent())
+ step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, once, v);
apply_predicates(ns, 0, stack);
}
@@ -9533,6 +9625,15 @@ PUGI__NS_BEGIN
return false;
}
+ case ast_opt_compare_attribute:
+ {
+ const char_t* value = (_right->_type == ast_string_constant) ? _right->_data.string : _right->_data.variable->get_string();
+
+ xml_attribute attr = c.n.node().attribute(_left->_data.nodetest);
+
+ return attr && strequal(attr.value(), value) && is_xpath_attribute(attr.name());
+ }
+
case ast_variable:
{
assert(_rettype == _data.variable->type());
@@ -9561,7 +9662,7 @@ PUGI__NS_BEGIN
{
xpath_allocator_capture cr(stack.result);
- return !eval_node_set(c, stack).empty();
+ return !eval_node_set(c, stack, nodeset_eval_any).empty();
}
default:
@@ -9607,7 +9708,7 @@ PUGI__NS_BEGIN
{
xpath_allocator_capture cr(stack.result);
- return static_cast<double>(_left->eval_node_set(c, stack).size());
+ return static_cast<double>(_left->eval_node_set(c, stack, nodeset_eval_all).size());
}
case ast_func_string_length_0:
@@ -9640,7 +9741,7 @@ PUGI__NS_BEGIN
double r = 0;
- xpath_node_set_raw ns = _left->eval_node_set(c, stack);
+ xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_all);
for (const xpath_node* it = ns.begin(); it != ns.end(); ++it)
{
@@ -9776,7 +9877,7 @@ PUGI__NS_BEGIN
{
xpath_allocator_capture cr(stack.result);
- xpath_node_set_raw ns = _left->eval_node_set(c, stack);
+ xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first);
xpath_node na = ns.first();
return xpath_string::from_const(local_name(na));
@@ -9793,7 +9894,7 @@ PUGI__NS_BEGIN
{
xpath_allocator_capture cr(stack.result);
- xpath_node_set_raw ns = _left->eval_node_set(c, stack);
+ xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first);
xpath_node na = ns.first();
return xpath_string::from_const(qualified_name(na));
@@ -9810,7 +9911,7 @@ PUGI__NS_BEGIN
{
xpath_allocator_capture cr(stack.result);
- xpath_node_set_raw ns = _left->eval_node_set(c, stack);
+ xpath_node_set_raw ns = _left->eval_node_set(c, stack, nodeset_eval_first);
xpath_node na = ns.first();
return xpath_string::from_const(namespace_uri(na));
@@ -9938,7 +10039,7 @@ PUGI__NS_BEGIN
return s;
}
- case ast_func_translate_table:
+ case ast_opt_translate_table:
{
xpath_string s = _left->eval_string(c, stack);
@@ -9973,7 +10074,7 @@ PUGI__NS_BEGIN
xpath_stack swapped_stack = {stack.temp, stack.result};
- xpath_node_set_raw ns = eval_node_set(c, swapped_stack);
+ xpath_node_set_raw ns = eval_node_set(c, swapped_stack, nodeset_eval_first);
return ns.empty() ? xpath_string() : string_value(ns.first(), stack.result);
}
@@ -9985,7 +10086,7 @@ PUGI__NS_BEGIN
}
}
- xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack)
+ xpath_node_set_raw eval_node_set(const xpath_context& c, const xpath_stack& stack, nodeset_eval_t eval)
{
switch (_type)
{
@@ -9995,8 +10096,8 @@ PUGI__NS_BEGIN
xpath_stack swapped_stack = {stack.temp, stack.result};
- xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack);
- xpath_node_set_raw rs = _right->eval_node_set(c, stack);
+ xpath_node_set_raw ls = _left->eval_node_set(c, swapped_stack, eval);
+ xpath_node_set_raw rs = _right->eval_node_set(c, stack, eval);
// we can optimize merging two sorted sets, but this is a very rare operation, so don't bother
rs.set_type(xpath_node_set::type_unsorted);
@@ -10010,7 +10111,7 @@ PUGI__NS_BEGIN
case ast_filter:
case ast_filter_posinv:
{
- xpath_node_set_raw set = _left->eval_node_set(c, stack);
+ xpath_node_set_raw set = _left->eval_node_set(c, stack, nodeset_eval_all);
// either expression is a number or it contains position() call; sort by document order
if (_type == ast_filter) set.sort_do();
@@ -10028,44 +10129,44 @@ PUGI__NS_BEGIN
switch (_axis)
{
case axis_ancestor:
- return step_do(c, stack, axis_to_type<axis_ancestor>());
+ return step_do(c, stack, eval, axis_to_type<axis_ancestor>());
case axis_ancestor_or_self:
- return step_do(c, stack, axis_to_type<axis_ancestor_or_self>());
+ return step_do(c, stack, eval, axis_to_type<axis_ancestor_or_self>());
case axis_attribute:
- return step_do(c, stack, axis_to_type<axis_attribute>());
+ return step_do(c, stack, eval, axis_to_type<axis_attribute>());
case axis_child:
- return step_do(c, stack, axis_to_type<axis_child>());
+ return step_do(c, stack, eval, axis_to_type<axis_child>());
case axis_descendant:
- return step_do(c, stack, axis_to_type<axis_descendant>());
+ return step_do(c, stack, eval, axis_to_type<axis_descendant>());
case axis_descendant_or_self:
- return step_do(c, stack, axis_to_type<axis_descendant_or_self>());
+ return step_do(c, stack, eval, axis_to_type<axis_descendant_or_self>());
case axis_following:
- return step_do(c, stack, axis_to_type<axis_following>());
+ return step_do(c, stack, eval, axis_to_type<axis_following>());
case axis_following_sibling:
- return step_do(c, stack, axis_to_type<axis_following_sibling>());
+ return step_do(c, stack, eval, axis_to_type<axis_following_sibling>());
case axis_namespace:
// namespaced axis is not supported
return xpath_node_set_raw();
case axis_parent:
- return step_do(c, stack, axis_to_type<axis_parent>());
+ return step_do(c, stack, eval, axis_to_type<axis_parent>());
case axis_preceding:
- return step_do(c, stack, axis_to_type<axis_preceding>());
+ return step_do(c, stack, eval, axis_to_type<axis_preceding>());
case axis_preceding_sibling:
- return step_do(c, stack, axis_to_type<axis_preceding_sibling>());
+ return step_do(c, stack, eval, axis_to_type<axis_preceding_sibling>());
case axis_self:
- return step_do(c, stack, axis_to_type<axis_self>());
+ return step_do(c, stack, eval, axis_to_type<axis_self>());
default:
assert(!"Unknown axis");
@@ -10120,12 +10221,17 @@ PUGI__NS_BEGIN
// Replace descendant-or-self::node()/child::foo with descendant::foo
// The former is a full form of //foo, the latter is much faster since it executes the node test immediately
+ // Do a similar kind of replacement for self/descendant/descendant-or-self axes
// Note that we only replace positionally invariant steps (//foo[1] != /descendant::foo[1])
- if (_type == ast_step && _axis == axis_child && _left &&
+ if (_type == ast_step && (_axis == axis_child || _axis == axis_self || _axis == axis_descendant || _axis == axis_descendant_or_self) && _left &&
_left->_type == ast_step && _left->_axis == axis_descendant_or_self && _left->_test == nodetest_type_node && !_left->_right &&
is_posinv_step())
{
- _axis = axis_descendant;
+ if (_axis == axis_child || _axis == axis_descendant)
+ _axis = axis_descendant;
+ else
+ _axis = axis_descendant_or_self;
+
_left = _left->_left;
}
@@ -10136,10 +10242,18 @@ PUGI__NS_BEGIN
if (table)
{
- _type = ast_func_translate_table;
+ _type = ast_opt_translate_table;
_data.table = table;
}
}
+
+ // Use optimized path for @attr = 'value' or @attr = $value
+ if (_type == ast_op_equal &&
+ _left->_type == ast_step && _left->_axis == axis_attribute && _left->_test == nodetest_name && !_left->_left && !_left->_right &&
+ (_right->_type == ast_string_constant || (_right->_type == ast_variable && _right->rettype() == xpath_type_string)))
+ {
+ _type = ast_opt_compare_attribute;
+ }
}
bool is_posinv_expr() const
@@ -10147,6 +10261,7 @@ PUGI__NS_BEGIN
switch (_type)
{
case ast_func_position:
+ case ast_func_last:
return false;
case ast_string_constant:
@@ -11074,6 +11189,25 @@ PUGI__NS_BEGIN
return impl->root->eval_string(c, sd.stack);
}
+
+ PUGI__FN impl::xpath_ast_node* evaluate_node_set_prepare(xpath_query_impl* impl)
+ {
+ if (!impl) return 0;
+
+ if (impl->root->rettype() != xpath_type_node_set)
+ {
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ return 0;
+ #else
+ xpath_parse_result res;
+ res.error = "Expression does not evaluate to node set";
+
+ throw xpath_exception(res);
+ #endif
+ }
+
+ return impl->root;
+ }
PUGI__NS_END
namespace pugi
@@ -11575,22 +11709,9 @@ namespace pugi
PUGI__FN xpath_node_set xpath_query::evaluate_node_set(const xpath_node& n) const
{
- if (!_impl) return xpath_node_set();
-
- impl::xpath_ast_node* root = static_cast<impl::xpath_query_impl*>(_impl)->root;
-
- if (root->rettype() != xpath_type_node_set)
- {
- #ifdef PUGIXML_NO_EXCEPTIONS
- return xpath_node_set();
- #else
- xpath_parse_result res;
- res.error = "Expression does not evaluate to node set";
+ impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast<impl::xpath_query_impl*>(_impl));
+ if (!root) return xpath_node_set();
- throw xpath_exception(res);
- #endif
- }
-
impl::xpath_context c(n, 1, 1);
impl::xpath_stack_data sd;
@@ -11598,11 +11719,28 @@ namespace pugi
if (setjmp(sd.error_handler)) return xpath_node_set();
#endif
- impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack);
+ impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_all);
return xpath_node_set(r.begin(), r.end(), r.type());
}
+ PUGI__FN xpath_node xpath_query::evaluate_node(const xpath_node& n) const
+ {
+ impl::xpath_ast_node* root = impl::evaluate_node_set_prepare(static_cast<impl::xpath_query_impl*>(_impl));
+ if (!root) return xpath_node();
+
+ impl::xpath_context c(n, 1, 1);
+ impl::xpath_stack_data sd;
+
+ #ifdef PUGIXML_NO_EXCEPTIONS
+ if (setjmp(sd.error_handler)) return xpath_node();
+ #endif
+
+ impl::xpath_node_set_raw r = root->eval_node_set(c, sd.stack, impl::nodeset_eval_first);
+
+ return r.first();
+ }
+
PUGI__FN const xpath_parse_result& xpath_query::result() const
{
return _result;
@@ -11622,16 +11760,15 @@ namespace pugi
return !_impl;
}
- PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const
+ PUGI__FN xpath_node xml_node::select_node(const char_t* query, xpath_variable_set* variables) const
{
xpath_query q(query, variables);
- return select_single_node(q);
+ return select_node(q);
}
- PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const
+ PUGI__FN xpath_node xml_node::select_node(const xpath_query& query) const
{
- xpath_node_set s = query.evaluate_node_set(*this);
- return s.empty() ? xpath_node() : s.first();
+ return query.evaluate_node(*this);
}
PUGI__FN xpath_node_set xml_node::select_nodes(const char_t* query, xpath_variable_set* variables) const
@@ -11644,6 +11781,17 @@ namespace pugi
{
return query.evaluate_node_set(*this);
}
+
+ PUGI__FN xpath_node xml_node::select_single_node(const char_t* query, xpath_variable_set* variables) const
+ {
+ xpath_query q(query, variables);
+ return select_single_node(q);
+ }
+
+ PUGI__FN xpath_node xml_node::select_single_node(const xpath_query& query) const
+ {
+ return query.evaluate_node(*this);
+ }
}
#endif
diff --git a/src/pugixml.hpp b/src/pugixml.hpp
index 69b2cb2..e252e16 100644
--- a/src/pugixml.hpp
+++ b/src/pugixml.hpp
@@ -585,12 +585,17 @@ namespace pugi
#ifndef PUGIXML_NO_XPATH
// Select single node by evaluating XPath query. Returns first node from the resulting node set.
- xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const;
- xpath_node select_single_node(const xpath_query& query) const;
+ xpath_node select_node(const char_t* query, xpath_variable_set* variables = 0) const;
+ xpath_node select_node(const xpath_query& query) const;
// Select node set by evaluating XPath query
xpath_node_set select_nodes(const char_t* query, xpath_variable_set* variables = 0) const;
xpath_node_set select_nodes(const xpath_query& query) const;
+
+ // (deprecated: use select_node instead) Select single node by evaluating XPath query.
+ xpath_node select_single_node(const char_t* query, xpath_variable_set* variables = 0) const;
+ xpath_node select_single_node(const xpath_query& query) const;
+
#endif
// Print subtree using a writer object
@@ -1134,6 +1139,12 @@ namespace pugi
// If PUGIXML_NO_EXCEPTIONS is defined, returns empty node set instead.
xpath_node_set evaluate_node_set(const xpath_node& n) const;
+ // Evaluate expression as node set in the specified context.
+ // Return first node in document order, or empty node if node set is empty.
+ // If PUGIXML_NO_EXCEPTIONS is not defined, throws xpath_exception on type mismatch and std::bad_alloc on out of memory errors.
+ // If PUGIXML_NO_EXCEPTIONS is defined, returns empty node instead.
+ xpath_node evaluate_node(const xpath_node& n) const;
+
// Get parsing result (used to get compilation errors in PUGIXML_NO_EXCEPTIONS mode)
const xpath_parse_result& result() const;
diff --git a/tests/allocator.cpp b/tests/allocator.cpp
index 3641585..094d5e5 100644
--- a/tests/allocator.cpp
+++ b/tests/allocator.cpp
@@ -1,6 +1,7 @@
#include "allocator.hpp"
#include <string.h>
+#include <assert.h>
// Low-level allocation functions
#if defined(_WIN32) || defined(_WIN64)
@@ -97,6 +98,8 @@ void* memory_allocate(size_t size)
size_t memory_size(void* ptr)
{
+ assert(ptr);
+
size_t result;
memcpy(&result, static_cast<size_t*>(ptr) - 1, sizeof(size_t));
diff --git a/tests/main.cpp b/tests/main.cpp
index 3bcf9be..75b0108 100644
--- a/tests/main.cpp
+++ b/tests/main.cpp
@@ -34,6 +34,7 @@ static void* custom_allocate(size_t size)
else
{
void* ptr = memory_allocate(size);
+ assert(ptr);
g_memory_total_size += memory_size(ptr);
g_memory_total_count++;
diff --git a/tests/test_dom_traverse.cpp b/tests/test_dom_traverse.cpp
index c2437ab..e42846f 100644
--- a/tests/test_dom_traverse.cpp
+++ b/tests/test_dom_traverse.cpp
@@ -1043,3 +1043,19 @@ TEST_XML(dom_node_children_attributes, "<node1 attr1='value1' attr2='value2' /><
CHECK(r4.begin() == xml_attribute_iterator());
CHECK(r4.end() == xml_attribute_iterator());
}
+
+TEST_XML(dom_unspecified_bool_coverage, "<node attr='value'>text</node>")
+{
+ xml_node node = doc.first_child();
+
+ node(0);
+ node.first_attribute()(0);
+ node.text()(0);
+
+#ifndef PUGIXML_NO_XPATH
+ xpath_query q(STR("/node"));
+
+ q(0);
+ q.evaluate_node(doc)(0);
+#endif
+}
diff --git a/tests/test_write.cpp b/tests/test_write.cpp
index 0c20e26..98650ac 100644
--- a/tests/test_write.cpp
+++ b/tests/test_write.cpp
@@ -57,6 +57,29 @@ TEST_XML_FLAGS(write_comment, "<!--text-->", parse_comments | parse_fragment)
CHECK_NODE_EX(doc, STR("<!--text-->\n"), STR(""), 0);
}
+TEST(write_comment_invalid)
+{
+ xml_document doc;
+ xml_node child = doc.append_child(node_comment);
+
+ CHECK_NODE(doc, STR("<!---->"));
+
+ child.set_value(STR("-"));
+ CHECK_NODE(doc, STR("<!--- -->"));
+
+ child.set_value(STR("--"));
+ CHECK_NODE(doc, STR("<!--- - -->"));
+
+ child.set_value(STR("---"));
+ CHECK_NODE(doc, STR("<!--- - - -->"));
+
+ child.set_value(STR("-->"));
+ CHECK_NODE(doc, STR("<!--- ->-->"));
+
+ child.set_value(STR("-->-"));
+ CHECK_NODE(doc, STR("<!--- ->- -->"));
+}
+
TEST_XML_FLAGS(write_pi, "<?name value?>", parse_pi | parse_fragment)
{
CHECK_NODE(doc, STR("<?name value?>"));
diff --git a/tests/test_xpath.cpp b/tests/test_xpath.cpp
index f7da258..2143376 100644
--- a/tests/test_xpath.cpp
+++ b/tests/test_xpath.cpp
@@ -122,6 +122,28 @@ TEST_XML(xpath_sort_attributes, "<node/>")
xpath_node_set_tester(reverse_sorted, "reverse sorted order failed") % 5 % 4 % 3;
}
+TEST(xpath_sort_random_medium)
+{
+ xml_document doc;
+ load_document_copy(doc, STR("<node>")
+ STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
+ STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
+ STR("<child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2><child1 attr1='value1' attr2='value2'/><child2 attr1='value1'>test</child2>")
+ STR("</node>"));
+
+ xpath_node_set ns = doc.select_nodes(STR("//node() | //@*"));
+
+ std::vector<xpath_node> nsv(ns.begin(), ns.end());
+ std::random_shuffle(nsv.begin(), nsv.end());
+
+ xpath_node_set copy(&nsv[0], &nsv[0] + nsv.size());
+ copy.sort();
+
+ xpath_node_set_tester tester(copy, "sorted order failed");
+
+ for (unsigned int i = 2; i < 39; ++i) tester % i;
+}
+
TEST(xpath_sort_random_large)
{
xml_document doc;
diff --git a/tests/test_xpath_api.cpp b/tests/test_xpath_api.cpp
index d831712..270f6aa 100644
--- a/tests/test_xpath_api.cpp
+++ b/tests/test_xpath_api.cpp
@@ -19,22 +19,22 @@ TEST_XML(xpath_api_select_nodes, "<node><head/><foo/><foo/><tail/></node>")
xpath_node_set_tester(ns2, "ns2") % 4 % 5;
}
-TEST_XML(xpath_api_select_single_node, "<node><head/><foo id='1'/><foo/><tail/></node>")
+TEST_XML(xpath_api_select_node, "<node><head/><foo id='1'/><foo/><tail/></node>")
{
- xpath_node n1 = doc.select_single_node(STR("node/foo"));
+ xpath_node n1 = doc.select_node(STR("node/foo"));
xpath_query q(STR("node/foo"));
- xpath_node n2 = doc.select_single_node(q);
+ xpath_node n2 = doc.select_node(q);
CHECK(n1.node().attribute(STR("id")).as_int() == 1);
CHECK(n2.node().attribute(STR("id")).as_int() == 1);
- xpath_node n3 = doc.select_single_node(STR("node/bar"));
+ xpath_node n3 = doc.select_node(STR("node/bar"));
CHECK(!n3);
- xpath_node n4 = doc.select_single_node(STR("node/head/following-sibling::foo"));
- xpath_node n5 = doc.select_single_node(STR("node/tail/preceding-sibling::foo"));
+ xpath_node n4 = doc.select_node(STR("node/head/following-sibling::foo"));
+ xpath_node n5 = doc.select_node(STR("node/tail/preceding-sibling::foo"));
CHECK(n4.node().attribute(STR("id")).as_int() == 1);
CHECK(n5.node().attribute(STR("id")).as_int() == 1);
@@ -42,20 +42,20 @@ TEST_XML(xpath_api_select_single_node, "<node><head/><foo id='1'/><foo/><tail/><
TEST_XML(xpath_api_node_bool_ops, "<node attr='value'/>")
{
- generic_bool_ops_test(doc.select_single_node(STR("node")));
- generic_bool_ops_test(doc.select_single_node(STR("node/@attr")));
+ generic_bool_ops_test(doc.select_node(STR("node")));
+ generic_bool_ops_test(doc.select_node(STR("node/@attr")));
}
TEST_XML(xpath_api_node_eq_ops, "<node attr='value'/>")
{
- generic_eq_ops_test(doc.select_single_node(STR("node")), doc.select_single_node(STR("node/@attr")));
+ generic_eq_ops_test(doc.select_node(STR("node")), doc.select_node(STR("node/@attr")));
}
TEST_XML(xpath_api_node_accessors, "<node attr='value'/>")
{
xpath_node null;
- xpath_node node = doc.select_single_node(STR("node"));
- xpath_node attr = doc.select_single_node(STR("node/@attr"));
+ xpath_node node = doc.select_node(STR("node"));
+ xpath_node attr = doc.select_node(STR("node/@attr"));
CHECK(!null.node());
CHECK(!null.attribute());
@@ -154,6 +154,9 @@ TEST_XML(xpath_api_evaluate, "<node attr='3'/>")
xpath_node_set ns = q.evaluate_node_set(doc);
CHECK(ns.size() == 1 && ns[0].attribute() == doc.child(STR("node")).attribute(STR("attr")));
+
+ xpath_node nr = q.evaluate_node(doc);
+ CHECK(nr.attribute() == doc.child(STR("node")).attribute(STR("attr")));
}
TEST_XML(xpath_api_evaluate_attr, "<node attr='3'/>")
@@ -173,6 +176,9 @@ TEST_XML(xpath_api_evaluate_attr, "<node attr='3'/>")
xpath_node_set ns = q.evaluate_node_set(n);
CHECK(ns.size() == 1 && ns[0] == n);
+
+ xpath_node nr = q.evaluate_node(n);
+ CHECK(nr == n);
}
#ifdef PUGIXML_NO_EXCEPTIONS
@@ -190,18 +196,20 @@ TEST_XML(xpath_api_evaluate_fail, "<node attr='3'/>")
#endif
CHECK(q.evaluate_node_set(doc).empty());
+
+ CHECK(!q.evaluate_node(doc));
}
#endif
TEST(xpath_api_evaluate_node_set_fail)
{
+ xpath_query q(STR("1"));
+
#ifdef PUGIXML_NO_EXCEPTIONS
- CHECK_XPATH_NODESET(xml_node(), STR("1"));
+ CHECK(q.evaluate_node_set(xml_node()).empty());
#else
try
{
- xpath_query q(STR("1"));
-
q.evaluate_node_set(xml_node());
CHECK_FORCE_FAIL("Expected exception");
@@ -212,6 +220,25 @@ TEST(xpath_api_evaluate_node_set_fail)
#endif
}
+TEST(xpath_api_evaluate_node_fail)
+{
+ xpath_query q(STR("1"));
+
+#ifdef PUGIXML_NO_EXCEPTIONS
+ CHECK(!q.evaluate_node(xml_node()));
+#else
+ try
+ {
+ q.evaluate_node(xml_node());
+
+ CHECK_FORCE_FAIL("Expected exception");
+ }
+ catch (const xpath_exception&)
+ {
+ }
+#endif
+}
+
TEST(xpath_api_evaluate_string)
{
xpath_query q(STR("\"0123456789\""));
@@ -384,4 +411,14 @@ TEST_XML(xpath_api_node_set_assign_out_of_memory_preserve, "<node><a/><b/></node
}
#endif
+TEST_XML(xpath_api_deprecated_select_single_node, "<node><head/><foo id='1'/><foo/><tail/></node>")
+{
+ xpath_node n1 = doc.select_single_node(STR("node/foo"));
+
+ xpath_query q(STR("node/foo"));
+ xpath_node n2 = doc.select_single_node(q);
+
+ CHECK(n1.node().attribute(STR("id")).as_int() == 1);
+ CHECK(n2.node().attribute(STR("id")).as_int() == 1);
+}
#endif
diff --git a/tests/test_xpath_functions.cpp b/tests/test_xpath_functions.cpp
index f5d9e53..da820ef 100644
--- a/tests/test_xpath_functions.cpp
+++ b/tests/test_xpath_functions.cpp
@@ -114,7 +114,7 @@ TEST(xpath_number_ceiling)
CHECK_XPATH_STRING(c, STR("string(1 div ceiling(0))"), STR("Infinity"));
// ceiling with argument in range (-1, -0] should result in minus zero
-#if !(defined(__APPLE__) && defined(__MACH__)) // MacOS X gcc 4.0.1 implements ceil incorrectly (ceil never returns -0)
+#if !(defined(__APPLE__) && defined(__MACH__)) && !defined(__CLR_VER) // MacOS X gcc 4.0.1 and x64 CLR implement ceil incorrectly (ceil never returns -0)
CHECK_XPATH_STRING(c, STR("string(1 div ceiling(-0))"), STR("-Infinity"));
CHECK_XPATH_STRING(c, STR("string(1 div ceiling(-0.1))"), STR("-Infinity"));
#endif
@@ -145,7 +145,7 @@ TEST(xpath_number_round)
// round with argument in range [-0.5, -0] should result in minus zero
CHECK_XPATH_STRING(c, STR("string(1 div round(0))"), STR("Infinity"));
-#if !(defined(__APPLE__) && defined(__MACH__)) // MacOS X gcc 4.0.1 implements ceil incorrectly (ceil never returns -0)
+#if !(defined(__APPLE__) && defined(__MACH__)) && !defined(__CLR_VER) // MacOS X gcc 4.0.1 and x64 CLR implement ceil incorrectly (ceil never returns -0)
CHECK_XPATH_STRING(c, STR("string(1 div round(-0.5))"), STR("-Infinity"));
CHECK_XPATH_STRING(c, STR("string(1 div round(-0))"), STR("-Infinity"));
CHECK_XPATH_STRING(c, STR("string(1 div round(-0.1))"), STR("-Infinity"));
diff --git a/tests/test_xpath_paths.cpp b/tests/test_xpath_paths.cpp
index c18acd2..f1d52ad 100644
--- a/tests/test_xpath_paths.cpp
+++ b/tests/test_xpath_paths.cpp
@@ -531,6 +531,27 @@ TEST_XML(xpath_paths_descendant_optimize, "<node><para><para/><para/><para><para
CHECK_XPATH_NODESET(doc, STR("/descendant-or-self::node()[3]/child::para")) % 4 % 5 % 6;
}
+TEST_XML(xpath_paths_descendant_optimize_axes, "<node><para><para/><para/><para><para/></para></para><para/></node>")
+{
+ CHECK_XPATH_NODESET(doc, STR("//.")) % 1 % 2 % 3 % 4 % 5 % 6 % 7 % 8;
+ CHECK_XPATH_NODESET(doc, STR("//descendant::*")) % 2 % 3 % 4 % 5 % 6 % 7 % 8;
+ CHECK_XPATH_NODESET(doc, STR("//descendant-or-self::*")) % 2 % 3 % 4 % 5 % 6 % 7 % 8;
+
+ CHECK_XPATH_NODESET(doc, STR("//..")) % 1 % 2 % 3 % 6;
+ CHECK_XPATH_NODESET(doc, STR("//ancestor::*")) % 2 % 3 % 6;
+ CHECK_XPATH_NODESET(doc, STR("//ancestor-or-self::*")) % 2 % 3 % 4 % 5 % 6 % 7 % 8;
+ CHECK_XPATH_NODESET(doc, STR("//preceding-sibling::*")) % 3 % 4 % 5;
+ CHECK_XPATH_NODESET(doc, STR("//following-sibling::*")) % 5 % 6 % 8;
+ CHECK_XPATH_NODESET(doc, STR("//preceding::*")) % 3 % 4 % 5 % 6 % 7;
+ CHECK_XPATH_NODESET(doc, STR("//following::*")) % 5 % 6 % 7 % 8;
+}
+
+TEST_XML(xpath_paths_descendant_optimize_last, "<node><para><para/><para/><para><para/></para></para><para/></node>")
+{
+ CHECK_XPATH_NODESET(doc, STR("//para[last()]")) % 6 % 7 % 8;
+ CHECK_XPATH_NODESET(doc, STR("//para[last() = 1]")) % 7;
+}
+
TEST_XML(xpath_paths_precision, "<node><para/><para/><para/><para/><para/></node>")
{
CHECK_XPATH_NODESET(doc, STR("//para[1]")) % 3;
@@ -555,4 +576,67 @@ TEST_XML(xpath_paths_unsorted_child, "<node><foo><bar/></foo><node><foo><bar/></
CHECK(ns[2] == nss[1]);
}
+TEST_XML(xpath_paths_optimize_compare_attribute, "<node id='1' /><node id='2' /><node xmlns='3' />")
+{
+ CHECK_XPATH_NODESET(doc, STR("node[@id = '1']")) % 2;
+ CHECK_XPATH_NODESET(doc, STR("node[@id = '2']")) % 4;
+ CHECK_XPATH_NODESET(doc, STR("node[@id = 2]")) % 4;
+ CHECK_XPATH_NODESET(doc, STR("node[@id[. > 3] = '2']"));
+ CHECK_XPATH_NODESET(doc, STR("node['1' = @id]")) % 2;
+
+ xpath_variable_set set;
+ set.set(STR("var1"), STR("2"));
+ set.set(STR("var2"), 2.0);
+
+ CHECK_XPATH_NODESET_VAR(doc, STR("node[@id = $var1]"), &set) % 4;
+ CHECK_XPATH_NODESET_VAR(doc, STR("node[@id = $var2]"), &set) % 4;
+
+ CHECK_XPATH_NODESET(doc, STR("node[@xmlns = '3']"));
+}
+
+TEST_XML(xpath_paths_optimize_step_once, "<node><para1><para2/><para3/><para4><para5 attr5=''/></para4></para1><para6/></node>")
+{
+ CHECK_XPATH_BOOLEAN(doc, STR("node//para2/following::*"), true);
+ CHECK_XPATH_BOOLEAN(doc, STR("node//para6/following::*"), false);
+
+ CHECK_XPATH_STRING(doc, STR("name(node//para2/following::*)"), STR("para3"));
+ CHECK_XPATH_STRING(doc, STR("name(node//para6/following::*)"), STR(""));
+
+ CHECK_XPATH_BOOLEAN(doc, STR("node//para1/preceding::*"), false);
+ CHECK_XPATH_BOOLEAN(doc, STR("node//para6/preceding::*"), true);
+
+ CHECK_XPATH_STRING(doc, STR("name(node//para1/preceding::*)"), STR(""));
+ CHECK_XPATH_STRING(doc, STR("name(node//para6/preceding::*)"), STR("para1"));
+
+ CHECK_XPATH_BOOLEAN(doc, STR("node//para6/preceding::para4"), true);
+
+ CHECK_XPATH_BOOLEAN(doc, STR("//@attr5/ancestor-or-self::*"), true);
+ CHECK_XPATH_BOOLEAN(doc, STR("//@attr5/ancestor::*"), true);
+
+ CHECK_XPATH_BOOLEAN(doc, STR("//@attr5/following::para6"), true);
+ CHECK_XPATH_STRING(doc, STR("name(//@attr5/following::para6)"), STR("para6"));
+}
+
+TEST_XML(xpath_paths_null_nodeset_entries, "<node attr='value'/>")
+{
+ xpath_node nodes[] =
+ {
+ xpath_node(doc.first_child()),
+ xpath_node(xml_node()),
+ xpath_node(doc.first_child().first_attribute(), doc.first_child()),
+ xpath_node(xml_attribute(), doc.first_child()),
+ xpath_node(xml_attribute(), xml_node()),
+ };
+
+ xpath_node_set ns(nodes, nodes + sizeof(nodes) / sizeof(nodes[0]));
+
+ xpath_variable_set vars;
+ vars.set(STR("x"), ns);
+
+ xpath_node_set rs = xpath_query("$x/.", &vars).evaluate_node_set(xml_node());
+
+ CHECK(rs.size() == 2);
+ CHECK(rs[0] == nodes[0]);
+ CHECK(rs[1] == nodes[2]);
+}
#endif
diff --git a/tests/test_xpath_variables.cpp b/tests/test_xpath_variables.cpp
index 785a504..70bb4ea 100644
--- a/tests/test_xpath_variables.cpp
+++ b/tests/test_xpath_variables.cpp
@@ -281,7 +281,7 @@ TEST_XML(xpath_variables_select, "<node attr='1'/><node attr='2'/>")
xpath_node_set ns = doc.select_nodes(STR("node[@attr=$one+1]"), &set);
CHECK(ns.size() == 1 && ns[0].node() == doc.last_child());
- xpath_node n = doc.select_single_node(STR("node[@attr=$one+1]"), &set);
+ xpath_node n = doc.select_node(STR("node[@attr=$one+1]"), &set);
CHECK(n == ns[0]);
}
diff --git a/tests/test_xpath_xalan_3.cpp b/tests/test_xpath_xalan_3.cpp
index 54b8a62..d2df3e5 100644
--- a/tests/test_xpath_xalan_3.cpp
+++ b/tests/test_xpath_xalan_3.cpp
@@ -4,7 +4,7 @@
TEST_XML(xpath_xalan_axes_1, "<far-north><north-north-west1/><north-north-west2/><north><near-north><far-west/><west/><near-west/><center center-attr-1='c1' center-attr-2='c2' center-attr-3='c3'><near-south-west/><near-south><south><far-south/></south></near-south><near-south-east/></center><near-east/><east/><far-east/></near-north></north><north-north-east1/><north-north-east2/></far-north>")
{
- xml_node center = doc.select_single_node(STR("//center")).node();
+ xml_node center = doc.select_node(STR("//center")).node();
CHECK_XPATH_NODESET(center, STR("self::*[near-south]")) % 10;
CHECK_XPATH_NODESET(center, STR("self::*[@center-attr-2]")) % 10;
@@ -35,7 +35,7 @@ TEST_XML(xpath_xalan_axes_1, "<far-north><north-north-west1/><north-north-west2/
TEST_XML_FLAGS(xpath_xalan_axes_2, "<far-north> Level-1<north-north-west1/><north-north-west2/><!-- Comment-2 --> Level-2<?a-pi pi-2?><north><!-- Comment-3 --> Level-3<?a-pi pi-3?><near-north><far-west/><west/><near-west/><!-- Comment-4 --> Level-4<?a-pi pi-4?><center center-attr-1='c1' center-attr-2='c2' center-attr-3='c3'><near-south-west/><!--Comment-5--> Level-5<?a-pi pi-5?><near-south><!--Comment-6--> Level-6<?a-pi pi-6?><south attr1='First' attr2='Last'> <far-south/></south></near-south><near-south-east/></center><near-east/><east/><far-east/></near-north></north><north-north-east1/><north-north-east2/></far-north>", parse_default | parse_comments | parse_pi)
{
- xml_node center = doc.select_single_node(STR("//center")).node();
+ xml_node center = doc.select_node(STR("//center")).node();
CHECK_XPATH_NODESET(center, STR("@*")) % 21 % 22 % 23;
CHECK_XPATH_NODESET(center, STR("@*/child::*"));
@@ -65,7 +65,7 @@ TEST_XML_FLAGS(xpath_xalan_axes_2, "<far-north> Level-1<north-north-west1/><nort
TEST_XML(xpath_xalan_axes_3, "<far-north><north><near-north><far-west/><west/><near-west/><center><near-south><south><far-south/></south></near-south></center><near-east/><east/><far-east/></near-north></north></far-north>")
{
- xml_node center = doc.select_single_node(STR("//center")).node();
+ xml_node center = doc.select_node(STR("//center")).node();
CHECK_XPATH_NODESET(center, STR("ancestor-or-self::*")) % 8 % 4 % 3 % 2;
CHECK_XPATH_NODESET(center, STR("ancestor::*[3]")) % 2;
@@ -99,7 +99,7 @@ TEST_XML(xpath_xalan_axes_3, "<far-north><north><near-north><far-west/><west/><n
TEST_XML(xpath_xalan_axes_4, "<far-north><north><near-north><far-west/><west/><near-west/><center><near-south><south><far-south/></south></near-south></center><near-east/><east/><far-east/></near-north></north></far-north>")
{
- xml_node north = doc.select_single_node(STR("//north")).node();
+ xml_node north = doc.select_node(STR("//north")).node();
CHECK_XPATH_STRING(north, STR("name(/descendant-or-self::north)"), STR("north"));
CHECK_XPATH_STRING(north, STR("name(/descendant::near-north)"), STR("near-north"));
@@ -166,7 +166,7 @@ TEST_XML(xpath_xalan_axes_6, "<doc><T>Test for source tree depth</T><a><T>A</T><
TEST_XML(xpath_xalan_axes_7, "<far-north><north><near-north><far-west/><west/><near-west/><center center-attr-1='c1' center-attr-2='c2' center-attr-3='c3'><near-south><south><far-south/></south></near-south></center><near-east/><east/><far-east/></near-north></north></far-north>")
{
- xml_node center = doc.select_single_node(STR("//center")).node();
+ xml_node center = doc.select_node(STR("//center")).node();
CHECK_XPATH_NODESET(center, STR("attribute::*[2]")) % 10;
CHECK_XPATH_NODESET(center, STR("@*")) % 9 % 10 % 11;
@@ -177,7 +177,7 @@ TEST_XML(xpath_xalan_axes_7, "<far-north><north><near-north><far-west/><west/><n
TEST_XML(xpath_xalan_axes_8, "<far-north><north><near-north><far-west/><west/><near-west/><center center-attr-1='c1' center-attr-2='c2' center-attr-3='c3'><near-south-east/><near-south><south><far-south/></south></near-south><near-south-west/></center><near-east/><east/><far-east/></near-north></north></far-north>")
{
- xml_node near_north = doc.select_single_node(STR("//near-north")).node();
+ xml_node near_north = doc.select_node(STR("//near-north")).node();
CHECK_XPATH_NODESET(near_north, STR("center//child::*")) % 12 % 13 % 14 % 15 % 16;
CHECK_XPATH_NODESET(near_north, STR("center//descendant::*")) % 12 % 13 % 14 % 15 % 16;
@@ -188,7 +188,7 @@ TEST_XML(xpath_xalan_axes_8, "<far-north><north><near-north><far-west/><west/><n
TEST_XML(xpath_xalan_axes_9, "<doc><foo att1='c'><foo att1='b'><foo att1='a'><baz/></foo></foo></foo><bar/></doc>")
{
- xml_node baz = doc.select_single_node(STR("//baz")).node();
+ xml_node baz = doc.select_node(STR("//baz")).node();
CHECK_XPATH_NODESET(baz, STR("ancestor-or-self::*[@att1][1]/@att1")) % 8;
CHECK_XPATH_NODESET(baz, STR("(ancestor-or-self::*)[@att1][1]/@att1")) % 4;
@@ -243,7 +243,7 @@ TEST_XML_FLAGS(xpath_xalan_axes_12, "<far-north><north>north-text1<near-north><f
TEST_XML(xpath_xalan_axes_13, "<doc att1='e'><foo att1='d'><foo att1='c'><foo att1='b'><baz att1='a'/></foo></foo></foo></doc>")
{
xml_node d = doc.child(STR("doc"));
- xml_node baz = doc.select_single_node(STR("//baz")).node();
+ xml_node baz = doc.select_node(STR("//baz")).node();
CHECK_XPATH_NUMBER(d, STR("count(descendant-or-self::*/@att1)"), 5);
CHECK_XPATH_NODESET(d, STR("descendant-or-self::*/@att1[last()]")) % 3 % 5 % 7 % 9 % 11;