From e7d2540c1a1be4c4f69d64771cfddfaf0fc6556a Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Tue, 14 Oct 2014 04:11:17 +0000 Subject: Adjust comment output to avoid malformed documents. Comment value can not contain the string "--" or end with "-". Since comments do not support escaping, we're handling this by adding an extra space after the first "-". A string of "-" thus turns into "- - - ...". git-svn-id: https://pugixml.googlecode.com/svn/trunk@1058 99668b35-9821-0410-8761-19e4c4f06640 --- src/pugixml.cpp | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 93b648b..7db62d0 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -3424,6 +3424,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(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"); @@ -3523,22 +3548,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()); @@ -3548,6 +3566,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'); -- cgit v1.2.3 From 87d4f03187c66181c47696f879542b5bf911e8c4 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Tue, 14 Oct 2014 04:11:26 +0000 Subject: tests: Add a test for printing comments that contain -- git-svn-id: https://pugixml.googlecode.com/svn/trunk@1059 99668b35-9821-0410-8761-19e4c4f06640 --- tests/test_write.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) 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, "", parse_comments | parse_fragment) CHECK_NODE_EX(doc, STR("\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, "", parse_pi | parse_fragment) { CHECK_NODE(doc, STR("")); -- cgit v1.2.3 From 883031fb45cf0f86cd36b20ad4762da58dd6126c Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Wed, 15 Oct 2014 06:05:49 +0000 Subject: XPath: Fix optimization bug with //name[last()] The actual condition for the optimization is invariance from context list -- this includes both position() and last(). Instead of splitting the posinv concept just include last() into non-posinv expressions - this requires sorting for boolean predicates that depend on last() and do not depend on position(). These cases should be very rare. git-svn-id: https://pugixml.googlecode.com/svn/trunk@1060 99668b35-9821-0410-8761-19e4c4f06640 --- src/pugixml.cpp | 1 + tests/test_xpath_paths.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 7db62d0..484f34b 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -9621,6 +9621,7 @@ PUGI__NS_BEGIN switch (_type) { case ast_func_position: + case ast_func_last: return false; case ast_string_constant: diff --git a/tests/test_xpath_paths.cpp b/tests/test_xpath_paths.cpp index c18acd2..43096bc 100644 --- a/tests/test_xpath_paths.cpp +++ b/tests/test_xpath_paths.cpp @@ -531,6 +531,12 @@ TEST_XML(xpath_paths_descendant_optimize, "") +{ + CHECK_XPATH_NODESET(doc, STR("//para[last()]")) % 6 % 7 % 8; + CHECK_XPATH_NODESET(doc, STR("//para[last() = 1]")) % 7; +} + TEST_XML(xpath_paths_precision, "") { CHECK_XPATH_NODESET(doc, STR("//para[1]")) % 3; -- cgit v1.2.3 From 5da51dff270f430701b26428c9422f21e0ea4c9c Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 16 Oct 2014 03:46:42 +0000 Subject: XPath: Optimize attribute axis lookup When looking for an attribute by name, finding the first attribute means we can stop looking since attribute names are unique. This makes some queries faster by 40%. Another very common pattern in XPath queries is finding an attribute with a specified value using a predicate (@name = 'value'). While we perform an optimal amount of traversal in that case, there is a substantial overhead with evaluating the nodes, saving and restoring the stack state, pushing the attribute node into a set, etc. Detecting this pattern allows us to use optimized code, resulting in up to 2x speedup for some queries. git-svn-id: https://pugixml.googlecode.com/svn/trunk@1061 99668b35-9821-0410-8761-19e4c4f06640 --- src/pugixml.cpp | 57 ++++++++++++++++++++++++++++++++++------------ tests/test_xpath_paths.cpp | 18 +++++++++++++++ 2 files changed, 60 insertions(+), 15 deletions(-) diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 484f34b..71c3f5d 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -7473,6 +7473,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) @@ -8217,7 +8222,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() @@ -8230,7 +8234,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 @@ -8296,7 +8303,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; @@ -8498,36 +8505,38 @@ 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); 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); break; default: ; } + + return false; } void step_push(xpath_node_set_raw& ns, const xml_node n, xpath_allocator* alloc) @@ -8589,7 +8598,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)) + break; // step_push returns true if it found a unique attribute break; } @@ -9007,6 +9017,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()); @@ -9412,7 +9431,7 @@ PUGI__NS_BEGIN return s; } - case ast_func_translate_table: + case ast_opt_translate_table: { xpath_string s = _left->eval_string(c, stack); @@ -9610,10 +9629,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 diff --git a/tests/test_xpath_paths.cpp b/tests/test_xpath_paths.cpp index 43096bc..4528acd 100644 --- a/tests/test_xpath_paths.cpp +++ b/tests/test_xpath_paths.cpp @@ -561,4 +561,22 @@ TEST_XML(xpath_paths_unsorted_child, "") +{ + 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']")); +} + #endif -- cgit v1.2.3 From 45e0c726f05d8a658600b7ca74db42f252f126e9 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Thu, 16 Oct 2014 06:41:04 +0000 Subject: tests: Disable tests that rely on ceil() on CLR CLR x64 JIT does not implement ceil() properly (ceil(-0.1) returns positive zero instead of negative zero). Disable the relevant portions of tests so that everything else is green... git-svn-id: https://pugixml.googlecode.com/svn/trunk@1062 99668b35-9821-0410-8761-19e4c4f06640 --- tests/test_xpath_functions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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")); -- cgit v1.2.3 From 72ec01c5f6d23405f30614d63fafa048279ca13d Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Sat, 18 Oct 2014 15:28:02 +0000 Subject: XPath: Extend the descendant-or-self optimization Use descendant-or-self::node() transformation for self, descendant and descendant-or-self axis. Self axis should be semi-frequent; descendant axes should not really be used with // but if they ever are the complexity of the step becomes quadratic so it's better to optimize this if possible. git-svn-id: https://pugixml.googlecode.com/svn/trunk@1063 99668b35-9821-0410-8761-19e4c4f06640 --- src/pugixml.cpp | 9 +++++++-- tests/test_xpath_paths.cpp | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 71c3f5d..1d9dcfe 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -9613,12 +9613,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; } diff --git a/tests/test_xpath_paths.cpp b/tests/test_xpath_paths.cpp index 4528acd..b6f53c7 100644 --- a/tests/test_xpath_paths.cpp +++ b/tests/test_xpath_paths.cpp @@ -531,6 +531,21 @@ TEST_XML(xpath_paths_descendant_optimize, "") +{ + 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, "") { CHECK_XPATH_NODESET(doc, STR("//para[last()]")) % 6 % 7 % 8; -- cgit v1.2.3 From f6635588758ed1b650be22903c2e2e81273e05c5 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Sun, 19 Oct 2014 07:33:42 +0000 Subject: XPath: Introduce xpath_query::evaluate_node This method is equivalent to xml_node::select_single_node. This makes select_single_node faster in certain cases by avoiding an allocation and - more importantly - paves the way for future step optimizations. git-svn-id: https://pugixml.googlecode.com/svn/trunk@1064 99668b35-9821-0410-8761-19e4c4f06640 --- src/pugixml.cpp | 56 +++++++++++++++++++++++++++++++++--------------- src/pugixml.hpp | 6 ++++++ tests/test_xpath_api.cpp | 33 +++++++++++++++++++++++++--- 3 files changed, 75 insertions(+), 20 deletions(-) diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 1d9dcfe..6f230dd 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -10581,6 +10581,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 @@ -11082,22 +11101,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)->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)); + if (!root) return xpath_node_set(); - throw xpath_exception(res); - #endif - } - impl::xpath_context c(n, 1, 1); impl::xpath_stack_data sd; @@ -11110,6 +11116,23 @@ namespace pugi 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)); + 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); + + return r.first(); + } + PUGI__FN const xpath_parse_result& xpath_query::result() const { return _result; @@ -11137,8 +11160,7 @@ namespace pugi PUGI__FN xpath_node xml_node::select_single_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 diff --git a/src/pugixml.hpp b/src/pugixml.hpp index 69b2cb2..2947bf4 100644 --- a/src/pugixml.hpp +++ b/src/pugixml.hpp @@ -1134,6 +1134,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/test_xpath_api.cpp b/tests/test_xpath_api.cpp index d831712..8ec5694 100644 --- a/tests/test_xpath_api.cpp +++ b/tests/test_xpath_api.cpp @@ -154,6 +154,9 @@ TEST_XML(xpath_api_evaluate, "") 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, "") @@ -173,6 +176,9 @@ TEST_XML(xpath_api_evaluate_attr, "") 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, "") #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\"")); -- cgit v1.2.3 From c3eb9c92a86b041b40e70afb32ea66d4369c892b Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Sun, 19 Oct 2014 07:33:51 +0000 Subject: XPath: Rename xml_node::select_single_node to ::select_node select_node is shorter and mistyping nodes as node or vice versa should not lead to any issues since return types are substantially different. select_single_node method still works and will be deprecated with an attribute and removed at some point. git-svn-id: https://pugixml.googlecode.com/svn/trunk@1065 99668b35-9821-0410-8761-19e4c4f06640 --- src/pugixml.cpp | 17 ++++++++++++++--- src/pugixml.hpp | 9 +++++++-- tests/test_xpath_api.cpp | 32 +++++++++++++++++++++----------- tests/test_xpath_variables.cpp | 2 +- tests/test_xpath_xalan_3.cpp | 16 ++++++++-------- 5 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 6f230dd..99cdfc0 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -11152,13 +11152,13 @@ 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 { return query.evaluate_node(*this); } @@ -11173,6 +11173,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 2947bf4..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 diff --git a/tests/test_xpath_api.cpp b/tests/test_xpath_api.cpp index 8ec5694..270f6aa 100644 --- a/tests/test_xpath_api.cpp +++ b/tests/test_xpath_api.cpp @@ -19,22 +19,22 @@ TEST_XML(xpath_api_select_nodes, "") xpath_node_set_tester(ns2, "ns2") % 4 % 5; } -TEST_XML(xpath_api_select_single_node, "") +TEST_XML(xpath_api_select_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, "< TEST_XML(xpath_api_node_bool_ops, "") { - 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, "") { - 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, "") { 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()); @@ -411,4 +411,14 @@ TEST_XML(xpath_api_node_set_assign_out_of_memory_preserve, "") +{ + 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_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, "") 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, "
") { - 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, " Level-1 Level-2 Level-3 Level-4
Level-5 Level-6
", 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, " Level-1
") { - 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, "
") { - 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, "Test for source tree depth
A< TEST_XML(xpath_xalan_axes_7, "
") { - 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, "
") { - 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, "") { - 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, "north-text1") { 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; -- cgit v1.2.3 From 2d4e549049a2ed593f5e295b95371c02540d41b1 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Sun, 19 Oct 2014 07:34:02 +0000 Subject: docs: Update XPath documentation Add documentation for xpath_query::evaluate_node and change select_single_node to select_node in documentation and samples. git-svn-id: https://pugixml.googlecode.com/svn/trunk@1066 99668b35-9821-0410-8761-19e4c4f06640 --- docs/manual.qbk | 26 ++++++++++++++------------ docs/samples/xpath_select.cpp | 2 +- docs/samples/xpath_variables.cpp | 2 +- 3 files changed, 16 insertions(+), 14 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 '''$name'''; 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 '''$name'''; 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: "; -- cgit v1.2.3 From 1b8b87904b0618f853345619e7ee2656cab80113 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Mon, 20 Oct 2014 01:00:48 +0000 Subject: XPath: Introduce _first/_any set evaluation modes Sometimes when evaluating the node set we don't need the entire set and only need the first element in docorder or any element. In the absence of iterator support we can still use this information to short-circuit traversals. This does not have any effect on straightforward node collection queries, but frequently improves performance of complex queries with predicates etc. XMark benchmark gets 15x faster with some queries enjoying 100x speedup on 10 Mb dataset due to a significant complexity improvement. git-svn-id: https://pugixml.googlecode.com/svn/trunk@1067 99668b35-9821-0410-8761-19e4c4f06640 --- src/pugixml.cpp | 190 +++++++++++++++++++++++++++++---------------- tests/test_xpath.cpp | 22 ++++++ tests/test_xpath_paths.cpp | 22 ++++++ 3 files changed, 167 insertions(+), 67 deletions(-) diff --git a/src/pugixml.cpp b/src/pugixml.cpp index 99cdfc0..e9ab09d 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -8270,6 +8270,13 @@ PUGI__NS_BEGIN nodetest_all_in_namespace }; + enum nodeset_eval_t + { + nodeset_eval_all, + nodeset_eval_any, + nodeset_eval_first + }; + template struct axis_to_type { static const axis_t axis; @@ -8334,8 +8341,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) @@ -8363,7 +8370,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) { @@ -8380,7 +8387,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) { @@ -8408,8 +8415,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) { @@ -8433,7 +8440,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) { @@ -8449,7 +8456,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) @@ -8469,7 +8476,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); @@ -8524,12 +8531,18 @@ PUGI__NS_BEGIN case nodetest_type_node: case nodetest_all: 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) && is_xpath_attribute(name)) + { ns.push_back(xpath_node(a, parent), alloc); + return true; + } break; default: @@ -8539,57 +8552,80 @@ PUGI__NS_BEGIN 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 void step_fill(xpath_node_set_raw& ns, const xml_node n, xpath_allocator* alloc, T) + template void step_fill(xpath_node_set_raw& ns, const xml_node n, xpath_allocator* alloc, bool once, T) { const axis_t axis = T::axis; @@ -8598,8 +8634,8 @@ PUGI__NS_BEGIN case axis_attribute: { for (xml_attribute a = n.first_attribute(); a; a = a.next_attribute()) - if (step_push(ns, a, n, alloc)) - break; // step_push returns true if it found a unique attribute + if (step_push(ns, a, n, alloc) & once) + return; break; } @@ -8607,7 +8643,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; } @@ -8616,13 +8653,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(); @@ -8643,7 +8682,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; } @@ -8651,7 +8691,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; } @@ -8666,7 +8707,8 @@ PUGI__NS_BEGIN for (;;) { - step_push(ns, cur, alloc); + if (step_push(ns, cur, alloc) & once) + return; if (cur.first_child()) cur = cur.first_child(); @@ -8698,7 +8740,8 @@ PUGI__NS_BEGIN 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(); @@ -8709,7 +8752,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()); @@ -8727,13 +8772,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(); } @@ -8760,7 +8807,7 @@ PUGI__NS_BEGIN } } - template void step_fill(xpath_node_set_raw& ns, const xml_attribute a, const xml_node p, xpath_allocator* alloc, T v) + template 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; @@ -8770,13 +8817,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(); } @@ -8811,7 +8860,8 @@ PUGI__NS_BEGIN if (!cur) break; } - step_push(ns, cur, alloc); + if (step_push(ns, cur, alloc) & once) + return; } break; @@ -8827,7 +8877,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; } @@ -8835,18 +8885,24 @@ PUGI__NS_BEGIN assert(!"Unimplemented axis"); } } - - template xpath_node_set_raw step_do(const xpath_context& c, const xpath_stack& stack, T v) + + template 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()); @@ -8859,9 +8915,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) + step_fill(ns, it->attribute(), it->parent(), stack.result, once, v); apply_predicates(ns, size, stack); } @@ -8869,9 +8925,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) + step_fill(ns, c.n.attribute(), c.n.parent(), stack.result, once, v); apply_predicates(ns, 0, stack); } @@ -9054,7 +9110,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: @@ -9100,7 +9156,7 @@ PUGI__NS_BEGIN { xpath_allocator_capture cr(stack.result); - return static_cast(_left->eval_node_set(c, stack).size()); + return static_cast(_left->eval_node_set(c, stack, nodeset_eval_all).size()); } case ast_func_string_length_0: @@ -9133,7 +9189,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) { @@ -9269,7 +9325,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)); @@ -9286,7 +9342,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)); @@ -9303,7 +9359,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)); @@ -9466,7 +9522,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); } @@ -9478,7 +9534,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) { @@ -9488,8 +9544,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); @@ -9503,7 +9559,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(); @@ -9521,44 +9577,44 @@ PUGI__NS_BEGIN switch (_axis) { case axis_ancestor: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_ancestor_or_self: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_attribute: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_child: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_descendant: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_descendant_or_self: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_following: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_following_sibling: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_namespace: // namespaced axis is not supported return xpath_node_set_raw(); case axis_parent: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_preceding: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_preceding_sibling: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); case axis_self: - return step_do(c, stack, axis_to_type()); + return step_do(c, stack, eval, axis_to_type()); default: assert(!"Unknown axis"); @@ -11111,7 +11167,7 @@ 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()); } @@ -11128,7 +11184,7 @@ namespace pugi if (setjmp(sd.error_handler)) return xpath_node(); #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_first); return r.first(); } 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, "") 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("") + STR("testtest") + STR("testtest") + STR("testtest") + STR("")); + + xpath_node_set ns = doc.select_nodes(STR("//node() | //@*")); + + std::vector 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_paths.cpp b/tests/test_xpath_paths.cpp index b6f53c7..d791cdf 100644 --- a/tests/test_xpath_paths.cpp +++ b/tests/test_xpath_paths.cpp @@ -594,4 +594,26 @@ TEST_XML(xpath_paths_optimize_compare_attribute, " CHECK_XPATH_NODESET(doc, STR("node[@xmlns = '3']")); } +TEST_XML(xpath_paths_optimize_step_once, "") +{ + 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")); +} #endif -- cgit v1.2.3 From 45b6315d995d70bc117b2ee7320112e54050463c Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Mon, 20 Oct 2014 01:00:56 +0000 Subject: tests: Add a coverage test for unspecified_bool It's unfortunate that we can even do that... git-svn-id: https://pugixml.googlecode.com/svn/trunk@1068 99668b35-9821-0410-8761-19e4c4f06640 --- tests/test_dom_traverse.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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, "< CHECK(r4.begin() == xml_attribute_iterator()); CHECK(r4.end() == xml_attribute_iterator()); } + +TEST_XML(dom_unspecified_bool_coverage, "text") +{ + 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 +} -- cgit v1.2.3 From 7774cdd96e01b2d89be16f7e240c1ffb2436b4c9 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Tue, 21 Oct 2014 03:33:37 +0000 Subject: XPath: Make sure step_push is called with valid nodes Some steps relied on step_push rejecting null inputs; this is no longer the case. Additionally stepping now more rigorously filters null inputs. git-svn-id: https://pugixml.googlecode.com/svn/trunk@1069 99668b35-9821-0410-8761-19e4c4f06640 --- src/pugixml.cpp | 8 ++++---- tests/test_xpath_paths.cpp | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/pugixml.cpp b/src/pugixml.cpp index e9ab09d..5c7fb6d 100644 --- a/src/pugixml.cpp +++ b/src/pugixml.cpp @@ -8705,7 +8705,7 @@ PUGI__NS_BEGIN while (cur && !cur.next_sibling()) cur = cur.parent(); cur = cur.next_sibling(); - for (;;) + while (cur) { if (step_push(ns, cur, alloc) & once) return; @@ -8733,7 +8733,7 @@ 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(); @@ -8916,7 +8916,7 @@ PUGI__NS_BEGIN if (it->node()) step_fill(ns, it->node(), stack.result, once, v); - else if (axis_has_attributes) + 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); @@ -8926,7 +8926,7 @@ PUGI__NS_BEGIN { if (c.n.node()) step_fill(ns, c.n.node(), stack.result, once, v); - else if (axis_has_attributes) + 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); diff --git a/tests/test_xpath_paths.cpp b/tests/test_xpath_paths.cpp index d791cdf..f1d52ad 100644 --- a/tests/test_xpath_paths.cpp +++ b/tests/test_xpath_paths.cpp @@ -616,4 +616,27 @@ TEST_XML(xpath_paths_optimize_step_once, "

") +{ + 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 -- cgit v1.2.3 From 7258aea09be1847b3dcc99ca389990027d4a92d3 Mon Sep 17 00:00:00 2001 From: Arseny Kapoulkine Date: Tue, 21 Oct 2014 03:33:47 +0000 Subject: tests: Assert on out-of-memory in tests This should never happen but can improve debugging experience for work-in-progress changes since that avoids memcpy() into negative memory space (debugger can't backtrace from failed memcpy since it does not set up the stack frame). git-svn-id: https://pugixml.googlecode.com/svn/trunk@1070 99668b35-9821-0410-8761-19e4c4f06640 --- tests/allocator.cpp | 3 +++ tests/main.cpp | 1 + 2 files changed, 4 insertions(+) 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 +#include // 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(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++; -- cgit v1.2.3