TinyXML2,an open source util also included by Android.
all below contents are from https://shilohjames.wordpress.com/2014/04/27/tinyxml2-tutorial/.
Welcome to my TinyXML2 tutorial!
TinyXML2 is the second major iteration of a free,lightweight XML parser,commonly used for data serialisation across a range of platforms and originally created by Lee Thomason. This tutorial assumes that the reader already possesses the following:
- An understanding of the fundamentals of C++ programming@H_404_11@
- Knowledge of the purpose of XML and the format & Syntax of XML files@H_404_11@
This tutorial is intended to demonstrate to the reader how to perform the following tasks using TinyXML2:
- Create a document to be used for storing data@H_404_11@
- Fill a document with the desired data@H_404_11@
- Save a document to an XML file@H_404_11@
- Load an XML file into a new document@H_404_11@
- Extract data from a loaded document@H_404_11@
Overview of TinyXML2
TinyXML2 defines a number of classes which provide varying layers of required functionality. In this tutorial,these are thethree classes of interest:
XMLDocument
acts as a container for a collection ofXMLElement
s,and is the primary interface for saving to and loading from XML files.@H_404_11@XMLElement
is a container for a single XML element,including any attributes.@H_404_11@XMLNode
is a base class of bothXMLElement
&XMLDocument
,and provides the interface required for traversal of elements within a document.@H_404_11@
Also of interest is an enumerated type defined in the library:
XMLError
defines the possible success/error codes returned by many TinyXML2 functions.@H_404_11@
The internal structure of an XMLDocument
is organised as a tree of XMLElement
s,each of which is capable of containing its own children. Where these classes differ is that XMLElement
is capable of holding data intended for serialisation,while XMLDocument
is the interface for exporting to and importing from XML files. XMLNode
is useful primarily because an XMLNode
pointer can be used as an interchangable interface between an XMLDocument
and its child XMLElement
s.
Getting started
The first order of business is importing the TinyXML2 library to your project. For this,you will need to download the source code,which is available from the TinyXML2 page on GitHub. Once you’ve done this,you can import it one of two ways:
- Directly add the two files tinyxml2.h and tinyxml2.cpp to your project,-or-@H_404_11@
- Compile the source code into a .lib and link it via your IDE’s link manager.@H_404_11@
Once you’ve done one of these,you will need to include the header in the appropriate file of your project. Note that the filepath declared in this line may differ,depending on where you placed tinyxml2.h and your project’s directory settings.
#include <TinyXML2/Include/tinyxml2.h>
Typically you will also want to specify a using directive for convenience,although it isn’t strictly necessary.
using namespace tinyxml2;
For my own convenience,I also defined a macro for checking error results:
#ifndef XMLCheckResult #define XMLCheckResult(a_eResult) if (a_eResult != XML_SUCCESS) { printf("Error: %i\n",a_eResult); return a_eResult; } #endif
For readers who are unfamiliar with macros,this one is called just like a function,taking in the error result as an argument. If the result isn’t equal to XML_SUCCESS
,it reports the error code to the console and causes the function it was called in to return that same error code.
Creating an XMLDocument
Creating a new document is ridiculously easy. It takes only one line of code:
XMLDocument xmlDoc;
By itself however,it is only an empty document. Writing this to a file will produce an empty XML file,so it can be used to clear expired data from an existing XML file,but for it to be truly useful it will need at least one child element.
It is a good practice to have a single root node within any XML document,which contains all elements within the document. It is also useful to retain a pointer to the root node as it makes it easier to add first-order child elements. Let’s create a Root node now,and store a pointer to it locally.
XMLNode * pRoot = xmlDoc.NewElement("Root");
This simply creates a new XMLElement
named Root and associates the element with that XMLDocument
. However,we must explicitly attach it to the XMLDocument
.
xmlDoc.InsertFirstChild(pRoot);
With the Root node attached,we may now begin filling the document with data.
Putting data into an XMLDocument
In XML,data is stored within elements,generally one element per unit of data. Let’s create another element,which we will name IntValue.
XMLElement * pElement = xmlDoc.NewElement("IntValue");
XMLElement
provides a number of overloaded functions for setting the value of an element. XML being based on text files,the name chosen for this function is SetText()
,though the varIoUs overloads accept any primitive type to set the element’s value. In this case we will use it to set an int value.
pElement->SetText(10);
And then we add the new element as a child of the document’s root node.
pRoot->InsertEndChild(pElement);
Creating an element to store a float is practically identical,we simply have to make use of the float-argument overload of SetText()
.
pElement = xmlDoc.NewElement("FloatValue"); pElement->SetText(0.5f); pRoot->InsertEndChild(pElement);
The other accepted primitive types are unsigned int
,double
,bool
and const char *
.
Setting an attribute:value pair within an XMLElement
is just as easy. We simply use XMLElement
‘s SetAttribute()
function,specifying both the name of the attribute and its value. Naturally,SetAttribute()
also has overloads for each of the primitive types accepted by SetValue()
.
pElement = xmlDoc.NewElement("Date"); pElement->SetAttribute("day",26); pElement->SetAttribute("month","April"); pElement->SetAttribute("year",2014); pElement->SetAttribute("dateFormat","26/04/2014"); pRoot->InsertEndChild(pElement);
It is often useful to nest XML elements within other elements in a tree structure,particularly when storing a group of related data. Let’s assume that we have a std::vector<int>
containing an unknown quantity of values,each of which we want to store in its own element,and that we want to group this list of elements within the XML file. To do this,we first create a parent XMLElement
which can be responsible for identifying its children as members of a list.
pElement = xmlDoc.NewElement("List");
We then iterate through the vector,creating a new XMLElement
for each item and attaching it as a child of the List element.
for (const auto & item : vecList) { XMLElement * pListElement = xmlDoc.NewElement("Item"); pListElement->SetText(item); pElement->InsertEndChild(pListElement); }
If we want to,we can also tell the List element how many members it has,though this is not strictly necessary.
pElement->SetAttribute("itemCount",vecGroup.size());
Don’t forget to attach the List element to the XMLDocument
!
pRoot->InsertEndChild(pElement);
And that just about covers building the document data. Using different variations and combinations of these examples,it is possible to create complex XML files which store many different kinds of data. How you choose to structure these documents is ultimately up to personal preference – or possibly to your organisation’s coding standards,and TinyXML2 is robust enough that it can meet the data persistence needs of many small-to-medium sized projects.
Saving the XMLDocument to an XML file
Once the XMLDocument
has been populated,it can be saved with a single function call. However,it is generally a good idea to check the returned XMLError
code to ensure that the file was saved successfully.
XMLError eResult = xmlDoc.SaveFile("SavedData.xml"); XMLCheckResult(eResult);
Assuming that you have followed the examples listed in the prevIoUs section,your exported XML file should look very close to this:
<Root> <IntValue>10</IntValue> <FloatValue>0.5</FloatValue> <Date day="26" month="April" year="2014" dateFormat="26/04/2014"/> <List itemCount="8"> <Item>1</Item> <Item>1</Item> <Item>2</Item> <Item>3</Item> <Item>5</Item> <Item>8</Item> <Item>13</Item> <Item>21</Item> </List> </Root>
If it does,great! Because now we’re going to load up that file and pull the data back out again.
Loading an XML file into a new XMLDocument
TinyXML2 uses the same interface for loading XML files as it does for saving them. First though,we will need to create an empty XMLDocument
in which to store the data in the file.
XMLDocument xmlDoc;
Then we simply load the file into the new document. It’s also important to confirm that the file loaded successfully,so we’ll store the result returned by LoadFile()
,
XMLError eResult = xmlDoc.LoadFile("SavedData.xml");
..and check it using our macro.
XMLCheckResult(eResult);
We’ll be doing these checks frequently during the data extraction process,so from this point on they will appear in the examples wherever checking is required.
Extracting data from an XMLDocument
As with storing data into an XMLDocument
,it is useful to obtain a pointer to the root node so that we can use it throughout the extraction process. We can do this with the following line of code:
XMLNode * pRoot = xmlDoc.FirstChild();
If for some reason no root element was found, FirstChild()
will return nullptr. This generally happens when attempting to load a poorly constructed XML file,or a file that does not contain XML. In any case,if we have no root node we cannot continue,so we should abort loading and return an error code.
if (pRoot == nullptr) return XML_ERROR_FILE_READ_ERROR;
The next step is to find the element in which we stored our first piece of data,which we can do by searching for it by name. Note that this method only works if the node being searched has only one child element with the specified name,however there are other element traversal methods which we will examine later. For now,let’s find the element named IntValue.
XMLElement * pElement = pRoot->FirstChildElement("IntValue"); if (pElement == nullptr) return XML_ERROR_PARSING_ELEMENT;
Once we have the desired element,we can access the data inside it with one of several functions. In this case we expect it to contain an int,so we declare a variable of the appropriate type and pass it into QueryIntText()
.
int IoUtInt; eResult = pElement->QueryIntText(&IoUtInt); XMLCheckResult(eResult);
These functions will return a non-success error code if they are not able to convert the string within the element to the requested type,which is useful for checking data integrity,and has other applications which we won’t be looking at here.
Next we will extract the float we stored earlier,in much the same way.
pElement = pRoot->FirstChildElement("FloatValue"); if (pElement == nullptr) return XML_ERROR_PARSING_ELEMENT; float fOutFloat; eResult = pElement->QueryFloatText(&fOutFloat); XMLCheckResult(eResult);
The next item we stored was an element named Date containing four attributes. We get this element as we would get any other element.
pElement = pRoot->FirstChildElement("Date"); if (pElement == nullptr) return XML_ERROR_PARSING_ELEMENT;
Extracting attribute data from an element is performed in the same way as extracting element data,except that we must also specify the name of the desired attribute.
int IoUtDay,IoUtYear; eResult = pElement->QueryIntAttribute("day",&IoUtDay); XMLCheckResult(eResult); eResult = pElement->QueryIntAttribute("year",&IoUtYear); XMLCheckResult(eResult);
Note that if the attribute’s value is a string no type conversion is necessary,so we can simply extract the attribute as raw text. However,because Attribute()
returns null
if no attribute with the specified name was found,and we cannot initialise a std::string
with null
,we must validate each result before proceeding.
const char * szAttributeText = nullptr; szAttributeText = pElement->Attribute("month"); if (szAttributeText == nullptr) return XML_ERROR_PARSING_ATTRIBUTE; std::string strOutMonth = szAttributeText; szAttributeText = pElement->Attribute("dateFormat"); if (szAttributeText == nullptr) return XML_ERROR_PARSING_ATTRIBUTE; std::string strOutFormat = szAttributeText;
The last child of the root node in our XML file is a list containing child elements representing individual list items. Let’s begin by getting the List element.
pElement = pRoot->FirstChildElement("List"); if (pElement == nullptr) return XML_ERROR_PARSING_ELEMENT;
Because the items in this list share the same name,we cannot use GetFirstChild()
to iterate through all of them. We can however use it to get the first Item in the list,which we can then use as an iteration starting point.
Let’s create a new pointer to represent the list item we are currently traversing,and make it point to the first Item. While we’re here,we should also create a std::vector<int>
for storing the values we extract.
XMLElement * pListElement = pElement->FirstChildElement("Item"); std::vector<int> vecList;
We now want to iterate through our list’s child elements and extract the value stored in each,until we reach the end of the list. The pointer we just set up makes this simple,as we can now simply use a while loop which terminates as soon as pListElement
becomes invalid.
while (pListElement != nullptr) {
For each item we encounter,we want to extract the int we prevIoUsly stored in it and push that value into our std::vector
.
int IoUtListValue; eResult = pListElement->QueryIntText(&IoUtListValue); XMLCheckResult(eResult); vecList.push_back(IoUtListValue);
We then want to iterate to the next element in the list before closing the loop,which we can do with this code:
pListElement = pListElement->NextSiblingElement("Item"); }
By this point we have successfully extracted all of the data from our XML file. Observant readers will note that we have been potentially returning XML error codes throughout this function,and given that our XML file loading went without a hitch,it makes sense to report our success to the caller.
return XML_SUCCESS;
And that’s it!
Conclusion
In terms of its usability and simplicity,TinyXML2 is a vast improvement over its predecessor,and provides an interface which should be easy to use even for those who are still learning how to program.
Hopefully this tutorial has provided all the information you need to integrate TinyXML2 into your own projects. If it is missing some important feature offered by the TinyXML2 library,or you find any bugs in the code of these examples,leave a note in the comments and I’ll update it accordingly!