From f6635588758ed1b650be22903c2e2e81273e05c5 Mon Sep 17 00:00:00 2001
From: Arseny Kapoulkine <arseny.kapoulkine@gmail.com>
Date: Sun, 19 Oct 2014 07:33:42 +0000
Subject: [PATCH] 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 1d9dcfee..6f230dd9 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::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;
 
@@ -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::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);
+
+		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 69b2cb25..2947bf4a 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 d8317129..8ec5694e 100644
--- a/tests/test_xpath_api.cpp
+++ b/tests/test_xpath_api.cpp
@@ -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\""));
-- 
GitLab