Skip to content

Commit d0a8e12

Browse files
committed
Node: Add eval_xpath()
Fixes #66
1 parent b923b8b commit d0a8e12

File tree

3 files changed

+174
-47
lines changed

3 files changed

+174
-47
lines changed

examples/dom_xpath/main.cc

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,36 @@ std::string result_type_to_ustring(xmlpp::XPathResultType result_type)
4040
}
4141
}
4242

43+
void print_nodeset(const xmlpp::Node::const_NodeSet& set)
44+
{
45+
// Print the structural paths and the values:
46+
for(const auto& child : set)
47+
{
48+
std::cout << " " << child->get_path();
49+
50+
auto attribute = dynamic_cast<const xmlpp::Attribute*>(child);
51+
if (attribute)
52+
std::cout << ", value=\"" << attribute->get_value() << "\"";
53+
54+
auto content_node = dynamic_cast<const xmlpp::ContentNode*>(child);
55+
if (content_node)
56+
std::cout << ", content=\"" << content_node->get_content() << "\"";
57+
58+
auto entity_reference = dynamic_cast<const xmlpp::EntityReference*>(child);
59+
if (entity_reference)
60+
std::cout << ", text=\"" << entity_reference->get_original_text() << "\"";
61+
62+
auto element = dynamic_cast<const xmlpp::Element*>(child);
63+
if (element)
64+
{
65+
auto text_node = element->get_first_child_text();
66+
if (text_node)
67+
std::cout << ", first_child_text=\"" << text_node->get_content() << "\"";
68+
}
69+
std::cout << std::endl;
70+
}
71+
}
72+
4373
bool xpath_test(const xmlpp::Node* node, const std::string& xpath)
4474
{
4575
bool result = true;
@@ -49,39 +79,46 @@ bool xpath_test(const xmlpp::Node* node, const std::string& xpath)
4979
try
5080
{
5181
auto set = node->find(xpath);
82+
std::cout << "find(): " << set.size() << " nodes have been found:" << std::endl;
83+
print_nodeset(set);
84+
}
85+
catch (const xmlpp::exception& ex)
86+
{
87+
std::cerr << "Exception caught from find: " << ex.what() << std::endl;
88+
result = false;
89+
}
5290

53-
std::cout << set.size() << " nodes have been found:" << std::endl;
54-
55-
//Print the structural paths and the values:
56-
for(const auto& child : set)
91+
try
92+
{
93+
auto var = node->eval_xpath(xpath);
94+
std::cout << "eval_xpath(): ";
95+
switch (var.index())
5796
{
58-
std::cout << " " << child->get_path();
59-
60-
auto attribute = dynamic_cast<const xmlpp::Attribute*>(child);
61-
if (attribute)
62-
std::cout << ", value=\"" << attribute->get_value() << "\"";
63-
64-
auto content_node = dynamic_cast<const xmlpp::ContentNode*>(child);
65-
if (content_node)
66-
std::cout << ", content=\"" << content_node->get_content() << "\"";
67-
68-
auto entity_reference = dynamic_cast<const xmlpp::EntityReference*>(child);
69-
if (entity_reference)
70-
std::cout << ", text=\"" << entity_reference->get_original_text() << "\"";
71-
72-
auto element = dynamic_cast<const xmlpp::Element*>(child);
73-
if (element)
74-
{
75-
auto text_node = element->get_first_child_text();
76-
if (text_node)
77-
std::cout << ", first_child_text=\"" << text_node->get_content() << "\"";
78-
}
79-
std::cout << std::endl;
97+
case 0: // nodeset
98+
{
99+
auto set = std::get<0>(var);
100+
std::cout << set.size() << " nodes have been found:" << std::endl;
101+
print_nodeset(set);
102+
break;
103+
}
104+
case 1: // boolean
105+
std::cout << "Boolean: " << (std::get<1>(var) ? "true" : "false") << std::endl;
106+
break;
107+
case 2: // number
108+
std::cout << "Number: " << std::get<2>(var) << std::endl;
109+
break;
110+
case 3: // string
111+
std::cout << "String: " << std::get<3>(var) << std::endl;
112+
break;
113+
default:
114+
std::cerr << "Unsupported result type." << std::endl;
115+
result = false;
116+
break;
80117
}
81118
}
82119
catch (const xmlpp::exception& ex)
83120
{
84-
std::cerr << "Exception caught from find: " << ex.what() << std::endl;
121+
std::cerr << "Exception caught from eval_xpath: " << ex.what() << std::endl;
85122
result = false;
86123
}
87124

@@ -122,6 +159,9 @@ int main(int argc, char* argv[])
122159
// Find all sections, no matter where:
123160
result &= xpath_test(root, "//section");
124161

162+
// Count the number of sections:
163+
result &= !xpath_test(root, "count(//section)");
164+
125165
// Find the title node (if there is one):
126166
result &= xpath_test(root, "title");
127167

libxml++/nodes/node.cc

Lines changed: 80 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,8 @@ Tlist get_children_common(const xmlpp::ustring& name, xmlNode* child)
5353
return children;
5454
}
5555

