TinyXML2

1. Installation and Setup (for macOS)

How to Install TinyXML2

There are instructions online that tells you how to brew-install or git-clone TinyXML2. But here I am using a different approach.

  • Go to source repo of TinyXML2: https://github.com/leethomason/tinyxml2

  • You only have to download tinyxml2.h and tinyxml2.cpp files.

  • Put them in the same directory as your project.

  • cd to the directory of your project. And run the following to compile tinyxml2.cpp into tinyxml2.o.

g++ -c -Wall -std=c++11 tinyxml2.cpp -o tinyxml2.o
  • Then run the following to create a static library libtinyxml2.a.
ar rcs libtinyxml2.a tinyxml2.o

How to Integrate It Into a C++ Project

Your Makefile should look something like this:

# Compiler to use
CXX = g++

# Flags to pass to the compiler
CXXFLAGS = -Wall -std=c++11

# Include and Library Paths for TinyXML2
INCLUDE_PATH = -I../tinyxml2/
LIBRARY_PATH = -L../tinyxml2/

# The name of the executable file
EXECUTABLE = embedded_serialization1

# Source files
SOURCES = embedded_serialization1.cpp

# Object files
OBJECTS = $(SOURCES:.cpp=.o)

# Linker flags
LDFLAGS = -ltinyxml2

# Makefile rules

# Rule to make everything
all: $(EXECUTABLE)

# Rule to make the executable
$(EXECUTABLE): $(OBJECTS)
    $(CXX) $(OBJECTS) $(LIBRARY_PATH) $(LDFLAGS) -o $@

# Rule to compile source files
%.o: %.cpp
    $(CXX) $(CXXFLAGS) $(INCLUDE_PATH) -c $< -o $@

# Rule to clean up all generated files
clean:
    rm -f $(OBJECTS) $(EXECUTABLE)

Here the static library libtinyxml2.a is in the relative path ../tinyxml2/.


2. Example 1: Embedded Serialization

See embedded_serialization1.cpp for the full code.

2.1 Introduction

This example shows how to serialize and deserialize simple heirarchical data. We have two classes:

  • Employee: with name and position.
  • Department: with name and employees.

2.2 Implementing Employee

Because we are doing embedded serialization, we need to implement the following methods:

  • void Serialize(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent): to serialize the object into an XML element.
void Employee::Serialize(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent) {
    tinyxml2::XMLElement *employeeElement = doc.NewElement("Employee");
    employeeElement->SetAttribute("name", name.c_str());
    employeeElement->SetAttribute("position", position.c_str());
    parent->InsertEndChild(employeeElement);
}
  • static Employee Deserialize(tinyxml2::XMLElement *elem):
Employee Employee::Deserialize(tinyxml2::XMLElement *elem)
{
    const char *name = elem->Attribute("name");
    const char *position = elem->Attribute("position");
    return Employee(name ? name : "", position ? position : "");
}

The static keyword means that we can call this method without an instance of the class, and the return type is Employee because deserialization will return an instance of the class.

2.3 Implementing Department

We again need to implement the following methods. But this time, we need to serialize and deserialize a vector of Employee objects.

  • void Serialize(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent):
void Department::Serialize(tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent)
{
    tinyxml2::XMLElement *deptElem = doc.NewElement("Department");
    deptElem->SetAttribute("name", name.c_str());

    for (auto &employee : employees)
    {
        employee.Serialize(doc, deptElem);
    }

    parent->InsertEndChild(deptElem);
}
  • static Department Deserialize(tinyxml2::XMLElement *elem):
static Department Deserialize(tinyxml2::XMLElement *deptElem)
{
    const char *name = deptElem->Attribute("name");
    Department dept(name ? name : "");

    for (tinyxml2::XMLElement *empElem = deptElem->FirstChildElement("Employee");
            empElem != nullptr;
            empElem = empElem->NextSiblingElement("Employee"))
    {
        dept.AddEmployee(Employee::Deserialize(empElem));
    }

    return dept;
}

2.4 Implementing main()

Now let's try to serialize and deserialize some data. Let's create an "Engineering" Department with two "Engineer" Employees.

Department dept("Engineering");
dept.AddEmployee(Employee("Alice", "Engineer"));
dept.AddEmployee(Employee("Bob", "Engineer"));

Then we need to first initialize the XML document.

tinyxml2::XMLDocument doc;

