Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VC9.0

Portable C++ XML Serialization

2.68/5 (6 votes)
2 Jan 2011GPL35 min read 1   763  
An open C++ library to serialize objects as XML

Introduction

XML serialization of objects is very important for a lot of applications.

C# provides an easy way to do this using the namespace System.Xml.Serialization.

However the standard C++ is poor to this feature.

Xmlser is an open source library that allows you to easily serialize/deserialize objects as XML.

Background

You should know a little about the XPATH expressions. Only the basics.

Given an XML Document:

XML
<Person first_name = "F">
	<last_name> L </last_name>
</Person>

To select Person (which the root element of the XML document), use the XPATH expression: /Person.

To select first_name which is an attribute of Person: /Person/@first_name.

As you noticed @ is used to specify an attribute.

/Person/last_name is an XPATH expression to select the last_name element.

The asterix (or star '*') is used to say anything:

  • /Person/* is equivalent in this example to /Person/last_name
  • /Person/@* is equivalent in this example to /Person/@first_name

Using the Code

Public Classes

Only 3 classes are to be used, you can find them all in xmlser.h:

  1. XmlSerializable
  2. XmlReader
  3. XmlWriter

To make your objects XML serializable, you must inherit from XMLSerializable.

As you find in the source code test.cpp, the class Point2D is XMLSerializable:

C#
class Point2D : public XmlSerializable 

Once inherited from XmlSerializable, a class should implement the two abstract members: writeXml and readXml. For the Point2D, we store as double its x and y coordinates, we should specify where (as XPATH expression) to place each of them:

C#
void writeXml(XmlWriter& ws)
{
	ws.setDouble(L"/Point2D/x",x);
	ws.setDouble(L"/Point2D/y",y);
}
C#
void readXml(XmlReader& rs)
{
	rs.getDouble(L"/Point2D/x",x);
	rs.getDouble(L"/Point2D/y",y);
}

The resulted XML to read/write is simliar to:

XML
<Point2D>
	<x> value of x as double </x>
	<y> value of y as double </y>
</Point2D>

Put attention that the strings used while doing the serialization are unicode strings (must use the prefix L for ex: L"bla bla").

Remark

The expression syntax I used to serialize objects is a subset of the XPATH syntax, not any XPATH expression can be used, but an XPATH expression with the following constraints:

  • Always starting from the root "/" (absolute expression not relative one, XPATH allows relative ones).
  • An attribute can appear only as a leaf.
  • No XPATH functions are used.
  • Use the star only at the end of the expression, @* is not allowed.

To save the XML to disk, you can easily use the method saveTo having an XmlWriter object:

C#
Point2D pt(11.3, 15.5);
XmlWriter writer;
pt.writeXml(writer);
writer.saveTo("c:\\myPt.xml");    

In fact, an XmlWriter object is an: in-memory XML document accumulator.

You can dump it any time you want using the saveTo method.

To read the XML from disk as a Point2D object, you should use an XmlReader with the loadFrom method:

C#
XmlReader reader;
Point2D pt;
reader.loadFrom("c:\\myPt.xml");
pt.readXml(reader);    

Using Attributes

You might prefer to save x as an attribute, y as an element, in this case change the implementation of readXml and writeXml in the class Point2D to the following:

C#
void writeXml(XmlWriter& ws)
{
    ws.setDouble(L"/Point2D/@x",x);
    ws.setDouble(L"/Point2D/y",y);
} 
C#
void readXml(XmlReader& rs)
{
    rs.getDouble(L"/Point2D/@x",x);
    rs.getDouble(L"/Point2D/y",y);
}

The resulted XML to read/write is similar to:

XML
<Point2D x="value of x as double">
	<y> value of y as double </y>
</Point2D>

XML Serialization for Composite Objects

Generally, an Object could be composed from another objects, this can be traduced using the XML format by putting the XML representation of the component object into the XML representation of the object composed by. For example, a rectangle is composed from 2 points. A rectangle object can be represented as XML according to the following template:

XML
<Rect2D>
           <TopLeft><Point2D><x></x><y></y><Point2D></TopLeft>
           <BottomDown><Point2D><x></x><y></y><Point2D></BottomDown>
</Rect2D>

The highlighted parts are exactly corresponding to a Point2d each of them.

The class Rect2D should have 2 point2d as explicit members or as array.

As explicit members:

C#
class Rect2D : public XmlSerializable
{
    private:
	Point2D topLeft, bottomRight;
};   

As an Array:

C#
class Rect2D : public XmlSerializable
{
    private:
    Point2D points[2];
};    

The implementation of the abstract stubs readXml and writeXml is required.
In case of explicit members:

C#
void readXml(XmlReader& rs)
{
	rs.getXml(L"/Rect2D/TopLeft",&topLeft);
	rs.getXml(L"/Rect2D/BottomRight",&bottomRight);
}
void writeXml(XmlWriter& ws)
{
	ws.setXml(L"/Rect2D/TopLeft",&topLeft);
	ws.setXml(L"/Rect2D/BottomRight",&bottomRight);
}

getXml and setXml methods provide here the reuse of the XmlSerializable Point2D objects serialization. In fact, topLeft and bottomRight members are Point2D and already have their way of read/write as XML. If members are defined as array:

C#
void readXml(XmlReader& rs)
{
	rs.getXml(L"/Rect2D/TopLeft",&points[0]);
	rs.getXml(L"/Rect2D/BottomRight",&points[1]);
}
void writeXml(XmlWriter& ws)
{
	ws.setXml(L"/Rect2D/TopLeft",&points[0]);
	ws.setXml(L"/Rect2D/BottomRight",&points[1]);
}    

Now, what if we think about an abbreviated template for the XML like the following:

XML
<Rect2D> 
           <Point2D><x></x><y></y><Point2D>
           <Point2D><x></x><y></y><Point2D>
</Rect2D>

where the first point is the top left, and the second is the bottom right one.

In this case, the code for read/write XML is:

C#
void readXml(XmlReader& rs)
{
	rs.getXml(L"/Rect2D/*",&points[0],0);
	rs.getXml(L"/Rect2D/*",&points[1],1);
}
void writeXml(XmlWriter& ws)
{
	ws.setXml(L"/Rect2D/*",&points[0]);
	ws.setXml(L"/Rect2D/*",&points[1]);
}	

You should use the * in the XPATH expression only in such cases, when you want to copy/paste another XML representation. The indices used in the getXml method (last parameter) are to specify that points[0] is the first in XML, point[1] is the second, since the indexing routine for "getter methods" is starting from 0. "setter methods" don't use any indices. Setters will never override the previous elements (in case of attributes, it will). It appends a next sibling element each time called on the same expression: I present here a tracing for the writeXml routine:

First call:

C#
ws.setXml(L"/Rect2D/*",&points[0]);
XML
<Rect2D> 
           <Point2D><x></x><y></y><Point2D> (points[0] goes here)
</Rect2D>

Second call:

C#
ws.setXml(L"/Rect2D/*",&points[1]);
XML
<Rect2D>
           <Point2D><x></x><y></y><Point2D> (points[0] still here)
           <Point2D><x></x><y></y><Point2D> (points[1] goes here)
</Rect2D>

You find in test.cpp an example for a Polygon that is composed from an array of Points, it is easy and no need to explain it.

Demo Example

The demo example located in test.cpp lets you learn how to use basically the API to serialize objects.

3 XML files are produced in the "start in" folder of the demo: f.xml, rect2d.xml, and poly2d.xml.

Third Party (All What I Did is a Thin Wrapper Around)

I used the VTD-XML open source library (XimpleWare), C version with little update for customization and solving some memory leaks.

VTD-XML is a very efficient XML parser.

Points of Interest

I used a restricted XPATH format to accomplish the general XML serialization following a simple, and easy to use approach. The code I wrote is multi-platform, although the project presented is compiled in the Windows environment. Xmlser API can deal with any XML document produced by different applications, and convert it to objects. XmlSer API can be converted to Java because there is an available version of VTD-XML written in Java.

History

  • Initial version: April/11/2008

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)