299 lines
12 KiB
C++
299 lines
12 KiB
C++
#include "pugixml_util.hpp"
|
|
#include <algorithm>
|
|
|
|
namespace pugiutil {
|
|
|
|
//Loads the XML file specified by filename into the passed pugi::xml_document
|
|
//
|
|
//Returns loc_data look-up for xml node line numbers
|
|
loc_data load_xml(pugi::xml_document& doc, //Document object to be loaded with file contents
|
|
const std::string filename) { //Filename to load from
|
|
auto location_data = loc_data(filename);
|
|
|
|
auto load_result = doc.load_file(filename.c_str());
|
|
if (!load_result) {
|
|
std::string msg = load_result.description();
|
|
auto line = location_data.line(load_result.offset);
|
|
auto col = location_data.col(load_result.offset);
|
|
throw XmlError("Unable to load XML file '" + filename + "', " + msg
|
|
+ " (line: " + std::to_string(line) + " col: " + std::to_string(col) + ")",
|
|
filename.c_str(), line);
|
|
}
|
|
|
|
return location_data;
|
|
}
|
|
|
|
//Gets the first child element of the given name and returns it.
|
|
//
|
|
// node - The parent xml node
|
|
// child_name - The child tag name
|
|
// loc_data - XML file location data
|
|
// req_opt - Whether the child tag is required (will error if required and not found) or optional. Defaults to REQUIRED
|
|
pugi::xml_node get_first_child(const pugi::xml_node node,
|
|
const std::string& child_name,
|
|
const loc_data& loc_data,
|
|
const ReqOpt req_opt) {
|
|
pugi::xml_node child = node.child(child_name.c_str());
|
|
if (!child && req_opt == REQUIRED) {
|
|
throw XmlError("Missing required child node '" + child_name + "' in parent node '" + node.name() + "'",
|
|
loc_data.filename(), loc_data.line(node));
|
|
}
|
|
return child;
|
|
}
|
|
|
|
//Gets the child element of the given name and returns it.
|
|
//Errors if more than one matching child is found.
|
|
//
|
|
// node - The parent xml node
|
|
// child_name - The child tag name
|
|
// loc_data - XML file location data
|
|
// req_opt - Whether the child tag is required (will error if required and not found) or optional. Defaults to REQUIRED
|
|
pugi::xml_node get_single_child(const pugi::xml_node node,
|
|
const std::string& child_name,
|
|
const loc_data& loc_data,
|
|
const ReqOpt req_opt) {
|
|
pugi::xml_node child = get_first_child(node, child_name, loc_data, req_opt);
|
|
|
|
if (child && child.next_sibling(child_name.c_str())) {
|
|
throw XmlError("Multiple child '" + child_name + "' nodes found in parent node '" + node.name() + "' (only one expected)",
|
|
loc_data.filename(), loc_data.line(node));
|
|
}
|
|
|
|
return child;
|
|
}
|
|
|
|
//Counts the number of child nodes of type 'child_name'
|
|
//
|
|
// node - The parent xml node
|
|
// child_name - The child tag name
|
|
// loc_data - XML file location data
|
|
// req_opt - Whether the child tag is required (will error if required and not found) or optional. Defaults to REQUIRED
|
|
size_t count_children(const pugi::xml_node node,
|
|
const std::string& child_name,
|
|
const loc_data& loc_data,
|
|
const ReqOpt req_opt) {
|
|
size_t count = 0;
|
|
|
|
pugi::xml_node child = get_first_child(node, child_name, loc_data, req_opt);
|
|
|
|
while (child) {
|
|
++count;
|
|
child = child.next_sibling(child_name.c_str());
|
|
}
|
|
|
|
//Note that we don't do any error checking here since get_first_child does the existance check
|
|
|
|
return count;
|
|
}
|
|
|
|
//Counts the number of child nodes (any type)
|
|
//
|
|
// node - The parent xml node
|
|
// loc_data - XML file location data
|
|
// req_opt - Whether the child tag is required (will error if required and not found) or optional. Defaults to REQUIRED
|
|
size_t count_children(const pugi::xml_node node,
|
|
const loc_data& loc_data,
|
|
const ReqOpt req_opt) {
|
|
size_t count = std::distance(node.begin(), node.end());
|
|
|
|
if (count == 0 && req_opt == REQUIRED) {
|
|
throw XmlError("Expected child node(s) in node '" + std::string(node.name()) + "'",
|
|
loc_data.filename(), loc_data.line(node));
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
//Throws a well formatted error if the actual count of child nodes name 'child_name' does not equal the 'expected_count'
|
|
//
|
|
// node - The parent xml node
|
|
// loc_data - XML file location data
|
|
// expected_count - The expected number of child nodes
|
|
void expect_child_node_count(const pugi::xml_node node,
|
|
std::string child_name,
|
|
size_t expected_count,
|
|
const loc_data& loc_data) {
|
|
size_t actual_count = count_children(node, child_name, loc_data, OPTIONAL);
|
|
|
|
if (actual_count != expected_count) {
|
|
throw XmlError("Found " + std::to_string(actual_count)
|
|
+ " '" + child_name + "' child node(s) of "
|
|
+ "'" + std::string(node.name()) + "'"
|
|
+ " (expected " + std::to_string(expected_count) + ")",
|
|
loc_data.filename(), loc_data.line(node));
|
|
}
|
|
}
|
|
|
|
//Throws a well formatted error if the actual child count does not equal the 'expected_count'
|
|
//
|
|
// node - The parent xml node
|
|
// loc_data - XML file location data
|
|
// expected_count - The expected number of child nodes
|
|
void expect_child_node_count(const pugi::xml_node node,
|
|
size_t expected_count,
|
|
const loc_data& loc_data) {
|
|
size_t actual_count = count_children(node, loc_data, OPTIONAL);
|
|
|
|
if (actual_count != expected_count) {
|
|
throw XmlError("Found " + std::to_string(actual_count)
|
|
+ " child node(s) of "
|
|
+ "'" + std::string(node.name()) + "'"
|
|
+ " (expected " + std::to_string(expected_count) + ")",
|
|
loc_data.filename(), loc_data.line(node));
|
|
}
|
|
}
|
|
|
|
//Throws a well formatted error if any of node's children are not part of child_names.
|
|
//Note this does not check whether the nodes in 'attribute_names' actually exist.
|
|
//
|
|
// node - The parent xml node
|
|
// child_names - expected attribute names
|
|
// loc_data - XML file location data
|
|
void expect_only_children(const pugi::xml_node node,
|
|
std::vector<std::string> child_names,
|
|
const loc_data& loc_data) {
|
|
for (auto child : node.children()) {
|
|
std::string child_name = child.name();
|
|
auto iter = std::find(child_names.begin(),
|
|
child_names.end(),
|
|
child_name);
|
|
if (iter == child_names.end()) {
|
|
std::string msg = "Unexpected child '" + child_name + "'"
|
|
+ " of node '" + node.name() + "'.";
|
|
|
|
if (child_names.size() > 0) {
|
|
msg += " Expected (possibly) one of: ";
|
|
for (size_t i = 0; i < child_names.size(); i++) {
|
|
if (i != 0) {
|
|
msg += ", ";
|
|
}
|
|
if (i > 0 && i == child_names.size() - 1) {
|
|
msg += "or ";
|
|
}
|
|
msg += "'" + child_names[i] + "'";
|
|
}
|
|
msg += ".";
|
|
}
|
|
|
|
throw XmlError(msg, loc_data.filename(), loc_data.line(child));
|
|
}
|
|
}
|
|
}
|
|
|
|
//Throws a well formatted error if any attribute other than those named in 'attribute_names' are found on 'node' with an additional explanation.
|
|
//Note this does not check whether the attribues in 'attribute_names' actually exist.
|
|
//
|
|
// node - The parent xml node
|
|
// attribute_names - expected attribute names
|
|
// loc_data - XML file location data
|
|
void expect_only_attributes(const pugi::xml_node node,
|
|
std::vector<std::string> attribute_names,
|
|
std::string explanation,
|
|
const loc_data& loc_data) {
|
|
for (auto attrib : node.attributes()) {
|
|
std::string attrib_name = attrib.name();
|
|
auto iter = std::find(attribute_names.begin(),
|
|
attribute_names.end(),
|
|
attrib_name);
|
|
if (iter == attribute_names.end()) {
|
|
std::string msg = "Unexpected attribute '" + attrib_name + "'"
|
|
+ " found on node '" + node.name() + "'";
|
|
|
|
if (!explanation.empty()) {
|
|
msg += explanation;
|
|
}
|
|
|
|
msg += ".";
|
|
|
|
if (attribute_names.size() > 0) {
|
|
msg += " Expected (possibly) one of: ";
|
|
for (size_t i = 0; i < attribute_names.size(); i++) {
|
|
if (i != 0) {
|
|
msg += ", ";
|
|
}
|
|
if (i > 0 && i == attribute_names.size() - 1) {
|
|
msg += "or ";
|
|
}
|
|
msg += "'" + attribute_names[i] + "'";
|
|
}
|
|
msg += ".";
|
|
}
|
|
|
|
throw XmlError(msg, loc_data.filename(), loc_data.line(node));
|
|
}
|
|
}
|
|
}
|
|
|
|
//Throws a well formatted error if any attribute other than those named in 'attribute_names' are found on 'node'.
|
|
//Note this does not check whether the attribues in 'attribute_names' actually exist; for that use get_attribute().
|
|
//
|
|
// node - The parent xml node
|
|
// attribute_names - expected attribute names
|
|
// loc_data - XML file location data
|
|
void expect_only_attributes(const pugi::xml_node node,
|
|
std::vector<std::string> attribute_names,
|
|
const loc_data& loc_data) {
|
|
expect_only_attributes(node, attribute_names, "", loc_data);
|
|
}
|
|
|
|
//Counts the number of attributes on the specified node
|
|
//
|
|
// node - The xml node
|
|
// loc_data - XML file location data
|
|
// req_opt - Whether any attributes are required (will error if required and none are found) or optional. Defaults to REQUIRED
|
|
size_t count_attributes(const pugi::xml_node node,
|
|
const loc_data& loc_data,
|
|
const ReqOpt req_opt) {
|
|
size_t count = std::distance(node.attributes_begin(), node.attributes_end());
|
|
|
|
if (count == 0 && req_opt == REQUIRED) {
|
|
throw XmlError("Expected attributes on node'" + std::string(node.name()) + "'",
|
|
loc_data.filename(), loc_data.line(node));
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
//Gets a named property on an node and returns it.
|
|
//
|
|
// node - The xml node
|
|
// attr_name - The attribute name
|
|
// loc_data - XML file location data
|
|
// req_opt - Whether the peropry is required (will error if required and not found) or optional. Defaults to REQUIRED
|
|
pugi::xml_attribute get_attribute(const pugi::xml_node node,
|
|
const std::string& attr_name,
|
|
const loc_data& loc_data,
|
|
const ReqOpt req_opt) {
|
|
pugi::xml_attribute attr = node.attribute(attr_name.c_str());
|
|
|
|
if (!attr && req_opt == REQUIRED) {
|
|
throw XmlError("Expected '" + attr_name + "' attribute on node '" + node.name() + "'",
|
|
loc_data.filename(), loc_data.line(node));
|
|
}
|
|
|
|
return attr;
|
|
}
|
|
|
|
//Checks that the given node matches the given tag name.
|
|
//
|
|
// node - The xml node
|
|
// tag_name - The expected tag name
|
|
// loc_data - XML file location data
|
|
// req_opt - Whether the tag name is required (will error if required and not found) or optional. Defaults to REQUIRED
|
|
bool check_node(const pugi::xml_node node,
|
|
const std::string& tag_name,
|
|
const loc_data& loc_data,
|
|
const ReqOpt req_opt) {
|
|
if (node.name() == tag_name) {
|
|
return true;
|
|
} else {
|
|
if (req_opt == REQUIRED) {
|
|
throw XmlError(std::string("Unexpected node type '") + node.name() + "' expected '" + tag_name + "'",
|
|
loc_data.filename(), loc_data.line(node));
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
} // namespace pugiutil
|