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:
<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:
XmlSerializable
XmlReader
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
:
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:
void writeXml(XmlWriter& ws)
{
ws.setDouble(L"/Point2D/x",x);
ws.setDouble(L"/Point2D/y",y);
}
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:
<Point2D>
<x> value of x as double </x>
<y> value of y as double </y>
</Point2D>
Put attention that the string
s used while doing the serialization are unicode string
s (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:
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:
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:
void writeXml(XmlWriter& ws)
{
ws.setDouble(L"/Point2D/@x",x);
ws.setDouble(L"/Point2D/y",y);
}
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:
<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:
<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:
class Rect2D : public XmlSerializable
{
private:
Point2D topLeft, bottomRight;
};
As an Array:
class Rect2D : public XmlSerializable
{
private:
Point2D points[2];
};
The implementation of the abstract
stubs readXml
and writeXml
is required.
In case of explicit members:
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:
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:
<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:
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:
ws.setXml(L"/Rect2D/*",&points[0]);
<Rect2D>
<Point2D><x></x><y></y><Point2D> (points[0] goes here)
</Rect2D>
Second call:
ws.setXml(L"/Rect2D/*",&points[1]);
<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