Skip to main content
gamedev.net
🔒 Locked

Presenting TinyXMLHelper

Started by jdhardy 3 replies 2.7k views
Original Post
jdhardy
jdhardy
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: For TinyXMLHelper to work properly, TIXML_USE_STL must be defined.

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;

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.

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;
}


Now let's examine this step by step, starting at the top.

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);
}


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.

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);
}


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.

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;


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.

	Xml::iterator< child<element> > end;


Then, an iterator to represent the end of the sequence is created (using the default constructor)...

	Xml::iterator< child<element> > it(doc_root);


...and an iterator for iterating over the children of the root element.

	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;
}


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

Limitation

As the previous example hopefully showed, the helper library can make some tings easier. However, it does not support the same functionality as the already existing TiXmlHandles, nor does it provide support for modifying nodes in the DOM. It can only be used for reading from files. Thus, it merely 'helps' you with a subset of TinyXML's full capabilities, and does not attempt to provide a full interface.

Final Words

This library is licenced under a zlib-style licence, which basically states that you can do whatever you want with it, as long as you don't claim that you wrote it. I would like to know who is using it, however, so the least I expect is an email. I can be reached at jdhardy@gmail.com. EDIT: Can we please have post preview?
daerid
daerid
Quote:
Original post by ze_jackal
TinyXML was designed to be portable, and thus does not make use of advanced C++ features like templates, RTTI, or namespaces.


Jebus Christo... statements like these make me wanna vomit. Namespaces, templates, and RTTI are all well-defined aspects of the C++ standard. They are no longer anything that inhibits portability.

It ain't 1997 anymore, peeps.
daerid@gmail.com
Puzzler183
Puzzler183
Sadly enough, many compilers still aren't very standards compliant.
Kylotan
Kylotan
As recently as last summer I was working on a platform which had no iostream implementation (Pocket PC 2002). I don't think it had namespaces either but I could be mistaken. So I do think it's a fair enough goal to restrict usage of these things if you can do it without unnecessary code obfuscation.
jdhardy
jdhardy
Quote:
Original post by daerid
Quote:
Original post by ze_jackal
TinyXML was designed to be portable, and thus does not make use of advanced C++ features like templates, RTTI, or namespaces.


Jebus Christo... statements like these make me wanna vomit. Namespaces, templates, and RTTI are all well-defined aspects of the C++ standard. They are no longer anything that inhibits portability.

It ain't 1997 anymore, peeps.
Tell that to the TinyXML guys. In all fairness, I think TinyXML has been around since '97.

The library has been through a few major changes, but it still looks like C++ as written by a C programmer. If it seems like people like this much, I'll send it to Lee and see what he says. Maybe it's time to make TinyXML not so tiny.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.