Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++14

Populating Structure from XML, the Automatic Way (Part 1 of 2)

3.40/5 (6 votes)
30 Jan 2016CPOL6 min read 25.1K   183  
intros_ptree: A library that lets you populate your structure or class from XML file (or json or ini file) automatically, and vice versa

Introduction

Modern C++ is a game changer for C++ programming language. We can do some real cool stuff with it. Here, I present to you one such thing. Using boost property tree library and some modern C++ techniques, I will show you how we can populate a C++ structure or class object from xml, json or ini file automatically (almost) and vice versa.

This is a two part article series:

  1. In this part, I will show you how to use intros_ptree library.
  2. And in the second part (link), we will go into the details of how the library works.

Even if you don’t care about xml files, you might still enjoy the article by watching the cool stuff that we can achieve using modern C++.

Attached with the article is intros_ptree library, with examples on how to use it.

Prerequisites

Compiler: We will need a modern C++ compiler to use this library. I have tested this code against VS2015 update 1, GCC 5.2 and clang 3.6.

3rd party libraries: Boost. I have been using boost 1.60 while developing this library.

Before We Begin

This is a small C++ header only library. You can get the latest version from here.

You will need to add the following to compile the examples presented in this article:

C++
#include <iostream>
#include <intros_ptree.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

using namespace std;
using namespace utils::intros_ptree;
using namespace boost::property_tree;

All the examples in this article are available in utils_examples.cpp file.

In the examples, I will be referring to xml file only, but the same applies for json and ini file too.

Using the Code

A Quick Introduction

Boost property tree library can open an xml, json or ini file and populate a property tree. Here is how we load a property tree from an XML file:

C++
ptree tree;
read_xml(filename, tree);

And here is how we write the tree to an XML file:

C++
write_xml(filename, tree);

And by using intros_ptree library, we can populate a structure from the tree and vice versa.

First, let’s create a structure that we want to be populated from the tree:

C++
struct test
{
    int x;
    string y;
;

Now, we need to define some internal data structure which will be used by the library (the almost part):

C++
BEGIN_INTROS_TYPE(test)
    ADD_INTROS_ITEM(x)
    ADD_INTROS_ITEM(y)
END_INTROS_TYPE(test)

Now, to populate a test object from the tree, all we have to do is:

C++
auto a = make_intros_object<test>(tree);

And, to populate a property tree from an object:

C++
ptree tree = make_ptree(a);

And that’s it.

Let’s summarize the whole thing now with a concrete example.

Here is an example data:

C++
string sample_xml = R"(
<book>
    <id>102</id>
    <name>i am a book</name>
    <author>i am a writer</author>
</book>
)";

Here, we define the structure to hold the data:

C++
struct book
{
    int id;
    string name;
    string author;
};

We need to declare some internal data structure for the library:

C++
BEGIN_INTROS_TYPE(book)
    ADD_INTROS_ITEM(id)
    ADD_INTROS_ITEM(name)
    ADD_INTROS_ITEM(author)
END_INTROS_TYPE(book)

Now, to populate a book object, all we have to do is:

C++
ptree tree;
stringstream ss(sample_xml);
read_xml(ss, tree);
auto ob = make_intros_object<book>(tree);

And to write the data in XML file, all we have to do is:

C++
ob.name = "new name"; // lets make some modification before writing
auto tree2 = make_ptree(ob);
xml_writer_settings<string> settings(' ', 4); 	// this is needed for xml printing 
						// to have proper whitespace
write_xml(cout, tree2, settings);

Description

intros_ptree library gives us the following tools to work with it:

C++
template<typename T>
boost::property_tree::ptree make_ptree(const T& in);

This function will accept any structure or class, for which intros structure is defined. And it will return the ptree for it.

C++
template<typename T>
T make_intros_object(const boost::property_tree::ptree& tree);

This function will accept a ptree, and will create a structure or class of type T, as long as, intros structure is defined for type T.

So, we see, the functions let us get a ptree from a type for which intros structure is defined, and vice versa.

But to do this, we need to define the intros structure. That’s where, the macros come in. Let’s see what they can do.

C++
BEGIN_INTROS_TYPE(type)

With this macro, you start defining the intros structure for your type.

