XML is a general structured format to store and exchange hierarchical data.
If you know HTML, you'll find XML quite similar (in fact, after some small modifications, a HTML file is a valid XML file): XML uses nested tags of the form <tagname>...</tagname> for tags with contents and <tagname/> for tags without content. Each tag can contain other tags, and the tag itself can have attributes of the form <tagname attribute=value>...</tagname>.
The name of the tags is not restricted (unlike HTML, which only defines a given set of proper HTML tags), so you can choose whatever name fits your needs.
As an example, let us assume that you want to store holiday information into a file and use Qt to load or modify it. To get a feeling for how XML looks like, here is one possible format for such a holiday file:
<?xml version='1.0' encoding='UTF-8'?>
<holidayset country="at">
<name>Holidays for Austria</name> <holiday> <name>New Year's Day</name> <date>2007-01-01</date> </holiday> <holiday> <name>Christmas</name> <date>2007-12-24</date> </holiday>
</holidayset> This file defines a holiday set for Austria (notice the country="at" attribute to the holidayset tag). The holiday set, enclosed in <holidayset>...</holidayset> contains two holidays, each enclosed with <holiday>...</holiday>. Each of these holiday elements contains the settings for that holiday enclosed in appropriately named tag.
Such an XML file can be represented as a tree structure, with the XML document being the root of the tree, and each subelement/attribute/text value is a child of it's enclosing XML element. The tree structure corresponding to the holiday file above looks like the following:
Notice that we did use the same tag <name> inside the <holidayset> and inside the <holiday> tags. We used quite generic names for the tags, which might become a problem with complexer structure, when we want to use the same name for different purposes. For this reason, XML also defines namespaces to allow for the same name (but from a different namespace, so they are actually different names) used in different context. In a later section we will look at these namespaces.
Also note that we implicitly used specially formated (ISO-formatted) contents for the date tags, without yet specifying it. Of course we could give any other value, say <date>I hope never</date>, and it would still be a valid XML file, but the parser will not be able to interpret the value as a date. Add such constraints and specific formats / values / value ranges for elements is possible using either a [[wikipedia>DTD]] or an XML Schema. If you have such a definition, a validating parser can check whether a given XML file really adheres to the document structure defined in that schema. Unfortunately, the Qt XML/DOM classes are not validating parsers, so you cannot validate XML documents against a given schema with Qt.
We will use the example from above throughout this tutorial. In our application, we want to store the holiday set in the following class:
class Holiday
{
public:
Holiday() {} ~Holiday() {}
QDate mDate; QString mName;
};
class HolidaySet { public:
HolidaySet( const QString &c ) : mCountry( c ) {} ~HolidaySet() {}
QString mCountry, mName; QList<Holiday> mHolidays;
};
In production code, you would not make the member variables public and directly access them, but rather add accessors and setter functions:
QDate date() { return mDate; }
void setDate( const QDate &date ) { mDate = date; }
To save space, I decided to neglect that rule of thumb here in this example. As this is a tutorial for XML and Qt DOM, I want to concentrate on the basics of Qt DOM and not on a good general programming style.
As there are only so many sensible names, sooner or later you will find out that you will use the same tagname or attribute name for different cases with different meanings. That is the point where namespaces come in.
Let us first look at how to use the Qt classes to generate the XML for the holiday file from the HolidaySet class that you have in memory. For this purpose, Qt offers the classes QDomDocument to represent the whole document and QDomNode and QDomElement to represent each individual tag and attribute.
To understand the code below, one has to be aware that DOM is actually a well-defined API to work with and modify XML documents. That is also the reason why the code above, in particular the addElement method, is not as beautiful as usual Qt-using code is. Instead, the code will be more or less identical in whatever programming language you use.
The XML document is described by a on object of the class QDomDocument with methods to create new elements. The general flow of building up a DOM tree is as follows:
As a line of code says more then a thousand words, let us look at some sample code to generate the DOM tree from the HolidaySet class:
/* Helper function to generate a DOM Element for the given DOM document
and append it to the children of the given node. */
QDomElement addElement( QDomDocument &doc, QDomNode &node,
const QString &tag, const QString &value = QString::null )
{
QDomElement el = doc.createElement( tag ); node.appendChild( el ); if ( !value.isNull() ) { QDomText txt = doc.createTextNode( value ); el.appendChild( txt ); } return el;
}
QString holidaySetToXML( const HolidaySet &hs )
{
QDomDocument doc;
// generate the <holidayset> tag as the root tag, add the country // attribute if needed QDomElement holidaySetElement = addElement( doc, doc, "holidayset" ); if ( !hs.mCountry.isEmpty() ) holidaySetElement.setAttribute( "country", hs.mCountry ); // Add the <name> and <comment> elements to the holidayset if ( !hs.mName.isEmpty() ) addElement( doc, holidaySetElement, "name", hs.mName );
// Add each holiday as a <holiday>..</holiday> element QList<Holiday>::iterator i; for ( i = hs.mHolidays.begin(); i != hs.mHolidays.end(); ++i) { QDomElement h = addElement( doc, holidaySetElement, "holiday" ); addElement( doc, h, "name", (*i).mName ); addElement( doc, h, "date", (*i).mDate.toString( Qt::ISODate ) ); }
return doc.toString();
}
One thing to notice is that all DOM nodes are passed by value (since some programming languages do not define pointers, the DOM API cannot use any pointer-based functionality!).
Let us now slowly step through the code:
addElement( doc, node, "tag", "contents").
<holidayset/>
<holidayset country="at"/>
You can now create the XML file contents simply via
// Create the data structure
HolidaySet hs("at");
hs.mName="Holidays for Austria";
Holiday h;
h.mDate = QDate( 2007, 01, 01 ); h.mName = QString( "New Year" ); hs.mHolidays.append( h );
h.mDate = QDate( 2006, 12, 24 ); h.mName = QString( "Christmas" ); hs.mHolidays.append( h );
// convert to the XML string QString output = holidaySetToXML( hs ); // output that XML string qDebug()<<output;
Let us now look at loading an XML file into memory and parsing it into our HolidaySet memory structure. There are two different strategies for loading XML documents:
From the description above it is clear that SAX can only be used to load an XML file, while DOM can also be used to build up or modify existing XML files. In fact, we already did exactly that in the previous chapter where we created the holiday file.
HolidaySet parseXMLwithDOM( QDomDocument &domTree )
{
HolidaySet hs( QString::null ); QDomElement set = domTree.namedItem("holidayset").toElement(); if ( set.isNull() ) { qWarning() << "No <holidayset> element found at the top-level of the XML file!"; return hs; // no holiday set found } if ( set.hasAttribute("country") ) { hs.mCountry = set.attribute("country"); } // Way 1: Explicitly search for a given element: QDomElement name = set.namedItem("name").toElement(); if ( !name.isNull() ) { // We have a <name>..</name> element in the set hs.mName = name.text(); }
// Way 2: Loop through all child nodes with a given tag name. QDomElement n = set.firstChildElement( "holiday" ); for ( ; !n.isNull(); n = n.nextSiblingElement( "holiday" ) ) { Holiday h; QDomElement e = n.toElement(); QDomElement v = e.namedItem("name").toElement(); if ( !v.isNull() ) h.mName = v.text(); v = e.namedItem("date").toElement(); if ( !v.isNull() ) { h.mDate = QDate::fromString( v.text(), Qt::ISODate ); } hs.mHolidays.append( h ); }
// Way 3: Loop through all child nodes and check if it is an element // with one of the wanted tagnames QDomNode nd = set.firstChild(); for ( ; !nd.isNull(); nd = nd.nextSibling() ) { if ( nd.isElement() && nd.toElement().tagName() == "holiday" ) { QDomElement n = nd.toElement(); // Same code as above... } }
return hs;
}
<?xml version='1.0' encoding='UTF-8'?>
<h:holidays xmlns:h="urn:kde:developer:tutorials:QtDom:holidays" h:country="at">
<h:holiday> <h:name>New Year's Day</h:name> <h:date>2007-01-01</h:date> </h:holiday> <h:holiday> <h:name>Christmas</h:name> <h:date>2007-12-24</h:date> </h:holiday>
</h:holidays>
(Copyright David Faure, put under the GPL)
KoXmlElement KoDom::namedItemNS( const KoXmlNode& node, const char* nsURI, const char* localName )
{
KoXmlNode n = node.firstChild(); for ( ; !n.isNull(); n = n.nextSibling() ) { if ( n.isElement() && n.localName() == localName && n.namespaceURI() == nsURI ) return n.toElement(); } return KoXmlElement();
}
From KODom.h (by dfaure):
/**
* This namespace contains a few convenience functions to simplify code using QDom * (when loading OASIS documents, in particular). * * To find the child element with a given name, use KoDom::namedItemNS. * * To find all child elements with a given name, use * QDomElement e; * forEachElement( e, parent ) * { * if ( e.localName() == "..." && e.namespaceURI() == KoXmlNS::... ) * { * ... * } * } * Note that this means you don't ever need to use QDomNode nor toElement anymore! * Also note that localName is the part without the prefix, this is the whole point * of namespace-aware methods. * * To find the attribute with a given name, use QDomElement::attributeNS. * * Do not use getElementsByTagNameNS, it's recursive (which is never needed in KOffice). * Do not use tagName() or nodeName() or prefix(), since the prefix isn't fixed. * * @author David Faure <faure@kde.org> */
Initial Author: Reinhold Kainhofer