Overview:
Introduction
PugiXml is a C++ open source library realizing a very fast XML parser; for navigation and selection within XML trees you can also use XPath expressions. PugiXml is available at http://pugixml.org/
In contrast to other XML parsers (e.g. TinyXml) you have to pay attention to the following aspects:
- Fully synchronize read and write
Adding new contents (e.g. child elements, attributes) to your XML tree always needs direct access to the XML document. It is not possible to prepare a new node/subtree outside the overall structure! As a consequence you cannot read (or save) your XML tree while some other thread is adding contents.
What is possible: If appropriate you may construct a new subtree within a separate temporary XML document and copy the constructed subtree to your target document. Then only the copy action has to be synchronized.
- Do not forget parsing options when loading an XML file
For better performance most parsing options are switched off. Consider explicitly activating the following options:
- parse_declaration:
if not set the XML declaration entries are not read and may get lost on next saving
- parse_comments:
if not set comments are not read and may get lost on next saving
- parse_trim_pcdata:
if not set leading and trailing white spaces will not be removed. If parsing application specific values (e.g. integers, strings) from the XML nodes and attribute entries your application specific parsing routines have to consider those white spaces!
Create, load and save an xml file
Create new xml file
Construct your XML tree within an "xml_document" and store it within an XML file using "save_file":
pugi::xml_document doc;
auto declarationNode = doc.append_child(pugi::node_declaration);
declarationNode.append_attribute("version") = "1.0";
declarationNode.append_attribute("encoding") = "ISO-8859-1";
declarationNode.append_attribute("standalone") = "yes";
auto root = doc.append_child("MyRoot");
bool saveSucceeded = doc.save_file(XML_FILE_PATH.c_str(), PUGIXML_TEXT(" "));
assert(saveSucceeded);
Load existing xml file
Load the contents of an existing XML file into an xml_document by calling "load_file":
pugi::xml_document doc;
pugi::xml_parse_result result = doc.load_file(XML_FILE_PATH.c_str(),
pugi::parse_default|pugi::parse_declaration);
if (!result)
{
std::cout << "Parse error: " << result.description()
<< ", character pos= " << result.offset;
}
pugi::xml_node root = doc.document_element();
Add data (nodes and attributes) to existing xml tree
Add child elements with attributes
In the simplest case child elements can be added at top ("prepend") or at end ("append") of possibly already existing child elements. You can assign attribute values of any basic type:
pugi::xml_node nodeChild = root.append_child("MyChild");
nodeChild.append_attribute("hint") = "inserted as last child";
nodeChild.append_attribute("intVal") = in_intVal;
nodeChild = root.append_child("MyChild");
nodeChild.append_attribute("hint") = "also inserted as last child";
nodeChild.append_attribute("doubleVal") = in_doubleVal;
nodeChild = root.prepend_child("MyChild");
nodeChild.append_attribute("hint") = "inserted at front";
nodeChild.append_attribute("boolVal") = in_boolVal;
Add child elements with direct values
Instead of storing some value within an attribute you also can store values within the node itself:
pugi::xml_node childrenWithValues = root.append_child("ChildrenWithValue");
pugi::xml_node nodeChild = childrenWithValues.append_child("MyChildWithIntValue");
nodeChild.append_child(pugi::node_pcdata).set_value(ToString(4712).c_str());
nodeChild = childrenWithValues.append_child("MyChildWithDoubleValue");
nodeChild.append_child(pugi::node_pcdata).set_value(ToString(3.18).c_str());
nodeChild = childrenWithValues.append_child("MyChildWithBoolValue");
nodeChild.append_child(pugi::node_pcdata).set_value(ToString(false).c_str());
Helper function for type conversion
template <typename T>
std::string ToString(T const & in_val)
{
return std::to_string(in_val);
}
template<>
std::string ToString(bool const & in_val)
{
std::ostringstream oss;
oss << std::boolalpha << in_val;
return oss.str();
}
Add external xml subtree (e.g. given in string format) to existing xml tree
You can add an external xml string to your xml_document by loading it into a temporary xml_document and copying it into a node of your target xml_document by using "append_copy":
std::string externalXmlString = "<ExternalData>"
"<X>-1.5</X>"
"<NumPoints>23</NumPoints>"
"<exists>true</exists>"
"<SomeChild intVal=\"1508\" doubleVal=\"4.5\" boolVal=\"false\"/>"
"</ExternalData>";
pugi::xml_document tmpDoc;
if (tmpDoc.load(externalXmlString.c_str()))
{
pugi::xml_node childWithExternalSubtree = root.append_child("ChildWithExternalXmlSubtree");
childWithExternalSubtree.append_copy(tmpDoc.document_element());
}
Example XML file as generated from code snippets
The sections above have generated the following xml contents:
R"(<?xml version="1.0" encoding="ISO-8859-1" standalone="yes"?>
<MyRoot>
<MyChild hint="inserted at front" boolVal="false" />
<MyChild hint="inserted at front" boolVal="true" />
<MyChild hint="inserted as last child" intVal="4711" />
<MyChild hint="also inserted as last child" doubleVal="3.14" />
<MyChild hint="inserted as last child" intVal="4712" />
<MyChild hint="also inserted as last child" doubleVal="3.15" />
<ChildrenWithValue>
<MyChildWithIntValue>4712</MyChildWithIntValue>
<MyChildWithDoubleValue>3.180000</MyChildWithDoubleValue>
<MyChildWithBoolValue>false</MyChildWithBoolValue>
</ChildrenWithValue>
<ChildWithExternalXmlSubtree>
<ExternalData>
<X>-1.5</X>
<NumPoints>23</NumPoints>
<exists>true</exists>
<SomeChild intVal="1508" doubleVal="4.5" boolVal="false" />
</ExternalData>
</ChildWithExternalXmlSubtree>
</MyRoot>
)";
This file contents will be used in the following code samples to demonstrate reading and searching of data.
Read attribute and node values
Check for attribute existence and read attribute value
If a given attribute exists you can read it as string or numeric type:
pugi::xml_attribute attr;
if (attr = selectedNode.attribute("intVal"))
{
std::cout << "read as string: intVal=" << attr.value() << std::endl;
int intVal = attr.as_int();
std::cout << "read as int : intVal=" << intVal << std::endl;
}
Read attribute values as numeric types
Each attribute value of suitable text format can be interpreted as a numeric type:
double doubleVal = selectedNode.child("SomeChild").attribute("doubleVal").as_double();
int intVal = selectedNode.child("SomeChild").attribute("intVal").as_int();
bool boolVal = selectedNode.child("SomeChild").attribute("boolVal").as_bool();
Read node values as numeric types
Each node value of suitable text format can be interpreted as a numeric type:
double x = selectedNode.child("X") .text().as_double();
int numPoints = selectedNode.child("NumPoints").text().as_int();
bool exists = selectedNode.child("exists") .text().as_bool();
Searching with XPath
XPath: search node with given name
Look for the first node of given name which has the given attribute value:
std::string searchStr = "ChildWithExternalXmlSubtree/ExternalData";
pugi::xpath_node xpathNode = root.select_single_node(searchStr.c_str());
if (xpathNode)
{
pugi::xml_node selectedNode = xpathNode.node();
XPath: search first/last node with given attribute value
Look for the first or last node of given name which has the given attribute value:
std::string searchStr = in_searchFirst ? "MyChild[@hint='inserted as last child']"
: "MyChild[@hint='inserted as last child'][last()]";
pugi::xpath_node xpathNode = root.select_single_node(searchStr.c_str());
if (xpathNode)
{
pugi::xml_node selectedNode = xpathNode.node();
Write xml data to a stream
Instead of saving the XML document to a file you can also write the whole document to a stream by using "save":
std::cout << "\nWrite xml doc to stdout with indent of 1 char:" << std::endl;
doc.save(std::cout," ");
std::cout << "\nWrite xml doc to stringstream with indent of 2 chars:" << std::endl;
std::stringstream ss;
doc.save(ss," ");
std::cout << "stream contents are:\n" << ss.str() << std::endl;
With use of "print" any xml subtree can be written to a stream:
std::cout << "\nWrite subtree to stdout with indent of 1 char:" << std::endl;
root.child("ChildWithExternalXmlSubtree").print(std::cout, " ");
std::cout << "\nWrite subtree to stringstream with indent of 2 chars:" << std::endl;
std::stringstream ss;
root.child("ChildWithExternalXmlSubtree").print(ss, " ");
std::cout << "stream contents are:\n" << ss.str() << std::endl;
Remove attributes
You can simply remove an attribute of an XMl node by specifying its name:
someNode.remove_attribute("x");
When iterating through attributes you can selectively decide to remove an attribute:
for (pugi::xml_attribute attr = someNode.first_attribute(); attr;)
{
pugi::xml_attribute nextAttr = attr.next_attribute();
if ((std::string(attr.name())=="phi") || (attr.as_double() < 0))
{
someNode.remove_attribute(attr);
}
attr = nextAttr;
}
Remove child nodes
You can simply remove all child nodes of a given parent node by specifying the child name:
while(someParentNode.remove_child("AnOtherNode"));
When iterating through child nodes you can selectively decide to remove a child:
for (pugi::xml_node child = someParentNode.first_child(); child; )
{
pugi::xml_node next = child.next_sibling();
if (std::string(child.name()) != "AnOtherNode")
{
child.parent().remove_child(child);
}
child = next;
}
References