8 minutes reading time
I started using RapidXML for work several years ago. It claims to be extremely fast and is a header-only library. I don't know how accurate the speed claims are, but I like the interface and the fact that it's a header library. So I often use it for parsing XML when I'm writing C++ code. This blog post is a slightly edited copy of notes that I use instead of googling the docs again and again
Installing XmlParser is easy. It's a header library. You download it from SourceForge, unzip it somewhere, and include the header file in your C++ code.
#include "rapidxml.hpp"
#include "rapidxml_utils.hpp"
#include "rapidxml_print.hpp"
NOTE In Visual Studio 2019 (and others) there are compilation errors like this one:
Severity Code Description Project File Line Suppression State
Error C3861 'print_pi_node': identifier not found XMLParser c:\rapidxml-1.13\rapidxml_print.hpp 150
A fix for gcc appears to also fix VS2019. It can be found at stack overflow.
I'm including it here because it's helpful to me.
rapidxml_ext.hpp with the following code#pragma once
#include "rapidxml.hpp"
// Adding declarations to make it compatible with gcc 4.7 and greater
// See https://stackoverflow.com/a/55408678
namespace rapidxml {
namespace internal {
template <class OutIt, class Ch>
inline OutIt print_children(OutIt out, const xml_node<Ch>* node, int flags, int indent);
template <class OutIt, class Ch>
inline OutIt print_attributes(OutIt out, const xml_node<Ch>* node, int flags);
template <class OutIt, class Ch>
inline OutIt print_data_node(OutIt out, const xml_node<Ch>* node, int flags, int indent);
template <class OutIt, class Ch>
inline OutIt print_cdata_node(OutIt out, const xml_node<Ch>* node, int flags, int indent);
template <class OutIt, class Ch>
inline OutIt print_element_node(OutIt out, const xml_node<Ch>* node, int flags, int indent);
template <class OutIt, class Ch>
inline OutIt print_declaration_node(OutIt out, const xml_node<Ch>* node, int flags, int indent);
template <class OutIt, class Ch>
inline OutIt print_comment_node(OutIt out, const xml_node<Ch>* node, int flags, int indent);
template <class OutIt, class Ch>
inline OutIt print_doctype_node(OutIt out, const xml_node<Ch>* node, int flags, int indent);
template <class OutIt, class Ch>
inline OutIt print_pi_node(OutIt out, const xml_node<Ch>* node, int flags, int indent);
}
}
#include "rapidxml_print.hpp"
rapidxml_ext.hpp in your code when you want use rapidxml_print.hpp
I'll be using the following XML for the examples
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<recording_setup>
<primary_camera>P0123456</primary_camera>
<cameras>
<camera id="P0123456">
<description>SW Corner</description>
<ip_address>192.168.1.150</ip_address>
<port>5000</port>
</camera>
<camera id="P0123458">
<description>SE Corner</description>
<ip_address>192.168.1.151</ip_address>
<port>5000</port>
</camera>
<camera id="P0123466">
<description>NE Corner</description>
<ip_address>192.168.1.152</ip_address>
<port>5000</port>
</camera>
<camera id="P0123453">
<description>NW Corner</description>
<ip_address>192.168.1.153</ip_address>
<port>5000</port>
</camera>
</cameras>
<calibration>calibration_scene_cameras.xml</calibration>
<world_scene>lab_scene.xml</world_scene>
</recording_setup>
Read XML in from a file with rapidxml::file<>() and xml_document<>::parse()
rapidxml::file<> xmlFile("test.xml");
rapidxml::xml_document<> doc;
doc.parse<0>(xmlFile.data());
Read XML in from a a memory buffer with rapidxml::file<>() and xml_document<>::parse().
char *xmlStr = new char[ BUFFER_SIZE ];
strcpy (xmlStr , someConstMessage.c_str() );
rapidxml::xml_document<> doc;
doc.parse<0>(xmlFile);
Once you have the XML read into an xml_document<> you access its root node with the first_node() function.
// If you specify a node name, RapidXML will try to match it
// to he first node of the document.
rapidxml::xml_node<>* ocv = doc.first_node("opencv_storage");
// If the specified name is not the first node, first_nod() will return NULL
if (ocv) {
std::cout << "found <opencv_storage> node" << std::endl;
}
else {
std::cout << "<opencv_storage> node not found" << std::endl;
}
Use the name() method to get the node name.
rapidxml::xml_node<>* firstNode = doc.first_node();
if (firstNode) {
std::cout << "First node found: " << firstNode->name() << std::endl;
}
Use the value() method to get the node value.
rapidxml::xml_node<>* primaryCameraNode = root->first_node("primary_camera");
if (primaryCameraNode) {
std::cout << "Primary Camera: " << primaryCameraNode->value() << "\n";
}
A node's children can be iterated by getting its first child and using its next_sibling()
// Get Camera Node and iterate its child nodes
rapidxml::xml_node<>* camerasNode = root->first_node("cameras");
if (camerasNode)
{
for (rapidxml::xml_node<>* cam = camerasNode->first_node(); cam; cam = cam->next_sibling()) {
rapidxml::xml_node<>* camChild = cam->first_node();
for (rapidxml::xml_node<>* cc = camChild->first_node(); cc; cc = cc->next_sibling()) {
std::cout << " cam: " << cc->name() << ":" << cc->value() << std::endl;
}
}
}
A node's attributes can be accessed with the first_attribute() method.
rapidxml::xml_attribute<>* attr = cam->first_attribute();
std::cout << " camera id attribute: " << attr->value() << "\n";
#include "rapidxml.hpp"
#include "rapidxml_utils.hpp"
#include "rapidxml_print.hpp"
#include <iostream>
int main(int argc, char* argv[]) {
// read the file
rapidxml::file<> xmlFile("test.xml");
rapidxml::xml_document<> doc;
try {
doc.parse<0>(xmlFile.data());
}
catch (rapidxml::parse_error& e) {
// invalid xml
std::cout << "Invalid XML: " << e.what() << std::endl;
return false;
}
rapidxml::xml_node<>* root = doc.first_node();
if (root) {
std::cout << "Root found: " << root->name() << std::endl;
// get primary node and read its value
rapidxml::xml_node<>* primaryCameraNode = root->first_node("primary_camera");
if (primaryCameraNode) {
std::cout << "Primary Camera: " << primaryCameraNode->value() << "\n";
}
// Get Camera Node and iterate its child nodes
rapidxml::xml_node<>* camerasNode = root->first_node("cameras");
if (camerasNode)
{
for (rapidxml::xml_node<>* cam = camerasNode->first_node(); cam; cam = cam->next_sibling()) {
rapidxml::xml_attribute<>* attr = cam->first_attribute();
std::cout << " camera id attribute: " << attr->value() << "\n";
// read attribute example
rapidxml::xml_node<>* descNode = cam->first_node("description");
std::cout << " description: " << descNode->value() << "\n";
rapidxml::xml_node<>* ipNode = cam->first_node("ip_address");
std::cout << " address: " << ipNode->value() << "\n";
rapidxml::xml_node<>* portNode = cam->first_node("port");
std::cout << " port: " << portNode->value() << "\n";
}
}
}
return 0;
}
A simple XML document can be created by instantiating rapidxml::xml_document<> and adding rapidxml::xml_node<> objects to it with append_node()
Create the document with
rapidxml::xml_document<> doc;
Add the XML declaration by
rapidxml::xml_node<> rapidxml::xml_node<>* decl = doc.allocate_node(rapidxml::node_declaration);
decl->append_attribute(doc.allocate_attribute("version", "1.0"));
decl->append_attribute(doc.allocate_attribute("encoding", "utf-8"));
doc.append_node(decl);
Adding nodes to the XML tree is just a matter of allocating more xml_node<>s and appending them to the doc or other nodes.
// append root node to document
rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "status");
root->append_attribute(doc.allocate_attribute("version", "1.0"));
doc.append_node(root);
Add node to other nodes in the same way
std::stringstream ss;
ss << "SN-000-345" << i;
rapidxml::xml_node<>* cameraNode = doc.allocate_node(rapidxml::node_element, "camera");
char* attr_val = doc.allocate_string(ss.str().c_str());
cameraNode->append_attribute(doc.allocate_attribute("id", attr_val));
root->append_node(cameraNode);
Once you've created your document you can output it to a file using streams.
// Write out to file stream
std::ofstream outfile("test_out.xml");
outfile << doc;
outfile.close();
This also works with std::stringstream
ss.str("");
std::ostream_iterator<char> iter_ostream(ss);
rapidxml::print(iter_ostream, doc, rapidxml::print_no_indenting);
std::cout << ss.str() << std::endl;
printf can be used to copy the doc to a char array.
char xml_array[4096];
memset(xml_array, 0, sizeof(xml_array));
print(xml_array, doc, 0);
std::cout << xml_array << std::endl;
#include "rapidxml_ext.h"
#include <iostream>
#include <sstream>
int main(int argc, char* argv[]) {
rapidxml::xml_document<> doc;
// xml declaration
rapidxml::xml_node<>* declarationNode = doc.allocate_node(rapidxml::node_declaration);
declarationNode->append_attribute(doc.allocate_attribute("version", "1.0"));
declarationNode->append_attribute(doc.allocate_attribute("encoding", "utf-8"));
doc.append_node(declarationNode);
// add root node
rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "status");
root->append_attribute(doc.allocate_attribute("version", "1.0"));
doc.append_node(root);
// create XML node
std::stringstream ss;
for (int i = 0; i < 4; ++i) {
// add node with attribute
ss << "SN-000-345" << i;
rapidxml::xml_node<>* cameraNode = doc.allocate_node(rapidxml::node_element, "camera");
char* attr_val = doc.allocate_string(ss.str().c_str());
cameraNode->append_attribute(doc.allocate_attribute("id", attr_val));
root->append_node(cameraNode);
// add nodes to the child node.
std::string statusMessage = "standby";
char* status_val = doc.allocate_string(statusMessage.c_str());
rapidxml::xml_node<>* statusNode = doc.allocate_node(rapidxml::node_element, "status", status_val);
cameraNode->append_node(statusNode);
std::string frameRateMessage = "60Hz";
char* framerate_val = doc.allocate_string(frameRateMessage.c_str());
rapidxml::xml_node<>* frameRateNode = doc.allocate_node(rapidxml::node_element, "frame_rate", framerate_val);
cameraNode->append_node(frameRateNode);
}
// Print without indenting to a string stream
ss.str("");
std::ostream_iterator<char> iter_ostream(ss);
rapidxml::print(iter_ostream, doc, rapidxml::print_no_indenting);
std::cout << ss.str() << std::endl;
// Write out to file stream
std::ofstream outfile("test_out.xml");
outfile << doc;
outfile.close();
// to array
char xml_array[4096];
memset(xml_array, 0, sizeof(xml_array));
print(xml_array, doc, 0);
std::cout << xml_array << std::endl;
return 0;
}