Cinder provides the
For some of the example code in this document, we'll refer to this very basic XML document:
<?xml version="1.0"?&>
<library>
<owner>
<name>Andrew Bell</name>
<city>New York</city>
</owner>
<album musician="John Coltrane" year="1989">
<title>Ole Coltrane</title>
<track id="0">Ole</track>
<track id="1">Dahomey Dance</track>
<track id="2">Aisha</track>
<track id="3">To Her Ladyship</track>
</album>
<album musician="Burial + Four Tet" year="2009">
<title>Moth/Wolf Club</title>
<track id="0">Moth</track>
<track id="1">Wolf</track>
</album>
</library>
To parse a block of XML, construct an XmlTree using a
XmlTree doc( loadFile( "/Users/andrewfb/music.xml" ) );
To parse XML on an http server, use
XmlTree doc( loadUrl( "http://rss.news.yahoo.com/rss/tech" ) );
To parse XML contained in a resource (discussed in more depth here), use
XmlTree doc( loadResource( RES_MUSIC_LIBRARY ) );
To parse XML contained in a string:
std::string myXmlStr( "<?xml version=\"1.0\"?>\n<library>\n<owne…"
XmlTree doc( myXmlStr );
To grab a particular child of a node by name, use the
XmlTree doc( loadFile( "/Users/andrewfb/music.xml" ) );
XmlTree musicLibrary = doc.getChild( "library" );
If the child does not exist, a
std::string myXmlStr( '<?xml version="1.0"?> <library><owne…' );
XmlTree doc( myXmlStr );
XmlTree also supports finding children by path, where each component of the path is separated by the /
character. For example:
console() << musicLibrary.getChild( "owner/city" ) << std::endl;
Output:
<city>New York</city>
The example above shows a convenient way to examine an XML node, which is to pass it to a std::ostream like
XmlTree ownerCity = doc.getChild( "library/owner/city" );
console() << "Path: " << ownerCity.getPath() << " Value: " << ownerCity.getValue() <<std::endl;
Output:
Path: library/owner/city Value: New York
To iterate the children of an XML node, use the
XmlTree firstAlbum = doc.getChild( "library/album" );
for( XmlTree::Iter child = firstAlbum.begin(); child != firstAlbum.end(); ++child )
console() << "Tag: " << child->getTag() <<" Value: " << child->getValue() << endl;
Output:
Path: library/owner/city Value: New York
Tag: title Value: Ole Coltrane
Tag: track Value: Ole
Tag: track Value: Dahomey Dance
Tag: track Value: Aisha
Tag: track Value: To Her Ladyship\endcode
You can also iterate the children of a node with the path syntax. For example, to iterate all the tracks of the music library, we can do something like the code below. Notice that the XmlTree::Iter is smart about finding all the nodes which match the path - there are tracks from both albums, not just the first.
for( XmlTree::Iter track = doc.begin("library/album/track"); track != doc.end(); ++track )
console() << track->getValue() << endl;
Output:
Ole
Dahomey Dance
Aisha
To Her Ladyship
Moth
Wolf
By default paths for the XmlTree are case insensitive. An optional boolean following the path allows you to force case sensitivity. Also for the uncommon case in which your node tags contain the '/' character, you can supply an alternate separator - we use the '.' below:
std::string ownerCity = xmlNode.getChild( "library.owner.city", true, '.' );
As we've already seen, you can get the tag (or name) of a node by calling
vector<float> myFloats;
for( XmlTree::Iter item = xml.begin(); item != xml.end(); ++item )
myFloats.push_back( item->getValue<float>() );
The XmlTree also offers facility for walking a node's attributes. To get the value of an attribute as a string, you can call
XmlTree firstAlbum = doc.getChild( "library/album" );
console() << "the musician is: " << firstAlbum.getAttributeValue( "musician" ) << endl;
There is also a variant which supports automatic type conversion:
XmlTree firstTrack = doc.getChild( "library/album/track" );
int firstTrackId = myNode.getAttributeValue<int>( "id" );
As an additional convenience, you can supply a default value in the case that a node does not have a particular attribute. If we wanted our default size to be 1 for nodes which do not posses a size attribute, we would do this:
float mySize = myNode.getAttributeValue<float>( "size", 1.0f );
The XmlTree class can be used to build and write XML documents as well. The example below creates a music library with one album and prints it to the
XmlTree library( "library", "" );
XmlTree album( "album", "" );
album.setAttribute( "musician", "Sufjan Stevens" );
album.setAttribute( "year", "2004" );
album.push_back( XmlTree( "title", "Seven Swans" ) );
album.push_back( XmlTree( "track", "All the Trees of the Field Will Clap Their Hands" ) );
album.push_back( XmlTree( "track", "The Dress Looks Nice on You" ) );
album.push_back( XmlTree( "track", "In the Devil's Territory" ) );
album.push_back( XmlTree( "track", "To Be Alone With You" ) );
library.push_back( album );
console() << library << std::endl;
Output:
<library>
<album musician="Sufjan Stevens" year="2004">
<title>Seven Swans</title>
<track>All the Trees of the Field Will Clap Their Hands</track>
<track>The Dress Looks Nice on You</track>
<track>In the Devil's Territory</track>
<track>To Be Alone With You</track>
</album>
</library>
Notice that the node echoed to the console was not treated as an XML document - it lacks the <?xml>
declaration of a true XML document. There are a couple of ways of achieving this. The simplest is to use
library.write( writeFile( "~/musicOutput.xml" ) );
This routine has an optional second boolean parameter which will create the <?xml>
declaration when true, its default value. Another option would be to create a document node ourselves and append the <library>
to that:
XmlTree doc = XmlTree::createDoc();
XmlTree library( "library", "" );
…
doc.push_back( library );
console() << doc << std::endl;
The XmlTree is designed to be const-correct, and supports a ConstIter which mimicks the const_iterator
of STL containers:
// Finds the track named \a searchTrack in the music library \a library.
// Throws XmlTree::ExcChildNotFound() if none is found.
const XmlTree& findTrackNamed( const XmlTree &library, const std::string &searchTrack )
{
for( XmlTree::ConstIter trackIt = library.begin("album/track"); trackIt != library.end(); ++trackIt ) {
if( trackIt->getValue() == searchTrack )
return *trackIt;
}
// failed to find a track named 'searchTrack'
throw XmlTree::ExcChildNotFound( library, searchTrack );
}
…
XmlTree doc( loadFile( "/Users/andrewfb/library.xml" ) );
console() << findTrackNamed( doc.getChild( "library" ), "Wolf" ) << std::endl;
Output:
It's also worth noting the value of passing XmlTrees by reference whenever possible. XmlTrees create a full copy of the XML data tree whenever they are copied, so passing by reference can improve performance significantly. Furthermore, assigning by copy will prevent us from modifying the "original" node of an XmlTree should we so desire. For example:
// Whoops - assignment by value doesn't modifying the original XmlTree
XmlTree firstTrackCopy = doc.getChild( "/library/album/track" );
firstTrackCopy.setValue( "Replacement name" );
console() << doc.getChild( "/library/album/track" ) << std::endl;
Output:
Instead, use a reference in order to modify the XmlTree:
XmlTree &firstTrackRef = doc.getChild( "/library/album/track" ); // notice the reference
firstTrackRef.setValue( "Replacement name" );
console() << doc.getChild( "/library/album/track" ) << std::endl;
Output:
The XmlTree::Iter and XmlTree::ConstIter are designed to be STL-compatible iterators. For example, if you are using a lambdas-aware C++ compiler (currently only VC2010 at the time of this writing) the following code prints the names of the albums in the music library:
std::for_each( doc.begin( "library/album" ), doc.end(), []( const XmlTree &child ) {
app::console() << child.getChild( "title" ).getValue() << std::endl;
} );
XmlTree is implemented using the RapidXML library. For unusually performance-conscious use cases, it is worth considering using RapidXML directly, as the XmlTree is designed to be convenient more than it is fast. The necessary header files are in cinder/include/rapidxml and can be #include
d like so:
#include "rapidxml/rapidxml.hpp"
#include "rapidxml/rapidxml_print.hpp"