C++
END_INTROS_TYPE(type)

With this macro, you end your declaration for your type.

C++
ADD_INTROS_ITEM(x)

With this macro, you add an item from your type to the intros structure.

C++
BEGIN_INTROS_TYPE_USER_NAME(type, name)

You will use this macro instead of BEGIN_INTROS_TYPE when you need to set the name yourself, instead of using the default one. The default name is the name for your type.

C++
ADD_INTROS_ITEM_USER_NAME(x, name)

Just like BEGIN_INTROS_TYPE_USER_NAME, you will use ADD_INTROS_ITEM_USER_NAME instead of ADD_INTROS_ITEM when you need to use a different user name from the default one. The default user name is the name of your variable.

C++
MAKE_USER_NAME(name, scope, is_attribute)

You will use this macro as the second parameter for ADD_INTROS_ITEM_USER_NAME. You will need this, when you want to say, the item is in a different scope, or when you want to mark your item as an xml attribute. We will see some more of what they do in the coming examples.

Some More Examples

Once we have intros support for our type, populating it from an xml file, or saving it into an xml file is trivial. Here is how we have to do it:

C++
// here is how we populate a structure
ptree tree;
read_xml(xml_file_name, tree);
auto ob = make_intros_object<MyStruct>(tree);

// here is how we write the data to xml file
auto tree2 = make_ptree(ob);
xml_writer_settings<string> settings(' ', 4);
write_xml(xml_file_name, tree2, settings);

