Original Post
The following is reprinted at http://www.ualberta.ca/~jdhardy/tinyxmlhelper. It is included here to save the effort of an extra click. Any and all comments, suggestions, or constructive criticism is encouraged. TinyXML is a very handy library for parsing XML files. It provides a good portion of the functionality required to handle modest XML files. However, the interface is not very intuitive to certain functions. While everything described here can certainly be accomplished with raw TinyXML commands, these functions were designed to eliminate tedious typing and to provide a simpler, more C++-style approach to handling XML documents. TinyXML was designed to be portable, and thus does not make use of advanced C++ features like templates, RTTI, or namespaces. This helper library requires all of those to achieve the functionality, so it may not be as portable as TinyXML itself. It has been tested under gcc 3.2 (Dev-C++) and MSVC++ 8 (Whidbey) Express beta. The primary purpose of the library is to simplify situations with a significant amount of iteration over unknown elements. If the elements are known (such as in a configuration file), then TinyXML's handles provide a sufficient abstraction that they do not need to be wrapped. Thus, the library is designated a 'helper' rather than a 'wrapper'. It is possible, though unnecessary, to completely avoid TinyXML altogether; however, this is more of a side-effect than an explicit goal of the library. There are three options for downloading the library: The default constructor (above, for example) will create an iterator that can be used to represent the end of an iteration sequence. Note: the std namespace also include an iterator class. Therefore, it is a good idea to always qualify the namespace on Xml::iterator, and it is required if both the Xml and std namepsace are imported. Now let's examine this step by step, starting at the top. One of the requirements of the library is that each element in an XML file must be represented as a class in code. So, we define a class entity to hold our entities, with the attributes value1 and value2 as members. Then, a loader is added that will retrieve the data contained in an entity. An entity contains a pair of attributes which contain strings, so we use Xml::attribute() to get them, specifying that they are std::strings. Xml::attribute() returns a value of the type requested. We also want some text, so we declare a structure to hold it. This is used to demonstrate how the filtering works. If you look at the XML file, you'll notice that there is a <bold> tag present. This is an element that is a child of the <text> tag, and a sibling of the strings on either side. The main part of the program loads an XML document and then gets the first (root) element of the document. Xml::document can also take an input stream and read from it. Then, an iterator to represent the end of the sequence is created (using the default constructor)... ...and an iterator for iterating over the children of the root element. Iterating over the XML is the same as any other container. Xml::has_value() is used to match iterators to types to allow them to be loaded. Then Xml::tag() can be used to read the data into the necessary structure. Xml::tag(), like Xml::attribute(), returns a value of the type requested
- the file itself: tinyxmlhelper.hpp
- with documentation: tinyxmlhelper-doc.zip
- with documentation, example, and TinyXML: tinyxmlhelper.zip
Structure
Nodes
Nodes are the fundamental unit of an XML file. There are various types of nodes (such as elements, comments, declarations, etc.), all of which may contain data. The library provides wrappers for the various TinyXML node classes that also double as the node policies described in the next section. These wrappers are provided to avoid some of the pointer mess-work associated with TinyXML.Iterator
The most important part of the helper is the Xml::iterator class, which provides a STL-compliant iterator over parts of the DOM. The iterator uses template policies to determine how to iterate over the elements. The two necessary policies are an iteration policy and a node policy. (The iteration policy is specified in terms of the node policy). The iteration policy determines how to iterate over the elements. For example, the two provided iteration policies, Xml::child and Xml::sibling, iterate over the children of the given node and siblings of the given node, respectively. The node policy determines which nodes are iterated over. A given node in an XML document might have several children of varying types - such as elements, text, and comments. Often, only one of these is desired at a particular time - elements, for example. Node policies restrict iteration to the requested type. The provided polices are Xml::element, Xml::comment, Xml::text, Xml::declaration, Xml::unknown (for nodes the TinyXML doesn't recognize), and Xml::document. A special policy, Xml::node, is provided which will iterate over all children, in case the user desires the functionality.
Xml::iterator< child<element> > it;
Data retrieval
XML files are only useful if data can retireved from them. To this end, the helper library provides a set of functions that retrieve data from the XML DOM: Xml::attribute(), Xml::tag(), Xml::childTag(), and Xml::getText(). Xml::attribute() is used to extract attributes from elements. Any type can be retreived from an attribute, as long as the type is InputStreamable (ie, it provides operator>>). Xml::tag() is used to extract data from an element. This is usually used when iterating over a set of elements. Xml::childTag() is used when the name of a child element is known, but there is no need to iterate over all child nodes. Xml::getText() is used to return the text in a Xml::text() node. Xml::tag() and Xml::childTag() both use loaders to retrieve data from elements. Loaders are simply functions that take a const Xml::element& paramater and return a value of the type requested. What occurs in these functions is up to the user; however, they must throw an exception to be handled by the user on error. The library does not handle loader failure.Examples
Let's say that we need to parse the following XML file:
<test>
<entity value1="1" value2="2"/>
<entity value1="3" value2="4"/>
<nonentity value1="54" value2="90"/>
<text>Mystery <bold>bold</bold> text!</text>
<entity value1="7" value2="8"/>
</test>
The following code could be used to do this:
struct entity
{
string value1;
string value2;
};
entity entity_loader(const element &elem)
{
entity ent;
ent.value1 = attribute<string>(elem, "value1");
ent.value2 = attribute<string>(elem, "value2");
return(ent);
}
struct xtext
{
string value;
};
xtext xtext_loader(const element &elem)
{
xtext t;
t.value = "";
Xml::iterator< child<text> > end;
Xml::node n(elem);
for(Xml::iterator< child<text> > it(elem); it != end; ++it)
{
t.value += getText(*it);
}
return(t);
}
int main(int argc, char *argv[])
{
document doc("test.xml");
element doc_root = doc->RootElement();
entity ent;
xtext t;
Xml::iterator< child<element> > end;
Xml::iterator< child<element> > it(doc_root);
for(; it != end; ++it)
{
if(has_value(it, "entity"))
{
ent = tag<entity>(*it, entity_loader);
cout<<"("<<ent.value1<<","<<ent.value2<<")"<<endl;
}
else if(has_value(it, "text"))
{
t = tag<xtext>(*it, xtext_loader);
cout<<"("<<t.value<<")"<<endl;
}
}
return 0;
}
struct entity
{
string value1;
string value2;
};
entity entity_loader(const element &elem)
{
entity ent;
ent.value1 = attribute<string>(elem, "value1");
ent.value2 = attribute<string>(elem, "value2");
return(ent);
}
struct xtext
{
string value;
};
xtext xtext_loader(const element &elem)
{
xtext t;
t.value = "";
Xml::iterator< child<text> > end;
Xml::node n(elem);
for(Xml::iterator< child<text> > it(elem); it != end; ++it)
{
t.value += getText(*it);
}
return(t);
}
Xml::element text
Xml::text "Mystery "
Xml::element <bold>
Xml::text "bold"
Xml::text "text!"
In this case, only the text is iterator over. If you needed the <bold>g tag, then you either have to iterate over the list, looking for Xml::elements, or iterate using Xml::nodes and do determination from there.
int main(int argc, char *argv[])
{
document doc("test.xml");
element doc_root = doc->RootElement();
entity ent;
xtext t;
Xml::iterator< child<element> > end;
Xml::iterator< child<element> > it(doc_root);
for(; it != end; ++it)
{
if(has_value(it, "entity"))
{
ent = tag<entity>(*it, entity_loader);
cout<<"("<<ent.value1<<","<<ent.value2<<")"<<endl;
}
else if(has_value(it, "text"))
{
t = tag<xtext>(*it, xtext_loader);
cout<<"("<<t.value<<")"<<endl;
}
}
return 0;
}