Parsing a Simple XML Document

Problem

You have a collection of data stored in an XML document. You want to parse the document and turn the data it contains into a collection of C++ objects. Your XML document is small enough to fit into memory and doesn't use an internal Document Type Definition (DTD) or XML Namespaces.

Solution

Use the TinyXml library. First, define an object of type TiXmlDocument and call its LoadFile() method, passing the pathname of your XML document as its argument. If LoadFile( ) returns true, your document has been successfully parsed. If parsing was successful, call the RootElement() method to obtain a pointer to an object of type TiXmlElement representing the document root. This object has a hierarchical structure that reflects the structure of your XML document; by traversing this structure, you can extract information about the document and use this information to create a collection of C++ objects.

For example, suppose you have an XML document animals.xml representing a collection of circus animals, as shown in Example 14-1. The document root is named animalList and has a number of child animal elements each representing an animal owned by the Feldman Family Circus. Suppose you also have a C++ class named Animal, and you want to construct a std::vector of Animals corresponding to the animals listed in the document.

Example 14-1. An XML document representing a list of circus animals






 
 Herby
 elephant
 1992-04-23
 
 
 
 
 Sheldon
 parrot
 1998-09-30
 
 
 
 
 Dippy
 penguin
 2001-06-08
 
 
 

Example 14-2 shows how the definition of the class Animal might look. Animal has five data members corresponding to an animal's name, species, date of birth, veterinarian, and trainer. An animal's name and species are represented as std::strings, its date of birth is represented as a boost::gregorian::date from Boost.Date_Time, and its veterinarian and trainer are represented as instances of the class Contact, also defined in Example 14-2. Example 14-3 shows how to use TinyXml to parse the document animals.xml, traverse the parsed document, and populate a std::vector of Animals using data extracted from the document.

Example 14-2. The header animal.hpp

#ifndef ANIMALS_HPP_INCLUDED
#define ANIMALS_HPP_INCLUDED

#include 
#include 
#include  // runtime_error
#include 
#include 

// Represents a veterinarian or trainer
class Contact {
public:
 Contact( ) { }
 Contact(const std::string& name, const std::string& phone)
 : name_(name)
 { 
 setPhone(phone);
 }
 std::string name( ) const { return name_; }
 std::string phone( ) const { return phone_; }
 void setName(const std::string& name) { name_ = name; }
 void setPhone(const std::string& phone)
 { 
 using namespace std;
 using namespace boost;
 // Use Boost.Regex to verify that phone 
 // has the form (ddd)ddd-dddd
 static regex pattern("\([0-9]{3}\)[0-9]{3}-[0-9]{4}");
 if (!regex_match(phone, pattern)) {
 throw runtime_error(string("bad phone number:") + phone);
 }
 phone_ = phone;
 }
private:
 std::string name_;
 std::string phone_;
};

// Compare two Contacts for equality; used in Recipe 14.9
// (for completeness, you should also define operator!=)
bool operator==(const Contact& lhs, const Contact& rhs)
{
 return lhs.name( ) == rhs.name( ) && lhs.phone( ) == rhs.phone( );
}

// Writes a Contact to an ostream
std::ostream& operator<<(std::ostream& out, const Contact& contact)
{
 out << contact.name( ) << " " << contact.phone( );
 return out;
}

// Represents an animal 
class Animal {
public:
 // Default constructs an Animal; this is 
 // the constructor you'll use most
 Animal( ) { }

 // Constructs an Animal with the given properties; 
 // you'll use this constructor in Recipe 14.9
 Animal( const std::string& name, 
 const std::string& species, 
 const std::string& dob, 
 const Contact& vet, 
 const Contact& trainer )
 : name_(name),
 species_(species),
 vet_(vet),
 trainer_(trainer)
 { 
 setDateOfBirth(dob);
 }

 // Getters
 std::string name( ) const { return name_; }
 std::string species( ) const { return species_; }
 boost::gregorian::date dateOfBirth( ) const { return dob_; }
 Contact veterinarian( ) const { return vet_; }
 Contact trainer( ) const { return trainer_; }

 // Setters
 void setName(const std::string& name) { name_ = name; }
 void setSpecies(const std::string& species) { species_ = species; }
 void setDateOfBirth(const std::string& dob) 
 { 
 dob_ = boost::gregorian::from_string(dob); 
 }
 void setVeterinarian(const Contact& vet) { vet_ = vet; }
 void setTrainer(const Contact& trainer) { trainer_ = trainer; }
private:
 std::string name_;
 std::string species_;
 boost::gregorian::date dob_;
 Contact vet_;
 Contact trainer_;
};

