15.3.2.  Generation of XML with DOM

[ fromfile: xml-dom.xml id: xmlgeneration ]

DOM documents are normally created by parsers to represent XML from an input stream, but DOM can also generate XML structures as output. It is preferable to generate XML through an API rather than by printing formatted strings because DOM generation guarantees that the resulting document will be parsable again.

In Figure 15.6, DocbookDoc is a factory for QDomElements. It is derived from QDomDocument and specialized for creating Docbook/XML documents.

Figure 15.6.  DocbookDoc

DocbookDoc

In the header file for this class, excerpted in Example 15.11, we added a typedef to improve readability. In the DOM standard, all DOM classes have simple names; for example Node, Element, Document, and Attribute.

Example 15.11. src/libs/docbook/docbookdoc.h

[ . . . . ]

typedef QDomElement Element; 1

1

Saves on typing, and is consistent with Java DOM.


As shown in Example 15.12, you can build documents by creating chapters, sections, and paragraphs.

Example 15.12. src/xml/dombuilder/zenflesh.cpp

#include <QTextStream>
#include <docbookdoc.h>

class ZenFlesh : public DocbookDoc {
    public: ZenFlesh();
};

ZenFlesh::ZenFlesh() : 
    DocbookDoc("Zen Flesh, Zen Bones") {

    chapter("101 Zen Stories");
    section("A cup of tea");
    para("Nan-in served a cup of tea.");
    section("Great Waves");
    QDomElement p = para("o-nami the wrestler sat in meditation and "
            "tried to imagine himself as a bunch of great waves.");
    setRole(p, "remark");
    chapter("The Gateless Gate");
    formalpara("The Gateless Gate",
      "In order to enter the gateless gate, you must have a ");
    bold(" mindless mind.");

    section("Joshu's dog");
    para("Has a dog buddha nature or not?");

    section("Haykujo's Fox");
    QDomElement fp = formalpara("This is a special topic", 
       "Which should have a role= remark attribute");
    setRole(fp, "remark");
}

int main() {
   QTextStream cout(stdout);
   ZenFlesh book;
   cout << book << endl;
}


The constructor generates a little book in XML which, after pretty-printing, could look like Example 15.13.

Example 15.13. src/xml/zen.xml

<book>
    <title>Zen Flesh, Zen Bones</title>
    <chapter>
        <title>101 Zen Stories</title>
        <section>
            <title>A cup of tea</title>
            <para>Nan-in served a cup of tea.</para>
        </section>
        <section>
            <title>Great Waves</title>
            <para>
            o-nami the wrestler sat in meditation and tried
            to imagine himself as a bunch of great waves.
            </para>
        </section>
    </chapter>
    <chapter>
        <title>The Gateless Gate</title>
        <formalpara>
            <title>The Gateless Gate</title>
            In order to enter the gateless gate,
            you must have a <emphasis role="strong">
            mindless mind</emphasis>
        </formalpara>
        <section>
            <title>Joshu's dog</title>
            <para>Has a dog buddha nature or not?</para>
        </section>
        <section>
            <title>Haykujo's Fox</title>
            <formalpara role="remark">
                <title>This is a special topic</title>
                Which should have a role="remark" attribute
            </formalpara>
        </section>
    </chapter>
</book>

The advantage of this format is that it can be easily converted into HTML (or PDF, or LaTEX) using xsltproc and the Docbook/XSL stylesheets [docbookxsl]. Example 15.14 shows the invocation for generating an HTML version.

Example 15.14. src/xml/zen2html

#!/bin/sh
# Translates zen.xml into index.html.
# Requires gnu xsltproc and docbook-xsl.
# DOCBOOK=/usr/share/docbook-xsl
xsltproc $DOCBOOK/html/onechunk.xsl zen.xml

Now look at Example 15.15, where the elements are created. Each major Docbook language element has a corresponding Factory method in DocbookDoc.

Example 15.15. src/libs/docbook/docbookdoc.cpp

[ . . . . ]

Element DocbookDoc::bridgehead(QString titleStr) {
    Element retval = createElement("bridgehead");
    Element titleEl = title(titleStr);
    currentParent.appendChild(retval);
    return retval;
}
Element DocbookDoc::title(QString name, Element parent) {
    Element retval = createElement("title");
    QDomText tn = createTextNode(name);
    retval.appendChild(tn);
    if (parent != Element())
        parent.appendChild(retval);
    return retval;
}

Element DocbookDoc::chapter(QString titleString) {
    Element chapter = createElement("chapter");
    title(titleString, chapter);
    documentElement().appendChild(chapter);
    currentParent = chapter;
    currentChapter = chapter;
    return chapter;
}

Element DocbookDoc::para(QString textstr) {
    QDomText tn = createTextNode(textstr);
    Element para = createElement("para");
    para.appendChild(tn);
    currentParent.appendChild(para);
    currentPara = para;
    return para;
}

In addition, there are some character-level elements that only modify text, as shown in Example 15.16.

Example 15.16. src/libs/docbook/docbookdoc.cpp

[ . . . . ]

Element DocbookDoc::bold(QString text) {
    QDomText tn = createTextNode(text);
    Element emphasis = createElement("emphasis");
    setRole(emphasis, "strong");
    emphasis.appendChild(tn);
    currentPara.appendChild(emphasis);
    return emphasis;
}

void  DocbookDoc::setRole(Element el, QString role) {
    el.setAttribute("role", role);    
}

Because each QDomNode must be created by QDomDocument, it makes sense to extend QDomDocument to write our own DOM factory.

Depending on what kind of element is being created, DocbookDoc adds newly created elements as children to previously created elements.