RapidXML

27 Apr 2022

Rapid XML notes

#Rapid XML Notes


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

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.

  1. Create a file named 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"
  1. Include rapidxml_ext.hpp in your code when you want use rapidxml_print.hpp

 
 
 
 

#Reading XML


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>

 
 
 
 

#Parse the XML

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);

 
 
 
 

#Read the XML Nodes

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";
	}

 
 
 
 

#Node Iteration

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;
				}

			}

		} 

 
 
 
 

#Node Attributes

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";

 
 
 
 

#putting it all together

#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;
}

 
 
 
 

#Writing XML


 
 
 
 

#Document Creation

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;

 
 
 
 

#Building the XML Tree

Add the XML declaration by

  1. Creating a rapidxml::xml_node<>
  2. Setting it's version and encoding attributes
  3. Appending the node to the document.
	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);

 
 
 
 

#Output the XML Tree

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;
	

 
 
 
 

#putting it all together

#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;	 
}