The XML standard requires that every XML document has a root element. Although we can just put our "Engineering" Department as the root element if there is only one Department, it is better to have a root element that contains all Departments. So let's create a root element.

tinyxml2::XMLElement *root = doc.NewElement("Root");
doc.InsertFirstChild(root);

The InsertFirstChild(root) method inserts the element at the beginning of the child nodes. In this case, we can actually also use InsertEndChild(root), which inserts the element at the end of the child nodes, because there is no child node yet.

Now we can serialize the "Engineering" Department by calling the Serialize() method that we implemented earlier.

dept.Serialize(doc, root);

Then we can save the XML document to a file.

doc.SaveFile("departments.xml");

Here is the result:

<Root>
    <Department name="Engineering">
        <Employee name="Alice" position="Engineer"/>
        <Employee name="Bob" position="Engineer"/>
    </Department>
</Root>

Now let's try to deserialize the XML document. First we need to make sure that the XML document is valid.

tinyxml2::XMLDocument loadDoc;
if (loadDoc.LoadFile("department.xml") == tinyxml2::XML_SUCCESS)
{
    // Deserialize the XML document...
}
else
{
    std::cout << "Failed to load XML file.\n";
}

Inside the if statement, we can get the root element. If the root element is not nullptr, then we can deserialize the XML document.

    tinyxml2::XMLElement *loadedRoot = loadDoc.FirstChildElement("Root");
    if (loadedRoot != nullptr)
    {
        // Deserialize the XML document...
    }
    else
    {
        std::cout << "Failed to find root element.\n";
    }

Now we can deserialize the XML document by calling the Deserialize() method that we implemented earlier.

    tinyxml2::XMLElement *loadedRoot = loadDoc.FirstChildElement("Root");
    if (loadedRoot != nullptr)
    {
        Department loadedDept = Department::Deserialize(loadedRoot->FirstChildElement("Department"));

        std::cout << "Loaded Department: " << loadedDept.name << std::endl;
        for (const auto &emp : loadedDept.employees)
        {
            std::cout << "Employee: " << emp.name << ", Position: " << emp.position << std::endl;
        }
    }
    else
    {
        std::cout << "Failed to find root element.\n";
    }

It should print out the following:

Loaded Department: Engineering
Employee: Alice, Position: Engineer
Employee: Bob, Position: Engineer

3. Example 2: External Serialization

See external_serialization1.cpp for the full code.

The code is almost the same as the previous example. The only difference is that we are using external serialization this time. So we need to implement the following functions:

  • For Employee:
void SerializeEmployee(const Employee &employee, tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent)
{
    tinyxml2::XMLElement *elem = doc.NewElement("Employee");
    elem->SetAttribute("name", employee.name.c_str());
    elem->SetAttribute("position", employee.position.c_str());
    parent->InsertEndChild(elem);
}

Employee DeserializeEmployee(tinyxml2::XMLElement *elem)
{
    const char *name = elem->Attribute("name");
    const char *position = elem->Attribute("position");
    return Employee(name ? name : "", position ? position : "");
}
  • For Department:
void SerializeDepartment(const Department &dept, tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parent)
{
    tinyxml2::XMLElement *deptElem = doc.NewElement("Department");
    deptElem->SetAttribute("name", dept.name.c_str());

    for (const auto &employee : dept.employees)
    {
        SerializeEmployee(employee, doc, deptElem);
    }

    parent->InsertEndChild(deptElem);
}

Department DeserializeDepartment(tinyxml2::XMLElement *deptElem)
{
    const char *name = deptElem->Attribute("name");
    Department dept(name ? name : "");

    for (tinyxml2::XMLElement *empElem = deptElem->FirstChildElement("Employee");
         empElem != nullptr;
         empElem = empElem->NextSiblingElement("Employee"))
    {
        dept.AddEmployee(DeserializeEmployee(empElem));
    }

    return dept;
}

And for example, now we can serialize the "Engineering" Department by calling the SerializeDepartment() method that we implemented earlier.

// Create a Department and add Employees
Department dept("Engineering");
dept.AddEmployee(Employee("Alice", "Engineer"));
dept.AddEmployee(Employee("Bob", "Engineer"));

// Serialize to XML
tinyxml2::XMLDocument doc;
tinyxml2::XMLElement *root = doc.NewElement("Root");
doc.InsertFirstChild(root);

SerializeDepartment(dept, doc, root);

// Save to file
doc.SaveFile("./results/department_embedded.xml");