A central part of Kode is kxml_compiler. This is a tool for creating C++ classes to access XML data from a schema definition.

Motivation

Accessing XML data from C++ is simple, but involves writing a lot of trivial repetitive code. This code usually follows the structure of the XML data, and doesn't involve a lot of creativity. Most of it follows directly from the schema of the XML document. Automatically generating the XML handling code from the schema eliminates the boring task to write the code manually.

Goal

For handling XML data in an application it usually needs three components, a representation of the XML data in C++, a parser for reading the data into this representation, and a writer to write the data back to XML. The goal of the XML compiler is to generate these three components automatically from the schema of the XML.

Automatically generating the code not only eliminates the boring work of writing the code manually, but also gives some additional flexibility. General improvements in parsing XML or for the data representation classes can be implemented centrally in the code generator and applied to a all generated code at once, without manual intervention. So lots of code can benefit of improvements at a central place.

kxml_compiler

kxml_compiler implements the code generation from a XML schemas. It generates C++ classes representing the data structure with a native Qt based API. It also generates the parser and writer needed to read the XML data into these classes and write it back to XML.

Three schema representations are currently supported: W3C XML Schema, Relax NG, and example XML. There are two variants of parsers, which can be generated, one based on QDom, and a custom one, optimized for speed on parsing the specific schema. Both parsers can be generated as part of the data classes or as external classes. The XML writer is a simple writer based on QTextStream. For the future, an implementation using QXmlStreamReader and QXmlStreamWriter would be desirable.

Example

In this section the code generation for a very simple case of XML is demonstrated. It uses the QDom parser as part of the data classes. The schema is provided as example XML.

XML Schema

The following XML serves as schema for the XML compiler. At the same time it is an example of the data which can be parsed and written by the generated code.

<simple>
  <food>
    <name>Cake</name>
    <taste>yummy</taste>
  </food>
  <food>
    <name>Zwieback</name>
    <taste>soso</taste>
  </food>
</simple>

Generated Code

For generating the code the following invocation of the XML compiler is used:

kxml_compiler simple.xml

Generated header file:

// This file is generated by kxml_compiler from simple.xml.
// All changes you do to this file will be lost.
/*
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
    USA.
*/
#ifndef SIMPLE_H
#define SIMPLE_H

#include <QString>
#include <QDomElement>
#include <QList>

class Food
{
  public:
    typedef QList<Food> List;

  public:
    void setName( const QString &v );
    QString name() const;
    void setTaste( const QString &v );
    QString taste() const;
    /**
      Parse XML object from DOM element.
     */
    static Food parseElement( const QDomElement &element, bool *ok );
    QString writeElement();

  private:
    QString mName;
    QString mTaste;
};

class Simple
{
  public:
    void addFood( const Food &v );
    void setFoodList( const Food::List &v );
    Food::List foodList() const;
    /**
      Parse XML object from DOM element.
     */
    static Simple parseElement( const QDomElement &element, bool *ok );
    QString writeElement();
    static Simple parseFile( const QString &filename, bool *ok );
    bool writeFile( const QString &filename );

  private:
    Food::List mFoodList;
};

#endif

Generated implementation:

// This file is generated by kxml_compiler from simple.xml.
// All changes you do to this file will be lost.
/*
    This file is part of KDE.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
    USA.
*/

#include "simple.h"

#include <QtDebug>
#include <QFile>
#include <QDomDocument>
#include <QtCore/QTextStream>
#include <QtCore/QtDebug>
#include <QtCore/QFile>

QString indent( int n = 0 )
{
  static int i = 0;
  i += n;
  QString space;
  return space.fill( ' ', i );
}

void Food::setName( const QString &v )
{
  mName = v;
}

QString Food::name() const
{
  return mName;
}

void Food::setTaste( const QString &v )
{
  mTaste = v;
}

QString Food::taste() const
{
  return mTaste;
}

Food Food::parseElement( const QDomElement &element, bool *ok )
{
  if ( element.tagName() != "food" ) {
    qCritical() << "Expected 'food', got '" <<element.tagName() << "'.";
    if ( ok ) *ok = false;
    return Food();
  }

  Food result = Food();

  QDomNode n;
  for( n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) {
    QDomElement e = n.toElement();
    if ( e.tagName() == "name" ) {
      result.setName( e.text() );
    }
    else if ( e.tagName() == "taste" ) {
      result.setTaste( e.text() );
    }
  }


  if ( ok ) *ok = true;
  return result;
}

QString Food::writeElement()
{
  QString xml;
  xml += indent() + "<food>\n";
  indent( 2 );
  xml += indent() + "<name>" + name() + "</name>\n";
  xml += indent() + "<taste>" + taste() + "</taste>\n";
  indent( -2 );
  xml += indent() + "</food>\n";
  return xml;
}


void Simple::addFood( const Food &v )
{
  mFoodList.append( v );
}

void Simple::setFoodList( const Food::List &v )
{
  mFoodList = v;
}

Food::List Simple::foodList() const
{
  return mFoodList;
}

Simple Simple::parseElement( const QDomElement &element, bool *ok )
{
  if ( element.tagName() != "simple" ) {
    qCritical() << "Expected 'simple', got '" <<element.tagName() << "'.";
    if ( ok ) *ok = false;
    return Simple();
  }

  Simple result = Simple();

  QDomNode n;
  for( n = element.firstChild(); !n.isNull(); n = n.nextSibling() ) {
    QDomElement e = n.toElement();
    if ( e.tagName() == "food" ) {
      bool ok;
      Food o = Food::parseElement( e, &ok );
      if ( ok ) result.addFood( o );
    }
  }


  if ( ok ) *ok = true;
  return result;
}

QString Simple::writeElement()
{
  QString xml;
  xml += indent() + "<simple>\n";
  indent( 2 );
  foreach( Food e, foodList() ) {
    xml += e.writeElement();
  }
  indent( -2 );
  xml += indent() + "</simple>\n";
  return xml;
}

Simple Simple::parseFile( const QString &filename, bool *ok )
{
  QFile file( filename );
  if ( !file.open( QIODevice::ReadOnly ) ) {
    qCritical() << "Unable to open file '" << filename << "'";
    if ( ok ) *ok = false;
    return Simple();
  }

  QString errorMsg;
  int errorLine, errorCol;
  QDomDocument doc;
  if ( !doc.setContent( &file, false, &errorMsg, &errorLine, &errorCol ) ) {
    qCritical() << errorMsg << " at " << errorLine << "," << errorCol;
    if ( ok ) *ok = false;
    return Simple();
  }

  qDebug() << "CONTENT:" << doc.toString();

  bool documentOk;
  Simple c = parseElement( doc.documentElement(), &documentOk );
  if ( ok ) {
    *ok = documentOk;
  }
  return c;
}

bool Simple::writeFile( const QString &filename )
{
  QFile file( filename );
  if ( !file.open( QIODevice::WriteOnly ) ) {
    qCritical() << "Unable to open file '" << filename << "'";
    return false;
  }

  QTextStream ts( &file );
  ts << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
  ts << writeElement();
  file.close();

  return true;
}