XMLIO Reference

Contents

Example Document
XMLIO Classes
Document Reading Process
Document Writing Process
Detailed source code for this example
Short implementation

Example Document

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>

XMLIO Classes:

Application:
Manipulates its own data structures, that can be seen as xml elements, because they are derived amongst other classes from XMLIO_Element.
Document:
Handles file and accesses application's data structures via the xmlio-interface.
Elements:
Because of the inheritance from XMLIO_Element, they provide necessary interfaces for document class access these structures.
Thus all classes must be adapted to the application. xmlio is only a framework, that grants read/write features.

object structure of xmlio

Document Reading Process:

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.

document reading interaction diagram

Document Writing Process:

Use of a recursive structure

document writing interaction diagram

Detailed source code for this example:

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.

Some headers and declarations:


#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 element element2:


/* 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 data elemement element1:


/* 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 document class document and application 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;
  }
};

The main program:


int main() {
  document doc;
  if (doc.read_example()!=0 || doc.write_example()!=0)
    return 1;
  return 0;
}

Here comes the short implementation

class element2:

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

class element1:

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

class document:

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.