// Compare two Animals for equality; used in Recipe 14.9
// (for completeness, you should also define operator!=)
bool operator==(const Animal& lhs, const Animal& rhs)
{
 return lhs.name( ) == rhs.name( ) && 
 lhs.species( ) == rhs.species( ) && 
 lhs.dateOfBirth( ) == rhs.dateOfBirth( ) && 
 lhs.veterinarian( ) == rhs.veterinarian( ) && 
 lhs.trainer( ) == rhs.trainer( );
}

// Writes an Animal to an ostream
std::ostream& operator<<(std::ostream& out, const Animal& animal)
{
 out << "Animal {
"
 << " name=" << animal.name( ) << ";
"
 << " species=" << animal.species( ) << ";
"
 << " date-of-birth=" << animal.dateOfBirth( ) << ";
"
 << " veterinarian=" << animal.veterinarian( ) << ";
"
 << " trainer=" << animal.trainer( ) << ";
"
 << "}";
 return out;
}

#endif // #ifndef ANIMALS_HPP_INCLUDED

 

Example 14-3. Parsing animals.xml with TinyXml

#include 
#include  // cout
#include  // runtime_error
#include  // EXIT_FAILURE
#include  // strcmp
#include 
#include 
#include "animal.hpp"

using namespace std;

// Extracts the content of an XML element that contains only text
const char* textValue(TiXmlElement* e)
{
 TiXmlNode* first = e->FirstChild( ); 
 if ( first != 0 && 
 first == e->LastChild( ) &&
 first->Type( ) == TiXmlNode::TEXT )
 {
 // the element e has a single child, of type TEXT;
 // return the child's
 return first->Value( );
 } else {
 throw runtime_error(string("bad ") + e->Value( ) + " element");
 }
}

// Constructs a Contact from a "veterinarian" or "trainer" element
Contact nodeToContact(TiXmlElement* contact)
{
 using namespace std;
 const char *name, *phone;
 if ( contact->FirstChild( ) == 0 &&
 (name = contact->Attribute("name")) && 
 (phone = contact->Attribute("phone")) )
 {
 // The element contact is childless and has "name" 
 // and "phone" attributes; use these values to
 // construct a Contact
 return Contact(name, phone);
 } else {
 throw runtime_error(string("bad ") + contact->Value( ) + " element");
 }
}

// Constructs an Animal from an "animal" element
Animal nodeToAnimal(TiXmlElement* animal)
{
 using namespace std;

 // Verify that animal corresponds to an "animal" element
 if (strcmp(animal->Value( ), "animal") != 0) {
 throw runtime_error(string("bad animal: ") + animal ->Value( ));
 }

 Animal result; // Return value
 TiXmlElement* element = animal->FirstChildElement( );

 // Read name
 if (element && strcmp(element->Value( ), "name") == 0) {
 // The first child element of animal is a "name"
 // element; use its text value to set the name of result
 result.setName(textValue(element));
 } else {
 throw runtime_error("no name attribute");
 }

 // Read species
 element = element->NextSiblingElement( );
 if (element && strcmp(element->Value( ), "species") == 0) {
 // The second child element of animal is a "species"
 // element; use its text value to set the species of result
 result.setSpecies(textValue(element));
 } else {
 throw runtime_error("no species attribute");
 }

 // Read date of birth
 element = element->NextSiblingElement( );
 if (element && strcmp(element->Value( ), "dateOfBirth") == 0) {
 // The third child element of animal is a "dateOfBirth"
 // element; use its text value to set the date of birth
 // of result
 result.setDateOfBirth(textValue(element));
 } else {
 throw runtime_error("no dateOfBirth attribute");
 }

 // Read veterinarian
 element = element->NextSiblingElement( );
 if (strcmp(element->Value( ), "veterinarian") == 0) { 
 // The fourth child element of animal is a "veterinarian"
 // element; use it to construct a Contact object and
 // set result's veterinarian
 result.setVeterinarian(nodeToContact(element));
 } else {
 throw runtime_error("no veterinarian attribute");
 }

 // Read trainer
 element = element->NextSiblingElement( );
 if (strcmp(element->Value( ), "trainer") == 0) { 
 // The fifth child element of animal is a "trainer"
 // element; use it to construct a Contact object and
 // set result's trainer
 result.setTrainer(nodeToContact(element));
 } else {
 throw runtime_error("no trainer attribute");
 }

 // Check that there are no more children
 element = element->NextSiblingElement( );
 if (element != 0) {
 throw runtime_error(
 string("unexpected element:") + 
 element->Value( )
 );
 
 }

 return result;
}

