Skip to content

Commit 11695d3

Browse files
committed
Node: Add eval_xpath()
Fixes #66
1 parent 60fde0a commit 11695d3

File tree

3 files changed

+182
-57
lines changed

3 files changed

+182
-57
lines changed

examples/dom_xpath/main.cc

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@
2121
#include <config.h>
2222
#endif
2323

24-
#include <libxml++/libxml++.h>
25-
#include <stdlib.h>
24+
#include <cstdlib>
2625
#include <iostream>
26+
#include <libxml++/libxml++.h>
2727

2828
Glib::ustring result_type_to_ustring(xmlpp::XPathResultType result_type)
2929
{
@@ -40,6 +40,36 @@ Glib::ustring 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 Glib::ustring& xpath)
4474
{
4575
bool result = true;
@@ -49,39 +79,46 @@ bool xpath_test(const xmlpp::Node* node, const Glib::ustring& 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

@@ -126,6 +163,9 @@ int main(int argc, char* argv[])
126163
// Find all sections, no matter where:
127164
result &= xpath_test(root, "//section");
128165

166+
// Count the number of sections:
167+
result &= !xpath_test(root, "count(//section)");
168+
129169
// Find the title node (if there is one):
130170
result &= xpath_test(root, "title");
131171

libxml++/nodes/node.cc

Lines changed: 86 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,8 @@ Tlist get_children_common(const Glib::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 Glib::ustring& xpath,
56+
// A common part of all overloaded xmlpp::Node::find() and eval_xpath() methods.
57+
xmlXPathObject* find_common1(const Glib::ustring& xpath,
5958
const xmlpp::Node::PrefixNsMap* namespaces, xmlNode* node)
6059
{
6160
auto ctxt = xmlXPathNewContext(node->doc);
@@ -65,30 +64,27 @@ Tvector find_common(const Glib::ustring& xpath,
6564

6665
if (namespaces)
6766
{
68-
for (xmlpp::Node::PrefixNsMap::const_iterator it = namespaces->begin();
69-
it != namespaces->end(); ++it)
67+
for (const auto& [prefix, ns_uri] : *namespaces)
7068
xmlXPathRegisterNs(ctxt,
71-
reinterpret_cast<const xmlChar*>(it->first.c_str()),
72-
reinterpret_cast<const xmlChar*>(it->second.c_str()));
69+
reinterpret_cast<const xmlChar*>(prefix.c_str()),
70+
reinterpret_cast<const xmlChar*>(ns_uri.c_str()));
7371
}
7472

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

7776
if (!result)
78-
{
79-
xmlXPathFreeContext(ctxt);
80-
8177
throw xmlpp::exception("Invalid XPath: " + xpath);
82-
}
8378

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

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+
{
9288
auto nodeset = result->nodesetval;
9389
Tvector nodes;
9490
if (nodeset && !xmlXPathNodeSetIsEmpty(nodeset))
@@ -100,15 +96,15 @@ Tvector find_common(const Glib::ustring& xpath,
10096
auto cnode = xmlXPathNodeSetItem(nodeset, i);
10197
if (!cnode)
10298
{
103-
std::cerr << "Node::find(): The xmlNode was null." << std::endl;
99+
std::cerr << "Node::" << method_name << "(): The xmlNode was null." << std::endl;
104100
continue;
105101
}
106102

107103
if (cnode->type == XML_NAMESPACE_DECL)
108104
{
109-
//In this case we would cast it to a xmlNs*,
110-
//but this C++ method only returns Nodes.
111-
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;
112108
continue;
113109
}
114110

@@ -123,11 +119,62 @@ Tvector find_common(const Glib::ustring& xpath,
123119
}
124120

125121
xmlXPathFreeObject(result);
126-
xmlXPathFreeContext(ctxt);
127122

128123
return nodes;
129124
}
130125

126+
// Common part of all overloaded xmlpp::Node::find() methods.
127+
template <typename Tvector>
128+
Tvector find_common(const Glib::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, Glib::ustring>
144+
eval_xpath_common(const Glib::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+
Glib::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+
131178
// Common part of xmlpp::Node::eval_to_[boolean|number|string]
132179
xmlXPathObject* eval_common(const Glib::ustring& xpath,
133180
const xmlpp::Node::PrefixNsMap* namespaces,
@@ -140,14 +187,13 @@ xmlXPathObject* eval_common(const Glib::ustring& xpath,
140187

141188
if (namespaces)
142189
{
143-
for (xmlpp::Node::PrefixNsMap::const_iterator it = namespaces->begin();
144-
it != namespaces->end(); ++it)
190+
for (const auto& [prefix, ns_uri] : *namespaces)
145191
xmlXPathRegisterNs(ctxt,
146-
reinterpret_cast<const xmlChar*>(it->first.c_str()),
147-
reinterpret_cast<const xmlChar*>(it->second.c_str()));
192+
reinterpret_cast<const xmlChar*>(prefix.c_str()),
193+
reinterpret_cast<const xmlChar*>(ns_uri.c_str()));
148194
}
149195

150-
auto xpath_value = xmlXPathEvalExpression(
196+
auto xpath_value = xmlXPathEval(
151197
reinterpret_cast<const xmlChar*>(xpath.c_str()), ctxt);
152198

153199
xmlXPathFreeContext(ctxt);
@@ -414,6 +460,18 @@ Node::const_NodeSet Node::find(const Glib::ustring& xpath, const PrefixNsMap& na
414460
return find_common<const_NodeSet>(xpath, &namespaces, impl_);
415461
}
416462

463+
std::variant<Node::NodeSet, bool, double, Glib::ustring>
464+
Node::eval_xpath(const Glib::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, Glib::ustring>
470+
Node::eval_xpath(const Glib::ustring& xpath, const PrefixNsMap& namespaces) const
471+
{
472+
return eval_xpath_common<const_NodeSet>(xpath, &namespaces, impl_);
473+
}
474+
417475
bool Node::eval_to_boolean(const Glib::ustring& xpath, XPathResultType* result_type) const
418476
{
419477
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 Glib::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 Glib::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{4,2}
243+
*/
244+
std::variant<NodeSet, bool, double, Glib::ustring>
245+
eval_xpath(const Glib::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 Glib::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{4,2}
256+
*/
257+
std::variant<const_NodeSet, bool, double, Glib::ustring>
258+
eval_xpath(const Glib::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)