The functionality of xmlio should be demonstrated with this xml document:
<?xml version="1.0"?> <element1> first chardata for element1 <element2 id="nested_in_element1"> chardata content for element2 </element2> second chardata for element1 </element1>
The application instantiates a document object, that handles the input and
drives the expat parser.
This document object instantiates a root element (element1) when the first start tag is found.
So the document object decides, if this file contains the expected data.
The name and the attributes are parameters of the constructor of element1.
From now on all events are passed to element1, for instance character data events of element1 or start tag events of nested elements.
Now element1 has to decides what to do with the received events. In our case it instantiates a element2, which then becomes the element that receives the following events...
All the time the events from the parser expat are forwared by the document object.
This process is shown in the following interaction diagram.
Use of a recursive structure
The following code shows in detail how the XMLIO read and write interface works. For convenience there are a lot of predefined classes that make the implementation of data structures easy. But at first we present the detailed code to explain how the basic mechanisms are applied.
#include <list> #include <map> #include <string> #include <iostream> /* compiler dependent */ #define HAVE_NAMESPACES 1 #include <xmlio/XMLIO_Document.h> #include <xmlio/XMLIO_Element.h> /* compiler dependent */ using namespace std;
Probably you need namespace support
/* the data structure element, that is contained by element1 as root element expects string content and attributes */ class element2: public XMLIO_Element{ public: /* store string here */ string my_content; string id; /* set name of this element and save attributes */ element2(const string& name, XMLIO_Attributes& attribs) { tag="element2"; if (attribs.find("id")!=attribs.end()) id=attribs["id"]; else cerr<<"element 2 requires the attribute id"<<endl; } /* collect characters */ virtual void XMLIO_getCharacters(const string& characters) { my_content.append(characters); } /* update attributes before writing*/ virtual const XMLIO_Attributes& XMLIO_getAttributes() const { XMLIO_Attributes& attrs=(XMLIO_Attributes&)attributes; /* cast const away */ attrs["id"]=id; /* update */ return attributes; } /* print characters */ virtual const int XMLIO_writeContent(XMLIO_Document& doc) const { return doc.write(my_content); } };
/* the root element */ class element1: public XMLIO_Element{ public: list<element2*> nested_elements; string chars1; string chars2; /* set my name */ element1(const string& name, XMLIO_Attributes& attribs) { tag="element1"; } /* clean up */ ~element1() { for(list<element2*>::iterator pos=nested_elements.begin(); pos != nested_elements.end(); ++pos) SAFE_DELETE(*pos); } /* be prepared for some elements with name element2 */ virtual XMLIO_Element* XMLIO_startTag(const string& name, XMLIO_Attributes& attribs){ if (name == "element2") { element2* new_element=new element2(name, attribs); nested_elements.push_back(new_element); return new_element; } /* otherwise ignore elements */ return NULL; } /* implement little difference between chars before the first element and chars between or after the element */ virtual void XMLIO_getCharacters(const string& characters) { if (nested_elements.empty()) chars1.append(characters); else chars2.append(characters); } /* just write the content */ virtual const int XMLIO_writeContent(XMLIO_Document& doc) const { int result=doc.write(chars1); if (result<0) return result; for(list<element2*>::const_iterator pos=nested_elements.begin(); pos!=nested_elements.end(); ++pos) { int tmp_result=doc.writeElement(**pos); if (tmp_result<0) return tmp_result; result+=tmp_result; } /* for */ int tmp_result=doc.write(chars2); if (tmp_result<0) return tmp_result; return result+tmp_result; } };
It is possible to include element1 inside another element (thats the reason, why no special root class exists), so you can embed the object easily and reuse the code.
The application code is merged into the XMLIO_Document class, so direct access to the root element, that holds the data is possible. Ohterwise, before the XMLIO_Document class will be deleted the application must safe the data. The application may steel the pointer from the document (copy it and set it to NULL).
/* the document code */ class document: public XMLIO_Document { public: /* store the root element */ element1* root; /* initialise the root pointer */ document(){ root=NULL; } ~document(){ SAFE_DELETE(root); } virtual XMLIO_Element* XMLIO_startTag(const string& name, XMLIO_Attributes& attribs) { if (name=="element1") { root=new element1(name, attribs); return root; } else { cerr<<"content with name "<<name<<" not expected!"<<endl; return NULL; } } virtual void XMLIO_getCharacters (const string& characters) { cerr<<"unexpected characters"<<endl; } virtual void XMLIO_finishedReading() { /* time to test consistency, do spellchecking, etc... */ } /* application code */ int read_example() { if (open("reference_example.xml","r")!=0){ cerr<<"could not open reference_example.xml"<<endl; return 1; } if (readDocument()!=0) { cerr<<"error while parsing the document"<<endl; close(); return 1; } close(); if (root==NULL) { cerr<<"no root element found!"<<endl; return 1; } return 0; } int write_example() { if (root==NULL) return 1; if (open("reference_example_2.xml","w")!=0) { cerr<<"could not open reference_example_2.xml"<<endl; return 1; } if (writeElement(*root)<0) { cerr<<"error while writing"<<endl; close(); return 1; } close(); return 0; } };
int main() { document doc; if (doc.read_example()!=0 || doc.write_example()!=0) return 1; return 0; }
XMLIO_StringElement is derived from string and collects the content in itself. So we only have to handle the attribute id.
/* Just extend the XMLIO_StringElement by the attribute id. */ class element2: public XMLIO_StringElement{ public: /* store attribute */ string id; /* set name of this element and save attributes */ element2(const string& name, XMLIO_Attributes& attribs) { tag="element2"; if (attribs.find("id")!=attribs.end()) id=attribs["id"]; else cerr<<"element 2 requires the attribute id"<<endl; } /* update attributes before writing*/ virtual const XMLIO_Attributes& XMLIO_getAttributes() const { XMLIO_Attributes& attrs=(XMLIO_Attributes&)attributes; /* cast const away */ attrs["id"]=id; /* update */ return attributes; } };
XMLIO_ContentElementArrayElement saves the mixture of content and elements as it appears in the element. By set_element_name a constraint to the accepted elements is given.
/* the root element, is an array of content strings and element2 */ class element1: public XMLIO_ContentElementArrayElement<string,element2> { public: /* set my name */ element1(const string& name, XMLIO_Attributes& attribs) { tag="element1"; set_element_name("element2"); } };
Here is one element missing. The consequence is, that the class document can't be shortend by a virtual inheritance.
... what a pity. It will be added tomorrow.