int main( )
{
 using namespace std;

 try {
 vector animalList;

 // Parse "animals.xml"
 TiXmlDocument doc("animals.xml");
 if (!doc.LoadFile( )) 
 throw runtime_error("bad parse");
 
 // Verify that root is an animal-list
 TiXmlElement* root = doc.RootElement( );
 if (strcmp(root->Value( ), "animalList") != 0) {
 throw runtime_error(string("bad root: ") + root->Value( ));
 }

 // Traverse children of root, populating the list 
 // of animals
 for ( TiXmlElement* animal = root->FirstChildElement( );
 animal;
 animal = animal->NextSiblingElement( ) )
 {
 animalList.push_back(nodeToAnimal(animal));
 }
 
 // Print the animals' names
 for ( vector::size_type i = 0,
 n = animalList.size( );
 i < n;
 ++i )
 {
 cout << animalList[i] << "
";
 }
 } catch (const exception& e) {
 cout << e.what( ) << "
";
 return EXIT_FAILURE;
 }
}

 

Discussion

TinyXml is an excellent choice for applications that need to do just a bit of XML processing. Its source distribution is small, it's easy to build and integrate with projects, and it has a very simple interface. It also has a very permissive license. Its main limitations are that it doesn't understand XML Namespaces, can't validate against a DTD or schema, and can't parse XML documents containing an internal DTD. If you need to use any of these features, or any of the XML-related technologies such as XPath or XSLT, you should use the other libraries covered in this chapter.

The TinyXml parser produces a representation of an XML document as a tree whose nodes represent the elements, text, comments and other components of an XML document. The root of the tree represents the XML document itself. This type of representation of a hierarchical document as a tree is known as a Document Object Model (DOM). The TinyXml DOM is similar to the one designed by the World Wide Web Consortium (W3C), although it does not conform to the W3C specification. In keeping with the minimalist spirit of TinyXml, the TinyXml DOM is simpler than the W3C DOM, but also less powerful.

The nodes in the tree representing an XML document can be accessed through the interface TiXmlNode, which provides methods to access a node's parent, to enumerate its child nodes, and to remove child nodes or insert additional child nodes. Each node is actually an instance of a more derived type; for example, the root of the tree is an instance of TiXmlDocument, nodes representing elements are instances TiXmlElement, and nodes representing text are instances of TiXmlText. The type of a TiXmlNode can be determined by calling its Type( ) method; once you know the type of a node, you can obtain a representation of the node as a more derived type by calling one of the convenience methods such as toDocument() , toElement( ) and toText( ). These derived types contain additional methods appropriate to the type of node they represent.

It's now easy to understand Example 14-3. First, the function textValue( ) extracts the text content from an element that contains only text, such as name, species, or dateOfBirth. It does this by first checking that an element has only one child, and that the child is a text node. It then obtains the child's text by calling the Value() method, which returns the textual content of a text node or comment node, the tag name of an element node, and the filename of a root node.

Next, the function nodeToContact( ) takes a node corresponding to a veterinarian or trainer element and constructs a Contact object from the values of its name and phone attributes, which it retrieves using the Attribute( ) method.

Similarly, the function nodeToAnimal( ) takes a node corresponding to an animal element and constructs an Animal object. It does this by iterating over the node's children using the NextSiblingElement( ) method, extracting the data contained in each element, and setting the corresponding property of the Animal object. The data is extracted using the function textValue( ) for the elements name, species, and dateOfBirth and the function nodeToContact( ) for the elements veterinarian and TRainer.

In the main function, I first construct a TiXmlDocument object corresponding to the file animals.xml and parse it using the LoadFile() method. I then obtain a TiXmlElement corresponding to the document root by calling the RootElement( ) method. Next, I iterate over the children of the root element, constructing an Animal object from each animal element using the function nodeToAnimal( ). Finally, I iterate over the collection of Animal objects, writing them to standard output.

One feature of TinyXml that is not illustrated in Example 14-3 is the SaveFile( ) method of TiXmlDocument, which writes the document represented by a TiXmlDocument to a file. This allows you to parse an XML document, modify it using the DOM interface, and save the modified document. You can even create a TiXmlDocument from scratch and save it to disk:

// Create a document hello.xml, consisting 
// of a single "hello" element
TiXmlDocument doc;
TiXmlElement root("hello");
doc.InsertEndChild(root);
doc.SaveFile("hello.xml");

 

See Also

Recipe 14.3 and Recipe 14.4

Building C++ Applications

Code Organization

Numbers

Strings and Text

Dates and Times

Managing Data with Containers

Algorithms

Classes

Exceptions and Safety

Streams and Files

Science and Mathematics

Multithreading

Internationalization

XML

Miscellaneous

Index



C++ Cookbook
Secure Programming Cookbook for C and C++: Recipes for Cryptography, Authentication, Input Validation & More
ISBN: 0596003943
EAN: 2147483647
Year: 2006
Pages: 241

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net