The interesting part is, how we add intros support for our type. In the following examples, we will see how we have to add intros support in different scenarios.

  1. Structure name and xml tag are different:

    Let’s say, our xml data is like the following:

    C++
    string sample_xml = R"(
    <root>
        <id>102</id>
        <name>i am a book</name>
        <author>i am a writer</author>
    </root>
    )";

    And our structure is this:

    C++
    struct book
    {
        int id;
        string name;
        string author;
    };

    The xml tag says root, but the structure name is book. Then we need to define intros structure like below:

    C++
    BEGIN_INTROS_TYPE_USER_NAME(book, "root")
        ADD_INTROS_ITEM(id)
        ADD_INTROS_ITEM(name)
        ADD_INTROS_ITEM(author)
    END_INTROS_TYPE(book)

    Lesson: Use BEGIN_INTROS_TYPE_USER_NAME when you need to use a name other than the type name. The second parameter is the new name you want to give your type.

  2. Item name and xml tag are different:

    This time, our xml structure is like below:

    C++
    string sample_xml = R"(
    <book>
        <book_id>102</book_id>
        <name>i am a book</name>
        <author>i am a writer</author>
    </book>
    )";

    And the structure is like below:

    C++
    struct book
    {
        int id;
        string name;
        string author;
    };

    Here, we need to map xml tag book_id with book::id. Here is, how we do this:

    C++
    BEGIN_INTROS_TYPE(book)
        ADD_INTROS_ITEM_USER_NAME(id, "book_id")
        ADD_INTROS_ITEM(name)
        ADD_INTROS_ITEM(author)
    END_INTROS_TYPE(book)

    Lesson: Use ADD_INTROS_ITEM_USER_NAME when you want to use a name other than the variable name for your variable.

  3. Multiple elements:

    This time, our xml structure is like below:

    C++
    string sample_xml = R"(
    <book>
        <book_id>102</book>
        <name>i am a book</name>
        <author>i am writer 1</author>
        <author>i am writer 2</author>
        <author>i am writer 3</author>
    </book>
    )";

    And our structure is this:

    C++
    struct book
    {
        int id;
        string name;
        vector<string> author;
    };

    For this, we will define the intros structure like below:

    C++
    BEGIN_INTROS_TYPE(book)
        ADD_INTROS_ITEM_USER_NAME(id, "book_id")
        ADD_INTROS_ITEM(name)
        ADD_INTROS_ITEM(author)
    END_INTROS_TYPE(book)

    Lesson: You can use containers just as easily as you would use other types.

  4. Adding scope to xml tags:

    Let’s say, our xml data is like below this time:

    C++
    string sample_xml = R"(
    <book>
        <book_id>102</book>
        <name>i am a book</name>
        <all_authors>
            <author>i am writer 1</author>
            <author>i am writer 2</author>
            <author>i am writer 3</author>
        </all_authors>
    </book>
    )";

    Here, author element is added under a new tag, all_authors. To support this, we need to define the intros structure like below:

    C++
    BEGIN_INTROS_TYPE(book)
        ADD_INTROS_ITEM_USER_NAME(id, "book_id")
        ADD_INTROS_ITEM(name)
        ADD_INTROS_ITEM_USER_NAME(author, MAKE_USER_NAME("author", "all_authors", false))
    END_INTROS_TYPE(book)

    Lesson: You can put an item to another scope (under a new xml element), with the help of MAKE_USER_NAME. Here, the first parameter is the name you want for your variable, and the second parameter is the scope name. The third parameter we will see in the next example.

  5. Using xml attribute:

    This time, our xml data is like this:

    C++
    string sample_xml = R"(
    <book book_id="102">
        <name>i am a book</name>
        <author>i am a writer</author>
    </book>
    )";

    And structure is this:

    C++
    struct book
    {
        int id;
        string name;
        string author;
    };

    This time, we need to define the intros structure like below:

    C++
    BEGIN_INTROS_TYPE(book)
        ADD_INTROS_ITEM_USER_NAME(id, MAKE_USER_NAME("book_id", "", true))
        ADD_INTROS_ITEM(name)
        ADD_INTROS_ITEM(author)
    END_INTROS_TYPE(book)

    Lesson: The third parameter of MAKE_USER_NAME marks the item as an attribute.

  6. Using type inside another type:

    We can get intros support for a type that uses another type inside it, as long as, both of the types have intros support.

    This time, our xml data is like below:

    C++
    string sample_xml = R"(
    <catalog>
        <name>I am the catalog</name>
        <book>
            <id>102</id>
            <name>i am a book</name>
            <author>i am writer 1</author>
        </book>
        <book>
            <id>103</id>
            <name>i am also book</name>
            <author>i am writer 2</author>
        </book>
        <book>
            <id>104</id>
            <name>i am another book</name>
            <author>i am writer 3</author>
        </book>
    </catalog>
    )";

    And now, our structures are like this:

    C++
    struct book
    {
        int id;
        string name;
        string author;
    };
    struct catalog
    {
        string name;
        vector<book> books;
    };

    Intros definition for book is just like before:

    C++
    BEGIN_INTROS_TYPE(book)
        ADD_INTROS_ITEM(id)
        ADD_INTROS_ITEM(name)
        ADD_INTROS_ITEM(author)
    END_INTROS_TYPE(book)

    And intros definition for catalog is also, what we would assume should be:

    C++
    BEGIN_INTROS_TYPE(catalog)
        ADD_INTROS_ITEM(name)
        ADD_INTROS_ITEM_USER_NAME(books, "book")
    END_INTROS_TYPE(catalog)

    Lesson: We can use one type inside another type, for intros_ptree, as long as, both the types has intros support.

Additional Notes

  1. C style array (ex. int a[10]) is not supported as intros_item.
  2. intros definition must be in a global scope.
  3. We cannot give a type two intros definitions. That is, for a type T, we can provide only one BEGIN_INTROS_TYPE/END_INTROS_TYPE block.

Debug Tip

As you can see from the above examples, defining intros struct is quite intuitive. And anytime, you feel you are not sure whether the intros struct you defined is correct or not, you can print it and see the result. Let me give you an example of that with the above catalog structure:

C++
catalog c;
c.books.resize(2);
ptree tree = make_ptree(c);
xml_writer_settings<string> settings(' ', 4); 	// this is needed for xml printing 
						// to have proper whitespace
write_xml(cout, tree, settings);

And this will print:

XML
<?xml version="1.0" encoding="utf-8"?>
<catalog>
    <name/>
    <books>
        <id>0</id>
        <name/>
        <author/>
    </books>
    <books>
        <id>0</id>
        <name/>
        <author/>
    </books>
</catalog>

This way, we can check, if we have the xml format to our liking.

And that’s the end of part 1. In the next part, I will show you, how intros_ptree library achieves this using modern C++.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)