56-
// Common part of all overloaded xmlpp::Node::find() methods.
57-
template <typename Tvector>
58-
Tvector find_common(const xmlpp::ustring& xpath,
56+
// A common part of all overloaded xmlpp::Node::find() and eval_xpath() methods.
57+
xmlXPathObject* find_common1(const xmlpp::ustring& xpath,
5958
const xmlpp::Node::PrefixNsMap* namespaces, xmlNode* node)
6059
{
6160
auto ctxt = xmlXPathNewContext(node->doc);
@@ -72,22 +71,20 @@ Tvector find_common(const xmlpp::ustring& xpath,
7271
}
7372

7473
auto result = xmlXPathEval((const xmlChar*)xpath.c_str(), ctxt);
74+
xmlXPathFreeContext(ctxt);
7575

7676
if (!result)
77-
{
78-
xmlXPathFreeContext(ctxt);
79-
8077
throw xmlpp::exception("Invalid XPath: " + xpath);
81-
}
8278

83-
if (result->type != XPATH_NODESET)
84-
{
85-
xmlXPathFreeObject(result);
86-
xmlXPathFreeContext(ctxt);
87-
88-
throw xmlpp::internal_error("Only nodeset result types are supported.");
89-
}
79+
return result;
80+
}
9081

82+
// A common part of all overloaded xmlpp::Node::find() and eval_xpath() methods.
83+
// Tvector == NodeSet or const_NodeSet
84+
// result->type == XPATH_NODESET
85+
template <typename Tvector>
86+
Tvector find_common2(xmlXPathObject* result, const char* method_name)
87+
{
9188
auto nodeset = result->nodesetval;
9289
Tvector nodes;
9390
if (nodeset && !xmlXPathNodeSetIsEmpty(nodeset))
@@ -99,15 +96,15 @@ Tvector find_common(const xmlpp::ustring& xpath,
9996
auto cnode = xmlXPathNodeSetItem(nodeset, i);
10097
if (!cnode)
10198
{
102-
std::cerr << "Node::find(): The xmlNode was null." << std::endl;
99+
std::cerr << "Node::" << method_name << "(): The xmlNode was null." << std::endl;
103100
continue;
104101
}
105102

106103
if (cnode->type == XML_NAMESPACE_DECL)
107104
{
108-
//In this case we would cast it to a xmlNs*,
109-
//but this C++ method only returns Nodes.
110-
std::cerr << "Node::find(): Ignoring an xmlNs object." << std::endl;
105+
// In this case we would cast it to a xmlNs*,
106+
// but this C++ method only returns Nodes.
107+
std::cerr << "Node::" << method_name << "(): Ignoring an xmlNs object." << std::endl;
111108
continue;
112109
}
113110

@@ -122,11 +119,62 @@ Tvector find_common(const xmlpp::ustring& xpath,
122119
}
123120

124121
xmlXPathFreeObject(result);
125-
xmlXPathFreeContext(ctxt);
126122

127123
return nodes;
128124
}
129125

126+
// Common part of all overloaded xmlpp::Node::find() methods.
127+
template <typename Tvector>
128+
Tvector find_common(const xmlpp::ustring& xpath,
129+
const xmlpp::Node::PrefixNsMap* namespaces, xmlNode* node)
130+
{
131+
auto result = find_common1(xpath, namespaces, node);
132+
133+
if (result->type != XPATH_NODESET)
134+
{
135+
xmlXPathFreeObject(result);
136+
throw xmlpp::internal_error("Only nodeset result types are supported.");
137+
}
138+
return find_common2<Tvector>(result, "find");
139+
}
140+
141+
// Common part of all overloaded xmlpp::Node::eval_xpath() methods.
142+
template <typename Tvector>
143+
std::variant<Tvector, bool, double, xmlpp::ustring>
144+
eval_xpath_common(const xmlpp::ustring& xpath,
145+
const xmlpp::Node::PrefixNsMap* namespaces, xmlNode* node)
146+
{
147+
auto result = find_common1(xpath, namespaces, node);
148+
149+
switch (result->type)
150+
{
151+
case XPATH_NODESET:
152+
return find_common2<Tvector>(result, "eval_xpath");
153+
154+
case XPATH_BOOLEAN:
155+
{
156+
auto val = static_cast<bool>(result->boolval);
157+
xmlXPathFreeObject(result);
158+
return val;
159+
}
160+
case XPATH_NUMBER:
161+
{
162+
double val = result->floatval;
163+
xmlXPathFreeObject(result);
164+
return val;
165+
}
166+
case XPATH_STRING:
167+
{
168+
xmlpp::ustring val = reinterpret_cast<const char*>(result->stringval);
169+
xmlXPathFreeObject(result);
170+
return val;
171+
}
172+
default:
173+
xmlXPathFreeObject(result);
174+
throw xmlpp::internal_error("Unsupported result type.");
175+
}
176+
}
177+
130178
// Common part of xmlpp::Node::eval_to_[boolean|number|string]
131179
xmlXPathObject* eval_common(const xmlpp::ustring& xpath,
132180
const xmlpp::Node::PrefixNsMap* namespaces,
@@ -145,7 +193,7 @@ xmlXPathObject* eval_common(const xmlpp::ustring& xpath,
145193
reinterpret_cast<const xmlChar*>(ns_uri.c_str()));
146194
}
147195

148-
auto xpath_value = xmlXPathEvalExpression(
196+
auto xpath_value = xmlXPathEval(
149197
reinterpret_cast<const xmlChar*>(xpath.c_str()), ctxt);
150198

151199
xmlXPathFreeContext(ctxt);
@@ -412,6 +460,18 @@ Node::const_NodeSet Node::find(const ustring& xpath, const PrefixNsMap& namespac
412460
return find_common<const_NodeSet>(xpath, &namespaces, impl_);
413461
}
414462

463+
std::variant<Node::NodeSet, bool, double, ustring>
464+
Node::eval_xpath(const ustring& xpath, const PrefixNsMap& namespaces)
465+
{
466+
return eval_xpath_common<NodeSet>(xpath, &namespaces, impl_);
467+
}
468+
469+
std::variant<Node::const_NodeSet, bool, double, ustring>
470+
Node::eval_xpath(const ustring& xpath, const PrefixNsMap& namespaces) const
471+
{
472+
return eval_xpath_common<const_NodeSet>(xpath, &namespaces, impl_);
473+
}
474+
415475
bool Node::eval_to_boolean(const ustring& xpath, XPathResultType* result_type) const
416476
{
417477
return eval_common_to_boolean(xpath, nullptr, result_type, impl_);

libxml++/nodes/node.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <list>
1414
#include <map>
1515
#include <vector>
16+
#include <variant>
1617

1718
#ifndef DOXYGEN_SHOULD_SKIP_THIS
1819
extern "C" {
@@ -230,6 +231,32 @@ class LIBXMLPP_API Node : public NonCopyable
230231
*/
231232
const_NodeSet find(const ustring& xpath, const PrefixNsMap& namespaces) const;
232233

234+
/** Evaluate an XPath expression.
235+
* @param xpath The XPath expression.
236+
* @param namespaces A map of namespace prefixes to namespace URIs to be used while evaluating.
237+
* @returns The resulting NodeSet (XPathResultType::NODESET), bool (XPathResultType::BOOLEAN),
238+
* double (XPathResultType::NUMBER) or ustring (XPathResultType::STRING).
239+
* @throws xmlpp::exception If the XPath expression cannot be evaluated.
240+
* @throws xmlpp::internal_error If the result type is not nodeset, boolean, number or string.
241+
*
242+
* @newin{5,4}
243+
*/
244+
std::variant<NodeSet, bool, double, ustring>
245+
eval_xpath(const ustring& xpath, const PrefixNsMap& namespaces = {});
246+
247+
/** Evaluate an XPath expression.
248+
* @param xpath The XPath expression.
249+
* @param namespaces A map of namespace prefixes to namespace URIs to be used while evaluating.
250+
* @returns The resulting const_NodeSet (XPathResultType::NODESET), bool (XPathResultType::BOOLEAN),
251+
* double (XPathResultType::NUMBER) or ustring (XPathResultType::STRING).
252+
* @throws xmlpp::exception If the XPath expression cannot be evaluated.
253+
* @throws xmlpp::internal_error If the result type is not nodeset, boolean, number or string.
254+
*
255+
* @newin{5,4}
256+
*/
257+
std::variant<const_NodeSet, bool, double, ustring>
258+
eval_xpath(const ustring& xpath, const PrefixNsMap& namespaces = {}) const;
259+
233260
/** Evaluate an XPath expression.
234261
* @param xpath The XPath expression.
235262
* @param[out] result_type Result type of the XPath expression before conversion

0 commit comments

Comments
 (0)