[prev in list] [next in list] [prev in thread] [next in thread] 

List:       kde-commits
Subject:    [calligra] /: Add api for writing ODF that is generated from the ODF RNG file.
From:       Jos van den Oever <jos () vandenoever ! info>
Date:       2013-08-04 17:41:20
Message-ID: E1V62Iy-0006d8-U9 () scm ! kde ! org
[Download RAW message or body]

Git commit 1cf17b47013c7353d5ccd59663145e46bc2f0750 by Jos van den Oever.
Committed on 04/08/2013 at 17:39.
Pushed by vandenoever into branch 'master'.

Add api for writing ODF that is generated from the ODF RNG file.

Two years ago I wrote an initial version of this patch and a detailed discussion on \
the mailing list [1] followed. The main objections to the patch have been dealt with \
(see below). Most of this new version was written at Akademy in Bilbao.

Very short summary of the patch:
 This patch should help everybody, young and old, with coding C++ for writing ODF and \
make errors easier to catch.  The OpenDocument Format specification is published with \
a Relax NG file that specifies the XML format. This file can be used to check if ODF \
files are valid. It can also be used to generate a C++ API headers. This is what this \
patch does.

Example:
 Instead of writing:
==
  xmlWriter->startElement("text:p");
  xmlWriter->addAttribute("text:style-name", "italic");
  xmlWriter->startElement("text:p");
  xmlWriter->addAttribute("text:style-name", "bold");
  xmlWriter->addTextNode("Hello World!");
  xmlWriter->endElement();
  xmlWriter->endElement();
==
you can write:
==
  text_p p(xmlWriter);
  p.set_text_style_name("italic");
  text_span span(p.add_text_span());
  span.set_text_style_name("italic");
  span.addTextNode("Hello World!");
==

Some advantages:
 - autocompletion when coding: faster coding
 - tag and attribute names are not strings but class and function names: less errors
 - nesting is checked by the compiler
 - you write to elements (span, p), not xmlwriter: easier to read
 - required attributes are part of the element constructor

Implementation considerations:
 - Calligra is large, so the generated code mixes well with the use of KoXmlWriter \
                and porting can be done in small steps.
 - class and function names are similar to the xml tags with ':' and '-' replaced by \
                '_'.
 - stack based: no heap allocations
 - only header files: all code will inline and have low impact on runtime
 - modular: one header file per namespace to reduce compile overhead
 - code generator is Qt code, part of Calligra and runs as a build step

Not in this patch (and places where you can help in the future):
 - generate enumerations based on Relax NG
 - check data type for attributes (you can still write "hello" to an integer \
                attribute)
 - complete port of Calligra to the generated code
 - improved speed by using static QString instead of const char*

Provided solutions to previously raised issues:
 - modular headers to reduce compile overhead
 - function "end()" to optionally close an element before it goes out of scope
 - use structure of Relax NG file to reduce header sizes by inheritance from common \
                groups
 - provide most KoXmlWriter functionality safely through the element instances
 - closing elements is now automatic at a slight runtime overhead

M  +1    -0    devtools/CMakeLists.txt
A  +4    -0    devtools/rng2cpp/CMakeLists.txt
A  +1389 -0    devtools/rng2cpp/rng2cpp.cpp     [License: LGPL (v2+)]
M  +2    -1    filters/libmso/CMakeLists.txt
M  +24   -43   filters/libmso/shapes.cpp
M  +54   -56   filters/libmso/shapes2.cpp
M  +1    -0    filters/sheets/excel/import/CMakeLists.txt
M  +322  -384  filters/sheets/excel/import/excelimporttoods.cc
M  +361  -271  filters/stage/powerpoint/PptToOdp.cpp
M  +13   -2    filters/stage/powerpoint/PptToOdp.h
M  +15   -24   libs/kotext/KoInlineNote.cpp
M  +2    -0    libs/odf/CMakeLists.txt
A  +10   -0    libs/odf/writeodf/CMakeLists.txt
A  +16   -0    libs/odf/writeodf/README.txt
A  +59   -0    libs/odf/writeodf/helpers.h     [License: UNKNOWN]  *
A  +101  -0    libs/odf/writeodf/odfwriter.h     [License: UNKNOWN]  *

The files marked with a * at the end have a non valid license. Please read: \
http://techbase.kde.org/Policies/Licensing_Policy and use the headers which are \
listed at that page.


http://commits.kde.org/calligra/1cf17b47013c7353d5ccd59663145e46bc2f0750

diff --git a/devtools/CMakeLists.txt b/devtools/CMakeLists.txt
index 15008fb..f0c4859 100644
--- a/devtools/CMakeLists.txt
+++ b/devtools/CMakeLists.txt
@@ -7,3 +7,4 @@ if (SHOULD_BUILD_DEVTOOLS)
     add_subdirectory(scripts)
 endif (SHOULD_BUILD_DEVTOOLS)
 
+add_subdirectory(rng2cpp)
diff --git a/devtools/rng2cpp/CMakeLists.txt b/devtools/rng2cpp/CMakeLists.txt
new file mode 100644
index 0000000..adf9e10
--- /dev/null
+++ b/devtools/rng2cpp/CMakeLists.txt
@@ -0,0 +1,4 @@
+find_package(Qt4 REQUIRED QtCore QtXml)
+include(${QT_USE_FILE})
+add_executable(rng2cpp rng2cpp.cpp)
+target_link_libraries(rng2cpp ${QT_LIBRARIES})
diff --git a/devtools/rng2cpp/rng2cpp.cpp b/devtools/rng2cpp/rng2cpp.cpp
new file mode 100644
index 0000000..4850c8b
--- /dev/null
+++ b/devtools/rng2cpp/rng2cpp.cpp
@@ -0,0 +1,1389 @@
+/* This file is part of the KDE project
+   Copyright (C) 2013 Jos van den Oever <jos@vandenoever.info>
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public
+   License as published by the Free Software Foundation; either
+   version 2 of the License, or (at your option) any later version.
+
+   This library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public License
+   along with this library; see the file COPYING.LIB.  If not, write to
+   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+#include <QFile>
+#include <QDebug>
+#include <QDomDocument>
+#include <QMap>
+#include <QtGlobal>
+#include <QSharedPointer>
+#include <QStringList>
+
+#define assert(cond, what) (Q_ASSERT_X(cond,"",(const \
char*)QString(what).toAscii())) +
+static const QString ns = "writeodf";
+
+class RNGItem;
+//typedef QSharedPointer<RNGItem> RNGItemPtr;
+typedef RNGItem* RNGItemPtr;
+typedef QSet<RNGItemPtr> RNGItems;
+typedef QVector<RNGItemPtr> RNGItemList;
+
+/**
+ * Helper class for writing fatal messages of the form fatal() << "error!";
+ */
+class fatal
+{
+private:
+    QString msg;
+public:
+    fatal() {}
+    ~fatal()
+    {
+        qFatal("%s", (const char*)msg.toAscii());
+    }
+    template<class T>
+    fatal& operator<<(T s)
+    {
+        msg += s;
+        return *this;
+    }
+};
+
+/**
+ * RNG Datatype has either a constant value or a type.
+ */
+class Datatype
+{
+public:
+    /**
+     * Standard comparator for Datatype.
+     */
+    bool operator==(const Datatype& a) const {
+        return constant == a.constant
+                && type == a.type;
+    }
+    /**
+     * Standard comparator for Datatype.
+     */
+    bool operator!=(const Datatype& a) const {
+        return !(*this == a);
+    }
+    QString constant;
+    QString type;
+};
+
+/**
+ * Define a qHash so Datatype can be put into a QSet.
+ */
+uint qHash(const Datatype& d)
+{
+    return qHash(d.constant) ^ qHash(d.type);
+}
+
+/**
+ * @brief The RNGItem class
+ * Generic class that describes any of a number of concepts from an RNG file.
+ * Any <define/>, <element/> or <attribute/> class is parsed into an instance
+ * of RNGItem.
+ */
+class RNGItem
+{
+public:
+    enum ItemType { Define, Start, Element, Attribute };
+private:
+    RNGItem(const RNGItem&);
+    void operator=(const RNGItem&);
+protected:
+    /**
+     * @brief type
+     */
+    const ItemType m_type;
+    const QString m_name;
+    /**
+     * Internal constructor.
+     */
+    RNGItem(ItemType type, const QString &name = QString()) :m_type(type),
+        m_name(name), mixedContent(false)
+    {
+        if (type != Start && name.isEmpty()) {
+            fatal() << "name is empty";
+        }
+    }
+public:
+    /**
+     * true if this item may contain text nodes
+     */
+    bool mixedContent;
+    /**
+     * name attribute of the <define/> element
+     */
+    const QString& name() const { return m_name; }
+    /**
+     * transformed name that is used in generated C++ code
+     */
+    QString cppName;
+    /**
+     * items that are allowed to be used in this item
+     */
+    RNGItems allowedItems;
+    /**
+     * items that must to be used in this item
+     */
+    RNGItems requiredItems;
+    /**
+     * names of items that are used in this item
+     * This list is resolved into allowedItems after parsing.
+     */
+    QSet<QString> referencedDeclares;
+    /**
+     * names of items that must be used in this item
+     * This list is resolved into allowedItems after parsing.
+     */
+    QSet<QString> requiredReferencedDeclares;
+    /**
+     * Collection of possible datatypes for this item.
+     */
+    QSet<Datatype> datatype;
+    /**
+     * true if this is item corresponds to a <element/>
+     */
+    bool isElement() const { return m_type == Element; }
+    /**
+     * true if this is item corresponds to a <attribute/>
+     */
+    bool isAttribute() const { return m_type == Attribute; }
+    /**
+     * Return a string value if this item can only contain a single constant value.
+     * For example "1.2" is the only allowed, but required value for the
+     * office:version attribute.
+     */
+    QString singleConstant() const
+    {
+        return datatype.size() == 1 ?datatype.constBegin()->constant :QString();
+    }
+    /**
+     * Return a string with the datatype if only one datatype is possible for
+     * this item.
+     */
+    QString singleType() const
+    {
+        return datatype.size() == 1 ?datatype.constBegin()->type :QString();
+    }
+    /**
+     * true if this is item corresponds to a <define/>
+     */
+    bool isDefine() const
+    {
+        bool isdefine = m_type == Define || m_type == Start;
+        assert(!isdefine || defineName.length(), elementName + " " + attributeName);
+        return isdefine;
+    }
+    /**
+     * true if this is item corresponds to a <start/>
+     */
+    bool isStart() const
+    {
+        return m_type == Start;
+    }
+    bool operator==(const RNGItem& a) const;
+};
+
+/**
+ * Specialization of RNGItem that is an element.
+ */
+class Element : public RNGItem
+{
+public:
+    Element(const QString& name) :RNGItem(RNGItem::Element, name)
+    {
+    }
+};
+
+/**
+ * Specialization of RNGItem that is an attribute.
+ */
+class Attribute : public RNGItem
+{
+public:
+    Attribute(const QString& name) :RNGItem(RNGItem::Attribute, name)
+    {
+    }
+};
+
+/**
+ * Specialization of RNGItem that is a a define.
+ */
+class Start : public RNGItem
+{
+public:
+    Start() :RNGItem(RNGItem::Start)
+    {
+    }
+};
+
+/**
+ * Specialization of RNGItem that is a a define.
+ */
+class Define : public RNGItem
+{
+public:
+    Define(const QString& name) :RNGItem(RNGItem::Define, name)
+    {
+    }
+};
+
+/**
+ * Simple helper class for collecting information about whether an RNGItem
+ * may contain a mix of text nodes and elements.
+ */
+class MixedCheck
+{
+public:
+    int elementCount;
+    bool mixed;
+    MixedCheck() :elementCount(0), mixed(false) {}
+};
+
+/**
+ * Determine if the RNGItem item may contain a mix of text nodes and elements.
+ * @param item item to be investigated
+ * @param seen items that were already seen, needed to avoid infinite recursion
+ * @param mc data that is being collected
+ */
+void isMixed(const RNGItemPtr& item, RNGItems& seen, MixedCheck& mc)
+{
+    if (item->isAttribute() || seen.contains(item)) {
+        return;
+    }
+    seen.insert(item);
+    mc.mixed = mc.mixed || item->mixedContent;
+    RNGItems::ConstIterator i = item->allowedItems.constBegin();
+    RNGItems::ConstIterator end = item->allowedItems.constEnd();
+    while (i != end) {
+        if ((*i)->isDefine()) {
+            isMixed(*i, seen, mc);
+        } else if ((*i)->isElement()) {
+            ++mc.elementCount;
+        }
+        ++i;
+    }
+}
+
+/**
+ * Determine if the RNGItem item may contain a mix of text nodes and elements.
+ * This function call a helper function that inspects the item recursively.
+ * @param item item to be investigated
+ * @return true if item may contain a mix of text nodes and elements
+ */
+bool isMixed(const RNGItemPtr& item)
+{
+    RNGItems seen;
+    MixedCheck mc;
+    isMixed(item, seen, mc);
+    return mc.mixed || mc.elementCount == 0;
+}
+
+/**
+ * Merge item b in to item a.
+ */
+void merge(RNGItemPtr& a, const RNGItemPtr& b)
+{
+    if (b->mixedContent) {
+        a->mixedContent = true;
+    }
+    assert(a->allowedItems.contains(b), "");
+    if (a->requiredItems.contains(b)) {
+        foreach(RNGItemPtr i, b->requiredItems) {
+            a->requiredItems.insert(i);
+        }
+        a->requiredItems.remove(b);
+    }
+    foreach(RNGItemPtr i, b->allowedItems) {
+        a->allowedItems.insert(i);
+    }
+    a->allowedItems.remove(b);
+}
+
+/**
+ * Sort function to sort the items in a nice way.
+ * <define/> items (including <start/> go first.
+ * <element/> items come next.
+ * <attribute/> items go last.
+ * Items of similar type are compared by their names.
+ */
+bool rngItemPtrLessThan(const RNGItemPtr &a, const RNGItemPtr &b)
+{
+    if (a->isDefine()) {
+        if (b->isDefine()) {
+            return a->name() < b->name();
+        }
+        return true;
+    }
+    if (b->isDefine()) {
+        return false;
+    }
+    if (a->isElement()) {
+        if (b->isElement()) {
+            if (a->name() == b->name()) {
+                // cppname maybe different, it can have e.g. a number appended
+                return a->cppName < b->cppName;
+            }
+            return a->name() < b->name();
+        }
+        return true;
+    }
+    if (b->isElement()) {
+        return false;
+    }
+    if (a->name() == b->name()) {
+        return a->cppName < b->cppName;
+    }
+    return a->name() < b->name();
+}
+
+/**
+ * Class that has a separate open header file for each namespace and each
+ * combination of namespaces.
+ * This object is passed around the code generating functions instead of
+ * a single output stream for a single header file.
+ */
+class Files
+{
+    /**
+     * List of open files.
+     */
+    QMap<QString,QMap<QString,QTextStream*> > files;
+    /**
+     * Directory into which to write all the header files.
+     */
+    const QString outdir;
+public:
+    Files(const QString& outdir_) :outdir(outdir_) {}
+    /**
+     * Close all open files after writing the closing '#endif'
+     */
+    ~Files() {
+        typedef const QMap<QString,QTextStream*> map;
+        foreach (map& m, files) {
+            foreach (QTextStream* out, m) {
+                *out << "#endif\n";
+                out->device()->close();
+                delete out->device();
+                delete out;
+            }
+        }
+    }
+    QTextStream& getFile(const QString& tag, const QString& tag2);
+    void closeNamespace();
+};
+
+/**
+ * Create a map that maps each Relax NG type to a Qt/C++ type.
+ */
+QMap<QString, QString> createTypeMap()
+{
+    QMap<QString, QString> map;
+    map.insert("string", "const QString&");
+    map.insert("date", "const QDate&");
+    map.insert("time", "const QTime&");
+    map.insert("dateTime", "const QDateTime&");
+    map.insert("duration", "qint64");
+    map.insert("integer", "qint64");
+    map.insert("nonNegativeInteger", "quint64");
+    map.insert("positiveInteger", "quint64");
+    map.insert("double", "double");
+    map.insert("anyURI", "const QUrl&");
+    map.insert("base64Binary", "const QByteArray&");
+    map.insert("ID", "const QString&");
+    map.insert("IDREF", "const QString&");
+    map.insert("IDREFS", "const QStringList&");
+    map.insert("NCName", "const QString&");
+    map.insert("language", "const QString&");
+    map.insert("token", "const QString&");
+    map.insert("QName", "const QString&");
+    map.insert("decimal", "double");
+    return map;
+}
+
+/**
+ * Return a Qt/C++ type for each Relax NG type.
+ */
+QString mapType(const QString& type)
+{
+    static const QMap<QString, QString> map = createTypeMap();
+    if (!map.contains(type)) {
+        fatal() << "Unknown data type " << type;
+    }
+    return map.value(type);
+}
+
+/**
+ * see below
+ */
+void parseContent(QDomElement content, RNGItem& item, bool required);
+
+/**
+ * Get a list of names for the attribute or element.
+ */
+QStringList getNames(QDomElement e)
+{
+    QStringList names;
+    QString name = e.attribute("name");
+    if (name.isEmpty()) {
+        QDomElement ce = e.firstChildElement();
+        if (ce.localName() == "choice") {
+            ce = ce.firstChildElement();
+            while (!ce.isNull()) {
+                if (ce.localName() == "name") {
+                    names << ce.text();
+                } else {
+                    fatal() << "Found element without comprehensible name.";
+                }
+                ce = ce.nextSiblingElement();
+            }
+        } else if (ce.localName() != "anyName") {
+            fatal() << "Found element without comprehensible name.";
+        }
+    } else {
+        names << name;
+    }
+    return names;
+}
+
+/**
+ * Parse an <element/> element.
+ */
+void parseElement(QDomElement e, RNGItem& parent, bool required)
+{
+    QStringList names = getNames(e);
+    foreach (const QString& name, names) {
+        RNGItemPtr element = RNGItemPtr(new Element(name));
+        parseContent(e, *element, true);
+        parent.allowedItems.insert(element);
+        if (required) {
+            parent.requiredItems.insert(element);
+        }
+    }
+}
+
+/**
+ * Parse an <attribute/> element.
+ */
+void parseAttribute(QDomElement e, RNGItem& parent, bool required)
+{
+    QStringList names = getNames(e);
+    foreach (const QString& name, names) {
+        RNGItemPtr attribute = RNGItemPtr(new Attribute(name));
+        parseContent(e, *attribute, true);
+        parent.allowedItems.insert(attribute);
+        if (required) {
+            parent.requiredItems.insert(attribute);
+        }
+    }
+}
+
+/**
+ * Parse the contents of any Relax NG element.
+ */
+void parseContent(QDomElement content, RNGItem& item, bool required)
+{
+    QDomElement e = content.firstChildElement();
+    while (!e.isNull()) {
+        QString type = e.localName();
+        QString name = e.attribute("name");
+        if (type == "interleave" || type == "oneOrMore" || type == "group") {
+            parseContent(e, item, required);
+        } else if (type == "optional" || type == "choice"
+                   || type == "zeroOrMore") {
+            parseContent(e, item, false);
+        } else if (type == "ref") {
+            item.referencedDeclares.insert(name);
+            if (required) {
+                item.requiredReferencedDeclares.insert(name);
+            }
+        } else if (type == "empty") {
+        } else if (type == "data") {
+            Datatype d;
+            d.type = mapType(e.attribute("type"));
+            item.datatype.insert(d);
+        } else if (type == "list") {
+        } else if (type == "description") {
+        } else if (type == "attribute") {
+            parseAttribute(e, item, required);
+        } else if (type == "element") {
+            parseElement(e, item, required);
+        } else if (type == "text") {
+            item.mixedContent = true;
+        } else if (type == "value") {
+            Datatype d;
+            d.constant = e.text();
+            item.datatype.insert(d);
+        } else if (type == "name") {
+        } else if (type == "anyName") {
+        } else if (type == "mixed") {
+        } else {
+            fatal() << "Unknown element " << type;
+        }
+        e = e.nextSiblingElement();
+    }
+}
+
+/**
+ * Parse the contents of a <define/> or <start/> element.
+ */
+RNGItemPtr parseDefine(QDomElement defineElement, RNGItems& items, bool isstart)
+{
+    RNGItemPtr item;
+    if (isstart) {
+        item = RNGItemPtr(new Start());
+    } else {
+        item = RNGItemPtr(new Define(defineElement.attribute("name")));
+    }
+    parseContent(defineElement, *item, true);
+    items.insert(item);
+    return item;
+}
+
+/**
+ * Parse all top level Relax NG elements.
+ */
+RNGItemPtr getDefines(QDomElement e, RNGItems& items)
+{
+    RNGItemPtr start = RNGItemPtr(0);
+    e = e.firstChildElement();
+    while (!e.isNull()) {
+        if (e.localName() == "define") {
+            parseDefine(e, items, false);
+        } else if (e.localName() == "start") {
+            assert(!start, "Multiple start elements.");
+            start = parseDefine(e, items, true);
+        } else {
+            fatal() << "Unknown element " << e.localName();
+        }
+        e = e.nextSiblingElement();
+    }
+    return start;
+}
+
+/**
+ * Load an XML from disk into a DOMDocument instance.
+ */
+QDomDocument loadDOM(QString url)
+{
+    QFile f(url);
+    f.open(QIODevice::ReadOnly);
+    QByteArray data = f.readAll();
+    f.close();
+
+    QDomDocument dom;
+    QString err;
+    if (!dom.setContent(data, true, &err)) {
+        qFatal("%s", err.ascii());
+    }
+    return dom;
+}
+
+/**
+ * Look through a set of RNGitems to find one that is the same.
+ * This can be used after parsing to find definitions that are the same.
+ * Such deduplication can reduce the size of the generated code.
+ */
+RNGItemPtr findEqualItem(const RNGItemPtr&i, const RNGItems& items)
+{
+    foreach (const RNGItemPtr& j, items) {
+        if (*i == *j) {
+            return j;
+        }
+    }
+    return RNGItemPtr();
+}
+
+/**
+ * Compare two RNGItem instances.
+ */
+bool RNGItem::operator==(const RNGItem& a) const
+{
+    bool unequal = m_type != a.m_type
+            || m_name != a.m_name
+            || mixedContent != a.mixedContent
+            || cppName != a.cppName
+            || referencedDeclares != a.referencedDeclares
+            || requiredReferencedDeclares != a.requiredReferencedDeclares
+            || allowedItems.size() != a.allowedItems.size()
+            || requiredItems.size() != a.requiredItems.size()
+            || datatype != a.datatype;
+    if (unequal) {
+        return false;
+    }
+    foreach (const RNGItemPtr& i, allowedItems) {
+        RNGItemPtr j = findEqualItem(i, a.allowedItems);
+        if (!j) {
+            return false;
+        }
+    }
+    foreach (const RNGItemPtr& i, requiredItems) {
+        RNGItemPtr j = findEqualItem(i, a.requiredItems);
+        if (!j) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/**
+ * Move all member items in the global list.
+ * If there is already a global member that is equal, use that in the item.
+ */
+void collect(RNGItem& item, RNGItems& collected)
+{
+    typedef QPair<RNGItemPtr,RNGItemPtr> Pair;
+    QList<Pair> toSwap;
+    foreach (const RNGItemPtr& i, item.allowedItems) {
+        RNGItemPtr j = findEqualItem(i, collected);
+        if (!j) {
+            collected.insert(i);
+            collect(*i, collected);
+        } else if (i != j) {
+            toSwap.append(qMakePair(i, j));
+        }
+    }
+    foreach (const Pair& i, toSwap) {
+        RNGItemPtr toRemove = i.first;
+        RNGItemPtr toAdd = i.second;
+        if (item.requiredItems.contains(toRemove)) {
+            item.requiredItems.remove(toRemove);
+            item.requiredItems.insert(toAdd);
+        }
+        item.allowedItems.remove(toRemove);
+        item.allowedItems.insert(toAdd);
+    }
+}
+
+/**
+ * Move all member items in the global list.
+ * If there is already a global member that is equal, use that in the item.
+ */
+void collect(const RNGItems& items, RNGItems& collected)
+{
+    foreach (const RNGItemPtr& item, items) {
+        collect(*item, collected);
+    }
+}
+
+/**
+ * Count how often a particular item is used by other items or itself.
+ */
+void countUsage(RNGItem& item, QMap<RNGItemPtr,int>& usageCount)
+{
+    foreach (const RNGItemPtr& i, item.allowedItems) {
+        if (usageCount.contains(i)) {
+            usageCount[i]++;
+        } else {
+            usageCount[i] = 1;
+        }
+    }
+}
+
+/**
+ * Remove items that are not used and merge items that are used in only one
+ * place into their parent if possible.
+ * This reduces the number of classes in the generated headers.
+ */
+int reduce(RNGItems& items)
+{
+    QMap<RNGItemPtr,int> usageCount;
+    foreach (const RNGItemPtr& item, items) {
+        countUsage(*item, usageCount);
+    }
+    RNGItems toRemove;
+    foreach (RNGItemPtr item, items) {
+        if (usageCount[item] <= 1 && !item->isStart() && item->isDefine()) {
+            RNGItemPtr user = RNGItemPtr(0);
+            foreach (const RNGItemPtr& i, items) {
+                if (i->allowedItems.contains(item)) {
+                    assert(!user, "");
+                    user = i;
+                }
+            }
+            if (user) {
+                merge(user, item);
+            }
+            toRemove.insert(item);
+            break;
+        }
+    }
+    foreach (const RNGItemPtr& item, toRemove) {
+        items.remove(item);
+    }
+    return toRemove.size();
+}
+
+/**
+ * Collect items that are contained in other items into a list with
+ * all the items.
+ * Relax NG is a hierarchical file format and this function creates a flat list
+ * with all items.
+ */
+int expand(RNGItems& items)
+{
+    RNGItems toAdd;
+    foreach (RNGItemPtr item, items) {
+        foreach (const RNGItemPtr& i, item->allowedItems) {
+            if (!items.contains(i)) {
+                toAdd.insert(i);
+            }
+        }
+    }
+    foreach (RNGItemPtr item, toAdd) {
+        items.insert(item);
+    }
+    return toAdd.size();
+}
+
+/**
+ * Find the <define/> item by name.
+ */
+RNGItemPtr getDefine(const QString& name, const RNGItems& items)
+{
+    RNGItemPtr item = RNGItemPtr(0);
+    foreach (RNGItemPtr i, items) {
+        if (i->name() == name) {
+            assert(!item, "Doubly defined element " + name + ".");
+            item = i;
+        }
+    }
+    assert(item, "Define not found " + name);
+    return item;
+}
+
+/**
+ * Resolve all <define/> references.
+ * After parsing, the <ref/> instances should be replaced by the actual
+ * items.
+ */
+void resolveDefines(RNGItemPtr start, const RNGItems& items, RNGItems& resolved)
+{
+    if (resolved.contains(start)) {
+        return;
+    }
+    resolved.insert(start);
+    foreach (const QString& name, start->referencedDeclares) {
+        RNGItemPtr i = getDefine(name, items);
+        if (start->requiredReferencedDeclares.contains(name)) {
+            start->requiredItems.insert(i);
+        }
+        start->allowedItems.insert(i);
+    }
+    start->referencedDeclares.clear();
+    start->requiredReferencedDeclares.clear();
+
+    foreach (RNGItemPtr item, start->allowedItems) {
+        resolveDefines(item, items, resolved);
+    }
+}
+
+/**
+ * Create a C++ name from the item name.
+ */
+QString makeCppName(const RNGItemPtr&item)
+{
+    QString name;
+    if (item->isElement() || item->isAttribute()) {
+        name = item->name();
+    } else {
+        name = "group_" + item->name();
+    }
+    name.replace(':', '_');
+    name.replace('-', '_');
+    return name;
+}
+
+/**
+ * Create a new name from the item name.
+ * The new name will not clash with the names from takenNames.
+ */
+QString makeUniqueCppName(const RNGItemPtr&item, QSet<QString>& takenNames)
+{
+    QString n = makeCppName(item);
+    QString name = n;
+    int i = 0;
+    while (!name.isEmpty() && takenNames.contains(name)) {
+        name = n + "_" + QString::number(++i);
+    }
+    takenNames.insert(name);
+    return name;
+}
+
+/**
+ * Create all the C++ names corresponding with the Relax NG items.
+ */
+void makeCppNames(RNGItemList& items)
+{
+    QSet<QString> cppnames;
+    // handle elements first so they have the nicest names
+    foreach (RNGItemPtr item, items) {
+        if (item->isElement()) {
+            item->cppName = makeUniqueCppName(item, cppnames);
+        }
+    }
+    // next handle the attributes
+    foreach (RNGItemPtr item, items) {
+        if (item->isAttribute()) {
+            item->cppName = makeUniqueCppName(item, cppnames);
+        }
+    }
+    // give the remaining declares names
+    foreach (RNGItemPtr item, items) {
+        if (item->isDefine()) {
+            item->cppName = makeUniqueCppName(item, cppnames);
+        }
+    }
+}
+
+/**
+ * Find all the items that are used in this item but are not element or
+ * attributes.
+ * These items will be base classes to a class that corresponds to an element.
+ */
+RNGItemList getBasesList(RNGItemPtr item)
+{
+    RNGItems list;
+    RNGItems antilist;
+    foreach (RNGItemPtr i, item->allowedItems) {
+        if (i->isDefine()) {
+            list.insert(i);
+            foreach (RNGItemPtr j, i->allowedItems) {
+                if (j->isDefine() && j != i) {
+                    antilist.insert(j);
+                }
+            }
+        }
+    }
+    list.subtract(antilist);
+    RNGItemList l = list.toList().toVector();
+    qStableSort(l.begin(), l.end(), rngItemPtrLessThan);
+    return l;
+}
+
+/**
+ * Sort items in the set.
+ * This is helpful in making the output reproducible.
+ */
+RNGItemList list(const RNGItems& items)
+{
+    RNGItemList list = items.toList().toVector();
+    qStableSort(list.begin(), list.end(), rngItemPtrLessThan);
+    return list;
+}
+
+/**
+ * Collect the data types of the attribute item.
+ */
+void resolveType(const RNGItemPtr& item, QSet<Datatype>& type)
+{
+    type.unite(item->datatype);
+    foreach (const RNGItemPtr& i, item->allowedItems) {
+        resolveType(i, type);
+    }
+}
+
+/**
+ * Collect the data types of the attributes.
+ */
+void resolveAttributeDataTypes(const RNGItems& items)
+{
+    foreach (const RNGItemPtr& i, items) {
+        if (i->isAttribute()) {
+            resolveType(i, i->datatype);
+        }
+    }
+}
+
+/**
+ * Create a ordered list of items.
+ * The order is such that dependencies of an item precede the item in the list.
+ */
+void addInOrder(RNGItemList& undefined, RNGItemList& defined)
+{
+    int last = -1;
+    while (last != undefined.size()) {
+        last = undefined.size();
+        for (int i = 0; i < undefined.size(); ++i) {
+            const RNGItemPtr& ii = undefined[i];
+            bool missingDependency = false;
+            foreach (const RNGItemPtr& j, list(ii->allowedItems)) {
+                if (j->isDefine() && !defined.contains(j) && j != ii) {
+                    if (undefined.contains(j)) {
+                        missingDependency = true;
+                    } else if (j->name().isEmpty()) {
+                        ii->allowedItems.remove(j);
+                        ii->requiredItems.remove(j);
+                    }
+                }
+            }
+            if (!missingDependency) {
+                defined.append(ii);
+                undefined.remove(i);
+            } else {
+                ++i;
+            }
+        }
+    }
+    if (undefined.size()) {
+        fatal() << undefined.size() << " missing dependencies";
+        undefined.clear();
+    }
+    // Q_ASSERT(undefined.size() == 0);
+}
+
+/**
+ * Helper structure to collect required arguments.
+ */
+struct RequiredArgsList
+{
+    int length;
+    QString args;
+    QString vals;
+};
+
+/**
+ * Write lists of required arguments that can be used in generated code.
+ * This list only covers the required attributes, not required elements.
+ */
+RequiredArgsList makeRequiredArgsList(const RNGItemPtr& item)
+{
+    RequiredArgsList r;
+    r.length = 0;
+    foreach (RNGItemPtr i, list(item->requiredItems)) {
+        if (i->isAttribute() && i->singleConstant().isNull()) {
+            QString name = makeCppName(i);
+            QString type = i->singleType();
+            if (type.isNull()) {
+                type = "const QString&";
+            }
+            r.args += type + " " + name + ", ";
+            r.vals += ", " + name;
+            ++r.length;
+        }
+    }
+    r.args = r.args.left(r.args.length() - 2);
+    return r;
+}
+
+/**
+ * Recursively find the items that are required for the given item.
+ */
+RNGItemList getAllRequiredAttributes(const RNGItemPtr& item, RNGItemList& list, int \
depth = 0) +{
+    if (depth > 10) {
+        return list;
+    }
+    foreach (RNGItemPtr i, item->allowedItems) {
+        if (item->requiredItems.contains(i)) {
+            if (i->isAttribute() && i->singleConstant().isNull()) {
+                list.append(i);
+            } else if (i->isDefine()) {
+                getAllRequiredAttributes(i, list, depth + 1);
+            }
+        }
+    }
+    return list;
+}
+
+/**
+ * Write full lists of required arguments that can be used in generated code.
+ */
+RequiredArgsList makeFullRequiredArgsList(const RNGItemPtr& item)
+{
+    RequiredArgsList r;
+    r.length = 0;
+    RNGItemList list;
+    getAllRequiredAttributes(item, list);
+    qStableSort(list.begin(), list.end(), rngItemPtrLessThan);
+    foreach (RNGItemPtr i, list) {
+        QString name = makeCppName(i);
+        QString type = i->singleType();
+        if (type.isNull()) {
+            type = "const QString&";
+        }
+        r.args += type + " " + name + ", ";
+        r.vals += ", " + name;
+        ++r.length;
+    }
+    r.args = r.args.left(r.args.length() - 2);
+    return r;
+}
+
+/**
+ * Write C++ code to set the required attribute values.
+ */
+void setRequiredAttributes(QTextStream& out, const RNGItemPtr& item)
+{
+    QString o;
+    if (!item->isElement()) {
+        o = "xml.";
+    }
+    foreach (RNGItemPtr i, list(item->requiredItems)) {
+        if (i->isAttribute()) {
+            out << "        " << o << "addAttribute(\"" + i->name() + "\", ";
+            QString constant = i->singleConstant();
+            if (constant.isNull()) {
+                out << makeCppName(i);
+            } else {
+                out << "\"" << constant << "\"";
+            }
+            out << ");\n";
+        }
+    }
+}
+
+/**
+ * Write the class definition for a class that corresponds to an xml element.
+ */
+void defineElement(QTextStream& out, const RNGItemPtr& item)
+{
+    RNGItemList bases = getBasesList(item);
+    out << "class " << item->cppName << " : public OdfWriter";
+    RNGItemList::const_iterator i = bases.begin();
+    while (i != bases.end()) {
+        out << ", public " << (*i)->cppName;
+        ++i;
+    }
+    out << " {\n";
+    out << "public:" << "\n";
+    RequiredArgsList r = makeFullRequiredArgsList(item);
+    if (r.args.length()) {
+        r.args = ", " + r.args;
+    }
+    out << "    " << item->cppName << "(OdfWriter* x" << r.args
+        << ") :OdfWriter(x, \"" << item->name() << "\", "
+        << (isMixed(item) ?"false" :"true") << ")";
+    i = bases.begin();
+    while (i != bases.end()) {
+        RequiredArgsList r;
+        if (item->requiredItems.contains(*i)) {
+            r = makeFullRequiredArgsList(*i);
+        }
+        out << ", " << (*i)->cppName << "(*static_cast<OdfWriter*>(this)" << r.vals \
<< ")"; +        ++i;
+    }
+    out << " {\n";
+    setRequiredAttributes(out, item);
+    out << "    }\n";
+    out << "    " << item->cppName << "(KoXmlWriter* x" << r.args
+        << ") :OdfWriter(x, \"" << item->name() << "\", "
+        << (isMixed(item) ?"false" :"true") << ")";
+    i = bases.begin();
+    while (i != bases.end()) {
+        RequiredArgsList r;
+        if (item->requiredItems.contains(*i)) {
+            r = makeFullRequiredArgsList(*i);
+        }
+        out << ", " << (*i)->cppName << "(*static_cast<OdfWriter*>(this)" << r.vals \
<< ")"; +        ++i;
+    }
+    out << " {\n";
+    setRequiredAttributes(out, item);
+    out << "    }\n";
+    QSet<QString> doneA;
+    QSet<QString> doneE;
+    foreach (RNGItemPtr i, list(item->allowedItems)) {
+        QString name = makeCppName(i);
+        if (i->isAttribute() && !item->requiredItems.contains(i) && \
!doneA.contains(name)) { +            QString type = i->singleType();
+            type = (type.isNull()) ?"const QString&" :type;
+            out << "    template<class T>\n";
+            out << "    void set_" << name << "(const T& value) {\n";
+            out << "        addAttribute(\"" + i->name() + "\", value);\n";
+            out << "    }\n";
+            doneA.insert(name);
+        } else if (i->isElement() && !doneE.contains(name)) {
+            RequiredArgsList r = makeFullRequiredArgsList(i);
+            out << "    " << i->cppName << " add_" << name << "(" + r.args + ");\n";
+            doneE.insert(name);
+        }
+    }
+    if (isMixed(item)) {
+        out << "    void addTextNode(const QString& data) {\n";
+        out << "        OdfWriter::addTextNode(data);\n";
+        out << "    }\n";
+    }
+    out << "};\n";
+}
+
+/**
+ * Write the class definition for a class that corresponds to a Relax NG group.
+ * These groups are bases to classes that correspond to elements.
+ */
+void defineGroup(QTextStream& out, const RNGItemPtr& item)
+{
+    RNGItemList bases = getBasesList(item);
+    out << "class " << item->cppName;
+    if (bases.size()) {
+        RNGItemList::const_iterator i = bases.begin();
+        out << " : public " << (*i)->cppName;
+        while (++i != bases.end()) {
+            out << ", public " << (*i)->cppName;
+        }
+    }
+    out << " {\n";
+    out << "private:\n";
+    out << "    OdfWriter& xml;\n";
+    out << "public:\n";
+    RequiredArgsList r = makeFullRequiredArgsList(item);
+    if (r.args.length()) {
+        r.args = ", " + r.args;
+    }
+    out << "    " << item->cppName << "(OdfWriter& x" + r.args + ") :";
+    foreach (const RNGItemPtr& i, bases) {
+        RequiredArgsList r;
+        if (item->requiredItems.contains(i)) {
+            r = makeFullRequiredArgsList(i);
+        }
+        out << i->cppName << "(x" + r.vals + "), ";
+    }
+    out << "xml(x) {\n";
+    setRequiredAttributes(out, item);
+    out << "    }\n";
+    if (r.length) {
+        out << "    " << item->cppName << "(OdfWriter& x) :";
+        foreach (const RNGItemPtr& i, bases) {
+            out << i->cppName << "(x), ";
+        }
+        out << "xml(x) {}\n";
+    }
+    QSet<QString> done;
+    foreach (RNGItemPtr i, list(item->allowedItems)) {
+        QString name = makeCppName(i);
+        // also allow setting of required elements, because the might need to be
+        // set in elements where the group is optional
+        // && !item->requiredItems.contains(i)
+        if (i->isAttribute() && !done.contains(name)) {
+            QString type = i->singleType();
+            type = (type.isNull()) ?"const QString&" :type;
+            out << "    template<class T>\n";
+            out << "    void set_" << name << "(const T& value) {\n";
+            out << "        xml.addAttribute(\"" + i->name() + "\", value);\n";
+            out << "    }\n";
+            done.insert(name);
+        } else if (i->isElement()) {
+            RequiredArgsList r = makeFullRequiredArgsList(i);
+            out << "    " << i->cppName << " add_" << name << "(" + r.args + ");\n";
+        }
+    }
+    if (isMixed(item)) {
+        out << "    void addTextNode(const QString& data) {\n";
+        out << "        xml.addTextNode(data);\n";
+        out << "    }\n";
+    }
+    out << "};\n";
+}
+
+/**
+ * Write the definition for a member function to add an element to another
+ * element.
+ */
+void writeAdderDefinition(const RNGItemPtr& item, const RNGItemPtr& i, QTextStream& \
out) +{
+    QString name = makeCppName(i);
+    RequiredArgsList r = makeFullRequiredArgsList(i);
+    out << "inline ";
+    if (!ns.isEmpty()) {
+        out << ns << "::";
+    }
+    out << i->cppName << "\n";
+    if (!ns.isEmpty()) {
+        out << ns << "::";
+    }
+    out << item->cppName << "::add_" << name << "(";
+    out << r.args << ") {\n";
+    out << "    return " << ns << "::" << i->cppName << "(";
+    if (item->isElement()) {
+        out << "this";
+    } else {
+        out << "&xml";
+    }
+    out << r.vals << ");\n";
+    out << "}\n";
+}
+
+/**
+ * Write the definitions for member functions to add elements to other
+ * element.
+ */
+void writeAdderDefinitions(const RNGItemPtr& item, Files& files)
+{
+    QSet<QString> done;
+    foreach (RNGItemPtr i, list(item->allowedItems)) {
+        QString name = makeCppName(i);
+        if (i->isElement() && !done.contains(name)) {
+            QString tag1 = (item->isElement()) ?item->name() :QString();
+            QTextStream& out = files.getFile(tag1, i->name());
+            writeAdderDefinition(item, i, out);
+            done.insert(name);
+        }
+    }
+}
+
+/**
+ * Write the definitions for member functions to add elements to other
+ * element.
+ */
+void writeAdderDefinitions(const RNGItemList& items, Files& files)
+{
+    foreach (RNGItemPtr item, items) {
+        writeAdderDefinitions(item, files);
+    }
+}
+
+/**
+ * Retrieve the namespace prefix from the tag name.
+ */
+QString getPrefix(const QString& tag)
+{
+    QString prefix = tag.left(tag.indexOf(":"));
+    if (prefix.isNull()) {
+        prefix = "";
+    }
+    return prefix;
+}
+
+/**
+ * Get the stream for the combination of the two tags.
+ * For tag1 = "office:text" and tag2 = "text:p", this returns a stream to a file
+ * "writeodfofficetext.h".
+ */
+QTextStream& Files::getFile(const QString& tag1, const QString& tag2 = QString())
+{
+    // each tag can result in either no prefix or a prefix
+    // if one if the prefixes is empty and the other is not, then the first
+    // prefix is given a value
+    QString prefix = getPrefix(tag1);
+    QString prefix2 = getPrefix(tag2);
+    if (prefix.isEmpty() || prefix == prefix2) {
+        prefix = prefix2;
+        prefix2 = "";
+    }
+    if (files.contains(prefix) && files[prefix].contains(prefix2)) {
+        return *files[prefix][prefix2];
+    }
+    QString name = "writeodf" + prefix + prefix2 + ".h";
+    QString path = outdir + "/" + name;
+    QFile* file = new QFile(path);
+    if (!file->open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
+        fatal() << file->errorString();
+    }
+    QTextStream* out = new QTextStream(file);
+    name = name.replace(".", "_").toUpper();
+
+    *out << "#ifndef " + name + "\n";
+    *out << "#define " + name + "\n";
+    if (name == "WRITEODF_H") {
+        *out << "#include \"writeodf/odfwriter.h\"\n";
+    } else {
+        *out << "#include \"writeodf.h\"\n";
+    }
+    if (!prefix2.isEmpty() && prefix2 != prefix) {
+        *out << "#include \"writeodf" + prefix + ".h\"\n";
+        *out << "#include \"writeodf" + prefix2 + ".h\"\n";
+    } else {
+        *out << "namespace " << ns << " {\n";
+    }
+    files[prefix][prefix2] = out;
+    return *out;
+}
+
+/**
+ * Close the namespace if it was opened previously.
+ */
+void Files::closeNamespace()
+{
+    typedef const QMap<QString,QTextStream*> map;
+    foreach (map& m, files) {
+        map::ConstIterator i = m.begin();
+        while (i != m.end()) {
+            if (!i.key().isNull() && !ns.isEmpty()) {
+                *i.value() << "}\n";
+            }
+            ++i;
+        }
+    }
+}
+
+/**
+ * Write the header files.
+ */
+void write(const RNGItemList& items, QString outdir)
+{
+    Files files(outdir);
+
+    QTextStream& out = files.getFile("", "");
+    RNGItemList undefined = items;
+    RNGItemList defined;
+    addInOrder(undefined, defined);
+    // declare all classes
+    foreach (RNGItemPtr item, defined) {
+        if (!item->isAttribute()) {
+            out << "class " << item->cppName << ";\n";
+        }
+    }
+    foreach (RNGItemPtr item, defined) {
+        if (item->isElement()) {
+            defineElement(files.getFile(item->name()), item);
+        } else if (item->isDefine()) {
+            defineGroup(out, item);
+        }
+    }
+    files.closeNamespace();
+    writeAdderDefinitions(defined, files);
+}
+
+/**
+ * Convert the given rng file to a collection of header files.
+ */
+void convert(const QString& rngfile, const QString& outdir)
+{
+    QDomDocument dom = loadDOM(rngfile);
+    RNGItems items;
+    RNGItemPtr start = getDefines(dom.documentElement(), items);
+    RNGItems collected;
+    //qDebug() << "define " << items.size();
+    //collect(items, collected);
+    collected = items;
+    //qDebug() << "collect " << collected.size();
+    RNGItems resolved;
+    resolveDefines(start, collected, resolved);
+    //qDebug() << "resolve " << resolved.size();
+    //while (expand(resolved)) {}
+    resolved.remove(start);
+    //qDebug() << "expand " << resolved.size();
+    resolveAttributeDataTypes(resolved);
+    while (reduce(resolved)) {}
+    //qDebug() << "reduce " << resolved.size();
+    RNGItemList list = resolved.toList().toVector();
+    //qDebug() << "filteredItems " << list.size();
+    qStableSort(list.begin(), list.end(), rngItemPtrLessThan);
+    makeCppNames(list);
+    write(list, outdir);
+    //qDebug() << list.size();
+}
+
+int main(int argc, char *argv[])
+{
+    QString rngfile;
+    QString outdir;
+    if (argc != 3) {
+        fatal() << "Usage " << argv[0] << " rngfile outputdir";
+    } else {
+        rngfile = argv[1];
+        outdir = argv[2];
+    }
+    convert(rngfile, outdir);
+    return 0;
+}
diff --git a/filters/libmso/CMakeLists.txt b/filters/libmso/CMakeLists.txt
index 6bc145f..b7bdd99 100644
--- a/filters/libmso/CMakeLists.txt
+++ b/filters/libmso/CMakeLists.txt
@@ -1,4 +1,5 @@
-include_directories( ${QT_INCLUDES} ${KDE4_INCLUDES} ../../libs/odf)
+include_directories( ${QT_INCLUDES} ${KDE4_INCLUDES}
+    ${KOODF_INCLUDES})
 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${KDE4_ENABLE_EXCEPTIONS}")
 if(NOT MSVC AND NOT (WIN32 AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Intel"))
     if("${CMAKE_CXX_COMPILER_ID}" MATCHES "SunPro")
diff --git a/filters/libmso/shapes.cpp b/filters/libmso/shapes.cpp
index 073e061..b5a6d99 100644
--- a/filters/libmso/shapes.cpp
+++ b/filters/libmso/shapes.cpp
@@ -32,6 +32,7 @@
 #include "drawstyle.h"
 #include "msodraw.h"
 #include "generated/leinputstream.h"
+#include "writeodf/writeodfdraw.h"
 
 #include <KoXmlWriter.h>
 #include <kdebug.h>
@@ -43,17 +44,7 @@
 
 
 using namespace MSO;
-
-namespace
-{
-void equation(Writer& out, const char* name, const char* formula)
-{
-    out.xml.startElement("draw:equation");
-    out.xml.addAttribute("draw:name", name);
-    out.xml.addAttribute("draw:formula", formula);
-    out.xml.endElement();
-}
-}
+using namespace writeodf;
 
 qint16
 ODrawToOdf::normalizeRotation(qreal rotation)
@@ -125,27 +116,23 @@ void ODrawToOdf::processRectangle(const OfficeArtSpContainer& \
o, Writer& out)  // see bug https://bugs.kde.org/show_bug.cgi?id=285577
             processPictureFrame(o, out);
         } else {
-            out.xml.startElement("draw:custom-shape");
+            draw_custom_shape rect(&out.xml);
             processStyleAndText(o, out);
-            out.xml.startElement("draw:enhanced-geometry");
-            out.xml.addAttribute("svg:viewBox", "0 0 21600 21600");
-            out.xml.addAttribute("draw:enhanced-path", "M 0 0 L 21600 0 21600 21600 \
                0 21600 0 0 Z N");
-            out.xml.addAttribute("draw:type", "rectangle");
+            draw_enhanced_geometry eg(rect.add_draw_enhanced_geometry());
+            eg.set_svg_viewBox("0 0 21600 21600");
+            eg.set_draw_enhanced_path("M 0 0 L 21600 0 21600 21600 0 21600 0 0 Z \
N"); +            eg.set_draw_type("rectangle");
             setShapeMirroring(o, out);
-            out.xml.endElement(); // draw:enhanced-geometry
-            out.xml.endElement(); // draw:custom-shape
         }
     }
 }
 
 void ODrawToOdf::processTextBox(const OfficeArtSpContainer& o, Writer& out)
 {
-    out.xml.startElement("draw:frame");
+    draw_frame frame(&out.xml);
     processStyle(o, out);
-    out.xml.startElement("draw:text-box");
+    draw_text_box text(frame.add_draw_text_box());
     processText(o, out);
-    out.xml.endElement(); // draw:text-box
-    out.xml.endElement(); // draw:frame
 }
 
 void ODrawToOdf::processLine(const OfficeArtSpContainer& o, Writer& out)
@@ -164,16 +151,14 @@ void ODrawToOdf::processLine(const OfficeArtSpContainer& o, \
Writer& out)  qSwap(x1, x2);
     }
 
-    out.xml.startElement("draw:line");
-    out.xml.addAttribute("svg:y1", client->formatPos(out.vOffset(y1)));
-    out.xml.addAttribute("svg:y2", client->formatPos(out.vOffset(y2)));
-    out.xml.addAttribute("svg:x1", client->formatPos(out.hOffset(x1)));
-    out.xml.addAttribute("svg:x2", client->formatPos(out.hOffset(x2)));
+    draw_line line(&out.xml,
+                   client->formatPos(out.hOffset(x1)),
+                   client->formatPos(out.hOffset(x2)),
+                   client->formatPos(out.vOffset(y1)),
+                   client->formatPos(out.vOffset(y2)));
     addGraphicStyleToDrawElement(out, o);
-    out.xml.addAttribute("draw:layer", "layout");
+    line.set_draw_layer("layout");
     processText(o, out);
-
-    out.xml.endElement();
 }
 
 void ODrawToOdf::drawStraightConnector1(qreal l, qreal t, qreal r, qreal b, Writer& \
out, QPainterPath &shapePath) const @@ -385,6 +370,7 @@ void \
ODrawToOdf::processConnector(const OfficeArtSpContainer& o, Writer& out, Pa  
     m.translate( shapeRect.center().x(), shapeRect.center().y() );
 
+    // the viewbox should be set, where is this done for draw:connector?
     out.xml.startElement("draw:connector");
     addGraphicStyleToDrawElement(out, o);
     out.xml.addAttribute("draw:layer", "layout");
@@ -417,7 +403,7 @@ void ODrawToOdf::processPictureFrame(const OfficeArtSpContainer& \
o, Writer& out)  // A value of 0x00000000 MUST be ignored.  [MS-ODRAW] — v20101219
     if (!ds.pib()) return;
 
-    out.xml.startElement("draw:frame");
+    draw_frame frame(&out.xml);
     processStyle(o, out);
 
     //NOTE: OfficeArtClienData might contain additional information
@@ -429,26 +415,21 @@ void ODrawToOdf::processPictureFrame(const \
OfficeArtSpContainer& o, Writer& out)  }
     // if the image cannot be found, just place an empty frame
     if (url.isEmpty()) {
-        out.xml.endElement(); //draw:frame
         return;
     }
-    out.xml.startElement("draw:image");
-    out.xml.addAttribute("xlink:href", url);
-    out.xml.addAttribute("xlink:type", "simple");
-    out.xml.addAttribute("xlink:show", "embed");
-    out.xml.addAttribute("xlink:actuate", "onLoad");
-    out.xml.endElement(); // image
-    out.xml.endElement(); // frame
+    draw_image image(frame.add_draw_image());
+    image.set_xlink_href(url);
+    image.set_xlink_type("simple");
+    image.set_xlink_show("embed");
+    image.set_xlink_actuate("onLoad");
 }
 
 void ODrawToOdf::processNotPrimitive(const MSO::OfficeArtSpContainer& o, Writer& \
out)  {
-    out.xml.startElement("draw:custom-shape");
+    draw_custom_shape shape(&out.xml);
     processStyleAndText(o, out);
-    out.xml.startElement("draw:enhanced-geometry");
+    draw_enhanced_geometry eg(shape.add_draw_enhanced_geometry());
     setEnhancedGeometry(o, out);
-    out.xml.endElement(); //draw:enhanced-geometry
-    out.xml.endElement(); //draw:custom-shape
 }
 
 
diff --git a/filters/libmso/shapes2.cpp b/filters/libmso/shapes2.cpp
index 0f0b906..6146f78 100644
--- a/filters/libmso/shapes2.cpp
+++ b/filters/libmso/shapes2.cpp
@@ -19,18 +19,24 @@
 
 #include <KoXmlWriter.h>
 #include "drawstyle.h"
+#include "writeodf/writeodfdraw.h"
 
 using namespace MSO;
+using namespace writeodf;
 
 namespace
 {
 void equation(Writer& out, const char* name, const char* formula)
 {
-    out.xml.startElement("draw:equation");
-    out.xml.addAttribute("draw:name", name);
-    out.xml.addAttribute("draw:formula", formula);
-    out.xml.endElement();
-
+    draw_equation eq(&out.xml);
+    eq.set_draw_name(name);
+    eq.set_draw_formula(formula);
+}
+void equation(draw_enhanced_geometry& eg, const char* name, const char* formula)
+{
+    draw_equation eq(eg.add_draw_equation());
+    eq.set_draw_name(name);
+    eq.set_draw_formula(formula);
 }
 
 }
@@ -51,67 +57,59 @@ void equation(Writer& out, const char* name, const char* formula)
 
 
 void ODrawToOdf::processParallelogram(const MSO::OfficeArtSpContainer& o, Writer& \
                out) {
-    out.xml.startElement("draw:custom-shape");
+    draw_custom_shape shape(&out.xml);
     processStyleAndText(o, out);
 
-    out.xml.startElement("draw:enhanced-geometry");
-    out.xml.addAttribute("draw:glue-points", "?f6 0 10800 ?f8 ?f11 10800 ?f9 21600 \
10800 ?f10 ?f5 10800"); +    draw_enhanced_geometry \
eg(shape.add_draw_enhanced_geometry()); +    eg.set_draw_glue_points("?f6 0 10800 ?f8 \
?f11 10800 ?f9 21600 10800 ?f10 ?f5 10800");  processModifiers(o, out, QList<int>() \
                << 5400);
-    out.xml.addAttribute("svg:viewBox", "0 0 21600 21600");
-    out.xml.addAttribute("draw:enhanced-path", "M ?f0 0 L 21600 0 ?f1 21600 0 21600 \
                Z N");
-    out.xml.addAttribute("draw:type", "parallelogram");
-    out.xml.addAttribute("draw:text-areas", "?f3 ?f3 ?f4 ?f4");
-    setShapeMirroring(o, out);
-    equation(out,"f0","$0 ");
-    equation(out,"f1","21600-$0 ");
-    equation(out,"f2","$0 *10/24");
-    equation(out,"f3","?f2 +1750");
-    equation(out,"f4","21600-?f3 ");
-    equation(out,"f5","?f0 /2");
-    equation(out,"f6","10800+?f5 ");
-    equation(out,"f7","?f0 -10800");
-    equation(out,"f8","if(?f7 ,?f13 ,0)");
-    equation(out,"f9","10800-?f5 ");
-    equation(out,"f10","if(?f7 ,?f12 ,21600)");
-    equation(out,"f11","21600-?f5 ");
-    equation(out,"f12","21600*10800/?f0 ");
-    equation(out,"f13","21600-?f12 ");
-    out.xml.startElement("draw:handle");
-    out.xml.addAttribute("draw:handle-position", "$0 top");
-    out.xml.addAttribute("draw:handle-range-x-maximum", "21600");
-    out.xml.addAttribute("draw:handle-range-x-minimum", "0");
-    out.xml.endElement(); // draw:handle
-    out.xml.endElement(); // enhanced geometry
-    out.xml.endElement(); // custom shape
+    eg.set_svg_viewBox("0 0 21600 21600");
+    eg.set_draw_enhanced_path("M ?f0 0 L 21600 0 ?f1 21600 0 21600 Z N");
+    eg.set_draw_type("parallelogram");
+    eg.set_draw_text_areas("?f3 ?f3 ?f4 ?f4");
+    setShapeMirroring(o, out);
+    equation(eg,"f0","$0 ");
+    equation(eg,"f1","21600-$0 ");
+    equation(eg,"f2","$0 *10/24");
+    equation(eg,"f3","?f2 +1750");
+    equation(eg,"f4","21600-?f3 ");
+    equation(eg,"f5","?f0 /2");
+    equation(eg,"f6","10800+?f5 ");
+    equation(eg,"f7","?f0 -10800");
+    equation(eg,"f8","if(?f7 ,?f13 ,0)");
+    equation(eg,"f9","10800-?f5 ");
+    equation(eg,"f10","if(?f7 ,?f12 ,21600)");
+    equation(eg,"f11","21600-?f5 ");
+    equation(eg,"f12","21600*10800/?f0 ");
+    equation(eg,"f13","21600-?f12 ");
+    draw_handle handle(eg.add_draw_handle("$0 top"));
+    handle.set_draw_handle_radius_range_maximum("21000");
+    handle.set_draw_handle_radius_range_minimum("0");
 }
 
 
 void ODrawToOdf::processTrapezoid(const MSO::OfficeArtSpContainer& o, Writer& out) {
-    out.xml.startElement("draw:custom-shape");
+    draw_custom_shape shape(&out.xml);
     processStyleAndText(o, out);
 
-    out.xml.startElement("draw:enhanced-geometry");
-    out.xml.addAttribute("draw:glue-points", "?f6 10800 10800 21600 ?f5 10800 10800 \
0"); +    draw_enhanced_geometry eg(shape.add_draw_enhanced_geometry());
+    eg.set_draw_glue_points("?f6 10800 10800 21600 ?f5 10800 10800 0");
     processModifiers(o, out, QList<int>() << 5400);
-    out.xml.addAttribute("svg:viewBox", "0 0 21600 21600");
-    out.xml.addAttribute("draw:enhanced-path", "M 0 0 L 21600 0 ?f0 21600 ?f1 21600 \
                Z N");
-    out.xml.addAttribute("draw:type", "trapezoid");
-    out.xml.addAttribute("draw:text-areas", "?f3 ?f3 ?f4 ?f4");
-    setShapeMirroring(o, out);
-    equation(out,"f0","21600-$0 ");
-    equation(out,"f1","$0 ");
-    equation(out,"f2","$0 *10/18");
-    equation(out,"f3","?f2 +1750");
-    equation(out,"f4","21600-?f3 ");
-    equation(out,"f5","$0 /2");
-    equation(out,"f6","21600-?f5 ");
-    out.xml.startElement("draw:handle");
-    out.xml.addAttribute("draw:handle-position", "$0 bottom");
-    out.xml.addAttribute("draw:handle-range-x-maximum", "10800");
-    out.xml.addAttribute("draw:handle-range-x-minimum", "0");
-    out.xml.endElement(); // draw:handle
-    out.xml.endElement(); // enhanced geometry
-    out.xml.endElement(); // custom shape
+    eg.set_svg_viewBox("0 0 21600 21600");
+    eg.set_draw_enhanced_path("M 0 0 L 21600 0 ?f0 21600 ?f1 21600 Z N");
+    eg.set_draw_type("trapezoid");
+    eg.set_draw_text_areas("?f3 ?f3 ?f4 ?f4");
+    setShapeMirroring(o, out);
+    equation(eg,"f0","21600-$0 ");
+    equation(eg,"f1","$0 ");
+    equation(eg,"f2","$0 *10/18");
+    equation(eg,"f3","?f2 +1750");
+    equation(eg,"f4","21600-?f3 ");
+    equation(eg,"f5","$0 /2");
+    equation(eg,"f6","21600-?f5 ");
+    draw_handle handle(eg.add_draw_handle("$0 bottom"));
+    handle.set_draw_handle_radius_range_maximum("10000");
+    handle.set_draw_handle_radius_range_minimum("0");
 }
 
 
diff --git a/filters/sheets/excel/import/CMakeLists.txt \
b/filters/sheets/excel/import/CMakeLists.txt index 2466218..225032c 100644
--- a/filters/sheets/excel/import/CMakeLists.txt
+++ b/filters/sheets/excel/import/CMakeLists.txt
@@ -3,6 +3,7 @@ include_directories(
     ${CMAKE_CURRENT_SOURCE_DIR}/../sidewinder
     ${CMAKE_BINARY_DIR}/filters/
     ${KOMAIN_INCLUDES}
+    ${KOODF_INCLUDES}
     ${CMAKE_SOURCE_DIR}/filters/sheets/xlsx
     ${CMAKE_SOURCE_DIR}/filters/libmso
     ${CMAKE_SOURCE_DIR}/filters/libmsooxml
diff --git a/filters/sheets/excel/import/excelimporttoods.cc \
b/filters/sheets/excel/import/excelimporttoods.cc index de788d4..53ee4f0 100644
--- a/filters/sheets/excel/import/excelimporttoods.cc
+++ b/filters/sheets/excel/import/excelimporttoods.cc
@@ -50,6 +50,13 @@
 #include <iostream>
 #include "ODrawClient.h"
 #include "ImportUtils.h"
+#include "writeodf/writeodfofficedc.h"
+#include "writeodf/writeodfofficemeta.h"
+#include "writeodf/writeodfofficestyle.h"
+#include "writeodf/writeodfofficetable.h"
+#include "writeodf/writeodftext.h"
+#include "writeodf/writeodfnumber.h"
+#include "writeodf/helpers.h"
 
 K_PLUGIN_FACTORY(ExcelImportFactory, registerPlugin<ExcelImport>();)
 K_EXPORT_PLUGIN(ExcelImportFactory("calligrafilters"))
@@ -58,6 +65,8 @@ K_EXPORT_PLUGIN(ExcelImportFactory("calligrafilters"))
 #define UNICODE_GBP 0x00A3
 #define UNICODE_JPY 0x00A5
 
+using namespace writeodf;
+
 namespace Swinder
 {
 // qHash function to support hashing by Swinder::FormatFont instances.
@@ -114,6 +123,14 @@ public:
     QHash<Cell*, QByteArray> cellShapes;
     QHash<Sheet*, QByteArray> sheetShapes;
 
+    struct CellValue {
+        Value value;
+        QString str;
+        QString linkName;
+        QString linkLocation;
+        Hyperlink link;
+    };
+
     QHash<Row*,int> rowsRepeatedHash;
     int rowsRepeated(Row* row, int rowIndex);
 
@@ -130,17 +147,20 @@ public:
     int rowFormatIndex;
     int cellFormatIndex;
 
-    void processWorkbookForBody(KoOdfWriteStore* store, Workbook* workbook, \
KoXmlWriter* xmlWriter); +    void processWorkbookForBody(Workbook* workbook, \
                KoXmlWriter* xmlWriter, office_body& body);
     void processWorkbookForStyle(Workbook* workbook, KoXmlWriter* xmlWriter);
-    void processSheetForBody(KoOdfWriteStore* store, Sheet* sheet, KoXmlWriter* \
xmlWriter); +    void processSheetForBody(Sheet* sheet, KoXmlWriter* xmlWriter, \
office_spreadsheet& spreadsheet);  void processSheetForStyle(Sheet* sheet, \
KoXmlWriter* xmlWriter);  void processSheetForHeaderFooter(Sheet* sheet, KoXmlWriter* \
                writer);
-    void processHeaderFooterStyle(const QString& text, KoXmlWriter* xmlWriter);
-    void processColumnForBody(Sheet* sheet, int columnIndex, KoXmlWriter* xmlWriter, \
unsigned& outlineLevel); +    void processHeaderFooterStyle(const QString& text, \
text_p& p); +    void processColumnForBody(Sheet* sheet, int columnIndex, \
                group_table_columns_and_groups& table, unsigned& outlineLevel);
     void processColumnForStyle(Sheet* sheet, int columnIndex, KoXmlWriter* \
                xmlWriter);
-    int processRowForBody(KoOdfWriteStore* store, Sheet* sheet, int rowIndex, \
KoXmlWriter* xmlWriter, unsigned& outlineLevel); +    int processRowForBody(Sheet* \
sheet, int rowIndex, KoXmlWriter* xmlWriter, group_table_rows_and_groups& table, \
                unsigned& outlineLevel);
     int processRowForStyle(Sheet* sheet, int rowIndex, KoXmlWriter* xmlWriter);
-    void processCellForBody(KoOdfWriteStore* store, Cell* cell, int rowsRepeat, \
KoXmlWriter* xmlWriter); +    void processCellForBody(Cell* cell, KoXmlWriter* \
xmlWriter, table_table_row& row); +    void processCellAttributesForBody(Cell* cell, \
group_table_table_cell_attlist& c, CellValue& cellValue); +    void \
processCellText(Cell* cell, group_paragraph_content& content, CellValue& cellValue); \
+    void processCellContentForBody(Cell* cell, KoXmlWriter* xmlWriter, \
group_table_table_cell_content& c, CellValue& cellValue);  void \
                processCellForStyle(Cell* cell, KoXmlWriter* xmlWriter);
     QString processCellFormat(const Format* format, const QString& formula = \
                QString());
     QString processRowFormat(Format* format, const QString& breakBefore = QString(), \
int rowRepeat = 1, double rowHeight = -1); @@ -339,17 +359,12 @@ bool \
ExcelImport::Private::createContent(KoOdfWriteStore* store)  }
 
     // FIXME this is dummy and hardcoded, replace with real font names
-    contentWriter->startElement("office:font-face-decls");
-    contentWriter->startElement("style:font-face");
-    contentWriter->addAttribute("style:name", "Arial");
-    contentWriter->addAttribute("svg:font-family", "Arial");
-    contentWriter->endElement(); // style:font-face
-    contentWriter->startElement("style:font-face");
-    contentWriter->addAttribute("style:name", "Times New Roman");
-    contentWriter->addAttribute("svg:font-family", "&apos;Times New Roman&apos;");
-    contentWriter->endElement(); // style:font-face
-    contentWriter->endElement(); // office:font-face-decls
-
+    office_font_face_decls decls(contentWriter);
+    style_font_face font(decls.add_style_font_face("Arial"));
+    font.set_svg_font_family("Arial");
+    style_font_face font2(decls.add_style_font_face("Times New Roman"));
+    font2.set_svg_font_family("&apos;Times New Roman&apos;");
+    decls.end();
 
     defaultColumnStyleIndex = 0;
     // office:automatic-styles
@@ -364,9 +379,9 @@ bool ExcelImport::Private::createContent(KoOdfWriteStore* store)
 
 
     // office:body
-    bodyWriter->startElement("office:body");
-    processWorkbookForBody(store, workbook, bodyWriter);
-    bodyWriter->endElement();  // office:body
+    office_body body(bodyWriter);
+    processWorkbookForBody(workbook, bodyWriter, body);
+    body.end();
 
     return store->closeContentWriter();
 }
@@ -383,29 +398,28 @@ bool ExcelImport::Private::createStyles(KoStore* store, \
KoXmlWriter* manifestWri  KoXmlWriter* stylesWriter = new KoXmlWriter(&dev);
 
     stylesWriter->startDocument("office:document-styles");
-    stylesWriter->startElement("office:document-styles");
-    stylesWriter->addAttribute("xmlns:office", \
                "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
-    stylesWriter->addAttribute("xmlns:style", \
                "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
-    stylesWriter->addAttribute("xmlns:text", \
                "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
-    stylesWriter->addAttribute("xmlns:table", \
                "urn:oasis:names:tc:opendocument:xmlns:table:1.0");
-    stylesWriter->addAttribute("xmlns:draw", \
                "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
-    stylesWriter->addAttribute("xmlns:fo", \
                "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
-    stylesWriter->addAttribute("xmlns:svg", \
                "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
-    stylesWriter->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
-    stylesWriter->addAttribute("xmlns:chart", \
                "urn:oasis:names:tc:opendocument:xmlns:chart:1.0");
-    stylesWriter->addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
-    stylesWriter->addAttribute("xmlns:meta", \
                "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
-    stylesWriter->addAttribute("xmlns:number", \
                "urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0");
-    //stylesWriter->addAttribute("xmlns:dr3d", \
                "urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0");
-    stylesWriter->addAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML");
-    stylesWriter->addAttribute("xmlns:of", \
                "urn:oasis:names:tc:opendocument:xmlns:of:1.2");
-    stylesWriter->addAttribute("office:version", "1.2");
+    office_document_styles styles(stylesWriter);
+    styles.addAttribute("xmlns:office", \
"urn:oasis:names:tc:opendocument:xmlns:office:1.0"); +    \
styles.addAttribute("xmlns:style", \
"urn:oasis:names:tc:opendocument:xmlns:style:1.0"); +    \
styles.addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0"); \
+    styles.addAttribute("xmlns:table", \
"urn:oasis:names:tc:opendocument:xmlns:table:1.0"); +    \
styles.addAttribute("xmlns:draw", \
"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"); +    \
styles.addAttribute("xmlns:fo", \
"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"); +    \
styles.addAttribute("xmlns:svg", \
"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"); +    \
styles.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); +    \
styles.addAttribute("xmlns:chart", \
"urn:oasis:names:tc:opendocument:xmlns:chart:1.0"); +    \
styles.addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/"); +    \
styles.addAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0"); \
+    styles.addAttribute("xmlns:number", \
"urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0"); +    \
//styles.addAttribute("xmlns:dr3d", \
"urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0"); +    \
styles.addAttribute("xmlns:math", "http://www.w3.org/1998/Math/MathML"); +    \
styles.addAttribute("xmlns:of", "urn:oasis:names:tc:opendocument:xmlns:of:1.2");  
     mainStyles->saveOdfStyles(KoGenStyles::MasterStyles, stylesWriter);
     mainStyles->saveOdfStyles(KoGenStyles::DocumentStyles, stylesWriter); // \
                office:style
     mainStyles->saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, stylesWriter); \
// office:automatic-styles  
-    stylesWriter->endElement();  // office:document-styles
+    styles.end();
     stylesWriter->endDocument();
 
     delete stylesWriter;
@@ -421,65 +435,58 @@ bool ExcelImport::Private::createMeta(KoOdfWriteStore* store)
     KoStoreDevice dev(store->store());
     KoXmlWriter* metaWriter = new KoXmlWriter(&dev);
     metaWriter->startDocument("office:document-meta");
-    metaWriter->startElement("office:document-meta");
-    metaWriter->addAttribute("xmlns:office", \
                "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
-    metaWriter->addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
-    metaWriter->addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
-    metaWriter->addAttribute("xmlns:meta", \
                "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
-    metaWriter->startElement("office:meta");
+
+    office_document_meta metadoc(metaWriter);
+    metadoc.addAttribute("xmlns:office", \
"urn:oasis:names:tc:opendocument:xmlns:office:1.0"); +    \
metadoc.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink"); +    \
metadoc.addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/"); +    \
metadoc.addAttribute("xmlns:meta", "urn:oasis:names:tc:opendocument:xmlns:meta:1.0"); \
+    office_meta meta(metadoc.add_office_meta());  
     if (workbook->hasProperty(Workbook::PIDSI_TITLE)) {
-        metaWriter->startElement("dc:title");
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_TITLE).toString());
                
-        metaWriter->endElement();
+        meta.add_dc_title().addTextNode(
+                    workbook->property(Workbook::PIDSI_TITLE).toString());
     }
     if (workbook->hasProperty(Workbook::PIDSI_SUBJECT)) {
-        metaWriter->startElement("dc:subject", false);
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_SUBJECT).toString());
                
-        metaWriter->endElement();
+        meta.add_dc_subject().addTextNode(
+                    workbook->property(Workbook::PIDSI_SUBJECT).toString());
     }
     if (workbook->hasProperty(Workbook::PIDSI_AUTHOR)) {
-        metaWriter->startElement("dc:creator", false);
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_AUTHOR).toString());
                
-        metaWriter->endElement();
+        meta.add_dc_creator().addTextNode(
+                    workbook->property(Workbook::PIDSI_AUTHOR).toString());
     }
     if (workbook->hasProperty(Workbook::PIDSI_KEYWORDS)) {
-        metaWriter->startElement("meta:keyword", false);
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_KEYWORDS).toString());
                
-        metaWriter->endElement();
+        meta.add_meta_keyword().addTextNode(
+                    workbook->property(Workbook::PIDSI_KEYWORDS).toString());
     }
     if (workbook->hasProperty(Workbook::PIDSI_COMMENTS)) {
-        metaWriter->startElement("meta:comments", false);
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_COMMENTS).toString());
                
-        metaWriter->endElement();
+        meta_user_defined c(meta.add_meta_user_defined("comments"));
+        c.set_meta_value_type("string");
+        c.addTextNode(
+                    workbook->property(Workbook::PIDSI_COMMENTS).toString());
     }
     if (workbook->hasProperty(Workbook::PIDSI_REVNUMBER)) {
-        metaWriter->startElement("meta:editing-cycles", false);
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_REVNUMBER).toString());
                
-        metaWriter->endElement();
+        meta.add_meta_editing_cycles().addTextNode(
+                    workbook->property(Workbook::PIDSI_REVNUMBER).toString());
     }
     if (workbook->hasProperty(Workbook::PIDSI_LASTPRINTED_DTM)) {
-        metaWriter->startElement("dc:print-date", false);
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_LASTPRINTED_DTM).toString());
                
-        metaWriter->endElement();
+        meta.add_meta_print_date().addTextNode(
+                    workbook->property(Workbook::PIDSI_LASTPRINTED_DTM).toString());
     }
     if (workbook->hasProperty(Workbook::PIDSI_CREATE_DTM)) {
-        metaWriter->startElement("meta:creation-date", false);
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_CREATE_DTM).toString());
                
-        metaWriter->endElement();
+        meta.add_meta_creation_date().addTextNode(
+                    workbook->property(Workbook::PIDSI_CREATE_DTM).toString());
     }
     if (workbook->hasProperty(Workbook::PIDSI_LASTSAVED_DTM)) {
-        metaWriter->startElement("dc:date", false);
-        metaWriter->addTextNode(workbook->property(Workbook::PIDSI_LASTSAVED_DTM).toString());
                
-        metaWriter->endElement();
+        meta.add_dc_date().addTextNode(
+                    workbook->property(Workbook::PIDSI_LASTSAVED_DTM).toString());
     }
 
     //if( workbook->hasProperty( Workbook::PIDSI_TEMPLATE )  ) \
metaWriter->addAttribute( "dc:", workbook->property( Workbook::PIDSI_TEMPLATE \
                ).toString() );
     //if( workbook->hasProperty( Workbook::PIDSI_LASTAUTHOR )  ) \
metaWriter->addAttribute( "dc:", workbook->property( Workbook::PIDSI_LASTAUTHOR \
                ).toString() );
     //if( workbook->hasProperty( Workbook::PIDSI_EDITTIME )  ) \
metaWriter->addAttribute( "dc:date", workbook->property( Workbook::PIDSI_EDITTIME \
).toString() );  
-    metaWriter->endElement(); // office:meta
-    metaWriter->endElement(); // office:document-meta
+    metadoc.end();
     metaWriter->endDocument();
 
     delete metaWriter;
@@ -494,63 +501,54 @@ bool ExcelImport::Private::createSettings(KoOdfWriteStore* \
store)  
     KoStoreDevice dev(store->store());
     KoXmlWriter* settingsWriter = KoOdfWriteStore::createOasisXmlWriter(&dev, \
                "office:document-settings");
-    settingsWriter->startElement("office:settings");
-    settingsWriter->startElement("config:config-item-set");
-    settingsWriter->addAttribute("config:name", "view-settings");
+    {
+    office_settings settings(settingsWriter);
+    config_config_item_set \
set(settings.add_config_config_item_set("view-settings"));  
     // units...
 
     // settings
-    settingsWriter->startElement("config:config-item-map-indexed");
-    settingsWriter->addAttribute("config:name", "Views");
-    settingsWriter->startElement("config:config-item-map-entry");
-    settingsWriter->addConfigItem("ViewId", QString::fromLatin1("View1"));
+    config_config_item_map_indexed \
map(set.add_config_config_item_map_indexed("Views")); +    \
config_config_item_map_entry entry(map.add_config_config_item_map_entry()); +
+    addConfigItem(entry, "ViewId", QString::fromLatin1("View1"));
     if(Sheet *sheet = workbook->sheet(workbook->activeTab()))
-        settingsWriter->addConfigItem("ActiveTable", sheet->name());
+            addConfigItem(entry, "ActiveTable", sheet->name());
 
-    settingsWriter->startElement("config:config-item-map-named");
-    settingsWriter->addAttribute("config:name", "Tables");
+    config_config_item_map_named \
named(entry.add_config_config_item_map_named("Tables"));  for(uint i = 0; i < \
workbook->sheetCount(); ++i) {  Sheet* sheet = workbook->sheet(i);
-        settingsWriter->startElement("config:config-item-map-entry");
-        settingsWriter->addAttribute("config:name", sheet->name());
+        config_config_item_map_entry \
entry(named.add_config_config_item_map_entry()); +        \
entry.set_config_name(sheet->name());  QPoint point = sheet->firstVisibleCell();
-        settingsWriter->addConfigItem("CursorPositionX", point.x());
-        settingsWriter->addConfigItem("CursorPositionY", point.y());
+        addConfigItem(entry, "CursorPositionX", point.x());
+        addConfigItem(entry, "CursorPositionY", point.y());
         //TODO how should we replace these settings?
 //         settingsWriter->addConfigItem("xOffset", columnWidth(sheet,point.x()));
 //         settingsWriter->addConfigItem("yOffset", rowHeight(sheet,point.y()));
-        settingsWriter->addConfigItem("ShowZeroValues", sheet->showZeroValues());
-        settingsWriter->addConfigItem("ShowGrid", sheet->showGrid());
-        settingsWriter->addConfigItem("FirstLetterUpper", false);
-        settingsWriter->addConfigItem("ShowFormulaIndicator", false);
-        settingsWriter->addConfigItem("ShowCommentIndicator", true);
-        settingsWriter->addConfigItem("ShowPageOutline", \
                sheet->isPageBreakViewEnabled()); // best match kspread provides
-        settingsWriter->addConfigItem("lcmode", false);
-        settingsWriter->addConfigItem("autoCalc", sheet->autoCalc());
-        settingsWriter->addConfigItem("ShowColumnNumber", false);
-        settingsWriter->endElement();
-    }
-    settingsWriter->endElement(); // config:config-item-map-named
-
-    settingsWriter->endElement(); // config:config-item-map-entry
-    settingsWriter->endElement(); // config:config-item-map-indexed
-    settingsWriter->endElement(); // config:config-item-set
-
-    settingsWriter->endElement(); // office:settings
-    settingsWriter->endElement(); // Root:element
+        addConfigItem(entry, "ShowZeroValues", sheet->showZeroValues());
+        addConfigItem(entry, "ShowGrid", sheet->showGrid());
+        addConfigItem(entry, "FirstLetterUpper", false);
+        addConfigItem(entry, "ShowFormulaIndicator", false);
+        addConfigItem(entry, "ShowCommentIndicator", true);
+        addConfigItem(entry, "ShowPageOutline", sheet->isPageBreakViewEnabled()); // \
best match kspread provides +        addConfigItem(entry, "lcmode", false);
+        addConfigItem(entry, "autoCalc", sheet->autoCalc());
+        addConfigItem(entry, "ShowColumnNumber", false);
+    }
+    } // end of block closes all elements
     settingsWriter->endDocument();
     delete settingsWriter;
     return store->store()->close();
 }
 
 // Processes the workbook content. The workbook is the top-level element for \
                content.
-void ExcelImport::Private::processWorkbookForBody(KoOdfWriteStore* store, Workbook* \
workbook, KoXmlWriter* xmlWriter) +void \
ExcelImport::Private::processWorkbookForBody(Workbook* workbook, KoXmlWriter* \
xmlWriter, office_body& body)  {
     if (!workbook) return;
     if (!xmlWriter) return;
 
-    xmlWriter->startElement("office:spreadsheet");
+    office_spreadsheet spreadsheet(body.add_office_spreadsheet());
 
     // count the number of rows in total to provide a good progress value
     rowsCountTotal = rowsCountDone = 0;
@@ -562,49 +560,37 @@ void \
ExcelImport::Private::processWorkbookForBody(KoOdfWriteStore* store, Workbo  // now \
start the whole work  for (unsigned i = 0; i < workbook->sheetCount(); i++) {
         Sheet* sheet = workbook->sheet(i);
-        processSheetForBody(store, sheet, xmlWriter);
+        processSheetForBody(sheet, xmlWriter, spreadsheet);
     }
 
     std::map<std::pair<unsigned, QString>, QString> &namedAreas = \
workbook->namedAreas();  if(namedAreas.size() > 0) {
-        xmlWriter->startElement("table:named-expressions");
+        table_named_expressions exprs(spreadsheet.add_table_named_expressions());
         for(std::map<std::pair<unsigned, QString>, QString>::iterator it = \
                namedAreas.begin(); it != namedAreas.end(); ++it) {
-            xmlWriter->startElement("table:named-range");
-            xmlWriter->addAttribute("table:name", it->first.second ); // e.g. "My \
Named Range"  QString range = it->second;
             if(range.startsWith(QLatin1Char('[')) && \
range.endsWith(QLatin1Char(']'))) {  range.remove(0, 1).chop(1);
             }
-            xmlWriter->addAttribute("table:cell-range-address", range); // e.g. \
                "$Sheet1.$B$2:.$B$3"
-            xmlWriter->endElement();//[Sheet1.$B$2:$B$3]
+            table_named_range(exprs.add_table_named_range(range, it->first.second));
         }
-        xmlWriter->endElement();
     }
 
-    bool openedDBRanges = false;
+    table_database_ranges ranges(spreadsheet.add_table_database_ranges());
     int rangeId = 1;
     for (unsigned i = 0; i < workbook->sheetCount(); i++) {
         QList<QRect> filters = workbook->filterRanges(i);
         QString sheetName = workbook->sheet(i)->name();
         if (filters.size()) {
-            if (!openedDBRanges) xmlWriter->startElement("table:database-ranges");
-            openedDBRanges = true;
-
             foreach (const QRect& filter, filters) {
                 QString sRange(encodeAddress(sheetName, filter.left(), \
filter.top()));  sRange.append(":");
                 sRange.append(encodeAddress(sheetName, filter.right(), \
                workbook->sheet(i)->maxRow()));
-                xmlWriter->startElement("table:database-range");
-                xmlWriter->addAttribute("table:name", \
                QString("excel-database-%1").arg(rangeId++));
-                xmlWriter->addAttribute("table:display-filter-buttons", "true");
-                xmlWriter->addAttribute("table:target-range-address", sRange);
-                xmlWriter->endElement(); // table:database-range
+                table_database_range range(ranges.add_table_database_range(sRange));
+                range.set_table_name(QString("excel-database-%1").arg(rangeId++));
+                range.set_table_display_filter_buttons("true");
             }
         }
     }
-    if (openedDBRanges) xmlWriter->endElement(); // table:database-ranges
-
-    xmlWriter->endElement();  // office:spreadsheet
 }
 
 // Processes the workbook styles. The workbook is the top-level element for content.
@@ -625,23 +611,22 @@ void ExcelImport::Private::processWorkbookForStyle(Workbook* \
workbook, KoXmlWrit  KoXmlWriter writer(&buf);
 
     //Hardcoded page-layout
-    writer.startElement("style:header-style");
-    writer.startElement("style:header-footer-properties");
-    writer.addAttribute("fo:min-height", "20pt");
-    writer.addAttribute("fo:margin-left", "0pt");
-    writer.addAttribute("fo:margin-right", "0pt");
-    writer.addAttribute("fo:margin-bottom", "10pt");
-    writer.endElement();
-    writer.endElement();
-
-    writer.startElement("style:footer-style");
-    writer.startElement("style:header-footer-properties");
-    writer.addAttribute("fo:min-height", "20pt");
-    writer.addAttribute("fo:margin-left", "0pt");
-    writer.addAttribute("fo:margin-right", "0pt");
-    writer.addAttribute("fo:margin-top", "10pt");
-    writer.endElement();
-    writer.endElement();
+    style_header_style header(&writer);
+    style_header_footer_properties hf(header.add_style_header_footer_properties());
+    hf.set_fo_min_height("20pt");
+    hf.set_fo_margin_left("0pt");
+    hf.set_fo_margin_right("0pt");
+    hf.set_fo_margin_bottom("10pt");
+    header.end();
+
+    style_footer_style footer(&writer);
+    style_header_footer_properties hf2(footer.add_style_header_footer_properties());
+    hf2.set_fo_min_height("20pt");
+    hf2.set_fo_margin_left("0pt");
+    hf2.set_fo_margin_right("0pt");
+    hf2.set_fo_margin_top("10pt");
+    footer.end();
+
     QString pageLyt = QString::fromUtf8(buf.buffer(), buf.buffer().size());
     buf.close();
     buf.setData("", 0);
@@ -668,16 +653,16 @@ void ExcelImport::Private::processWorkbookForStyle(Workbook* \
workbook, KoXmlWrit  }
 
 // Processes a sheet.
-void ExcelImport::Private::processSheetForBody(KoOdfWriteStore* store, Sheet* sheet, \
KoXmlWriter* xmlWriter) +void ExcelImport::Private::processSheetForBody(Sheet* sheet, \
KoXmlWriter* xmlWriter, office_spreadsheet& spreadsheet)  {
     if (!sheet) return;
     if (!xmlWriter) return;
 
-    xmlWriter->startElement("table:table");
+    table_table table(spreadsheet.add_table_table());
 
-    xmlWriter->addAttribute("table:name", sheet->name());
-    xmlWriter->addAttribute("table:print", "false");
-    xmlWriter->addAttribute("table:style-name", sheetStyles[sheetFormatIndex]);
+    table.set_table_name(sheet->name());
+    table.set_table_print("false");
+    table.set_table_style_name(sheetStyles[sheetFormatIndex]);
     ++sheetFormatIndex;
 
     if(sheet->password() != 0) {
@@ -687,52 +672,39 @@ void ExcelImport::Private::processSheetForBody(KoOdfWriteStore* \
store, Sheet* sh  }
 
     if (!sheet->drawObjects().isEmpty()) {
-        xmlWriter->startElement("table:shapes");
-        xmlWriter->addCompleteElement(sheetShapes[sheet]);
-        xmlWriter->endElement(); // table:shapes
+        table_shapes shapes(table.add_table_shapes());
+        shapes.addCompleteElement(sheetShapes[sheet]);
     }
 
 
     const unsigned columnCount = qMin(maximalColumnCount, sheet->maxColumn());
     unsigned outlineLevel = 0;
     for (unsigned i = 0; i <= columnCount; ++i) {
-        processColumnForBody(sheet, i, xmlWriter, outlineLevel);
-    }
-    while (outlineLevel > 0) {
-        xmlWriter->endElement(); // table:table-column-group
-        outlineLevel--;
+        processColumnForBody(sheet, i, table, outlineLevel);
     }
 
     // in odf default-cell-style's only apply to cells/rows/columns that are present \
                in the file while in Excel
     // row/column styles should apply to all cells in that row/column. So, try to \
                fake that behavior by writing
     // a number-columns-repeated to apply the styles/formattings to "all" columns.
     if (columnCount < maximalColumnCount-1) {
-        xmlWriter->startElement("table:table-column");
-        xmlWriter->addAttribute("table:style-name", \
                defaultColumnStyles[defaultColumnStyleIndex]);
-        xmlWriter->addAttribute("table:number-columns-repeated", maximalColumnCount \
                - 1 - columnCount);
-        xmlWriter->endElement();
+        table_table_column column(table.add_table_table_column());
+        column.set_table_style_name(defaultColumnStyles[defaultColumnStyleIndex]);
+        column.set_table_number_columns_repeated(maximalColumnCount - 1 - \
columnCount);  }
 
     // add rows
+    outlineLevel = 0;
     const unsigned rowCount = qMin(maximalRowCount, sheet->maxRow());
     for (unsigned i = 0; i <= rowCount;) {
-        i += processRowForBody(store, sheet, i, xmlWriter, outlineLevel);
-    }
-    while (outlineLevel > 0) {
-        xmlWriter->endElement(); // table:table-row-group
-        outlineLevel--;
+        i += processRowForBody(sheet, i, xmlWriter, table, outlineLevel);
     }
 
     // same we did above with columns is also needed for rows.
     if(rowCount < maximalRowCount-1) {
-        xmlWriter->startElement("table:table-row");
-        xmlWriter->addAttribute("table:number-rows-repeated", maximalRowCount - 1 - \
                rowCount);
-        xmlWriter->startElement("table:table-cell");
-        xmlWriter->endElement();
-        xmlWriter->endElement();
+        table_table_row row(table.add_table_table_row());
+        row.set_table_number_rows_repeated(maximalRowCount - 1 - rowCount);
+        row.add_table_table_cell();
     }
-
-    xmlWriter->endElement();  // table:table
     ++defaultColumnStyleIndex;
 }
 
@@ -814,99 +786,73 @@ void ExcelImport::Private::processSheetForHeaderFooter(Sheet* \
sheet, KoXmlWriter  if (!sheet) return;
     if (!xmlWriter) return;
 
-    xmlWriter->startElement("style:header");
+    style_header header(xmlWriter);
     if (!sheet->leftHeader().isEmpty()) {
-        xmlWriter->startElement("style:region-left");
-        xmlWriter->startElement("text:p");
-        processHeaderFooterStyle(sheet->leftHeader(), xmlWriter);
-        xmlWriter->endElement();
-        xmlWriter->endElement();
+        style_region_left left(header.add_style_region_left());
+        text_p p(left.add_text_p());
+        processHeaderFooterStyle(sheet->leftHeader(), p);
     }
     if (!sheet->centerHeader().isEmpty()) {
-        xmlWriter->startElement("style:region-center");
-        xmlWriter->startElement("text:p");
-        processHeaderFooterStyle(sheet->centerHeader(), xmlWriter);
-        xmlWriter->endElement();
-        xmlWriter->endElement();
+        style_region_center center(header.add_style_region_center());
+        text_p p(center.add_text_p());
+        processHeaderFooterStyle(sheet->centerHeader(), p);
     }
     if (!sheet->rightHeader().isEmpty()) {
-        xmlWriter->startElement("style:region-right");
-        xmlWriter->startElement("text:p");
-        processHeaderFooterStyle(sheet->rightHeader(), xmlWriter);
-        xmlWriter->endElement();
-        xmlWriter->endElement();
+        style_region_right right(header.add_style_region_right());
+        text_p p(right.add_text_p());
+        processHeaderFooterStyle(sheet->rightHeader(), p);
     }
-    xmlWriter->endElement();
+    header.end();
 
-    xmlWriter->startElement("style:footer");
+    style_footer footer(xmlWriter);
     if (!sheet->leftFooter().isEmpty()) {
-        xmlWriter->startElement("style:region-left");
-        xmlWriter->startElement("text:p");
-        processHeaderFooterStyle(sheet->leftFooter(), xmlWriter);
-        xmlWriter->endElement();
-        xmlWriter->endElement();
+        style_region_left left(footer.add_style_region_left());
+        text_p p(left.add_text_p());
+        processHeaderFooterStyle(sheet->leftFooter(), p);
     }
     if (!sheet->centerFooter().isEmpty()) {
-        xmlWriter->startElement("style:region-center");
-        xmlWriter->startElement("text:p");
-        processHeaderFooterStyle(sheet->centerFooter(), xmlWriter);
-        xmlWriter->endElement();
-        xmlWriter->endElement();
+        style_region_center center(footer.add_style_region_center());
+        text_p p(center.add_text_p());
+        processHeaderFooterStyle(sheet->centerFooter(), p);
     }
     if (!sheet->rightFooter().isEmpty()) {
-        xmlWriter->startElement("style:region-right");
-        xmlWriter->startElement("text:p");
-        processHeaderFooterStyle(sheet->rightFooter(), xmlWriter);
-        xmlWriter->endElement();
-        xmlWriter->endElement();
+        style_region_right right(footer.add_style_region_right());
+        text_p p(right.add_text_p());
+        processHeaderFooterStyle(sheet->rightFooter(), p);
     }
-    xmlWriter->endElement();
 }
 
 // Processes the styles of a headers and footers for a sheet.
-void ExcelImport::Private::processHeaderFooterStyle(const QString& text, \
KoXmlWriter* xmlWriter) +void ExcelImport::Private::processHeaderFooterStyle(const \
QString& text, text_p& p)  {
-    QString content;
     bool skipUnsupported = false;
     int lastPos;
     int pos = text.indexOf('&');
     int len = text.length();
     if ((pos < 0) && (text.length() > 0))   // If ther is no &
-        xmlWriter->addTextNode(text);
+        p.addTextNode(text);
     else if (pos > 0) // Some text and '&'
-        xmlWriter->addTextNode(text.mid(0,  pos - 1));
+        p.addTextNode(text.mid(0,  pos - 1));
 
     while (pos >= 0) {
         switch (text[pos + 1].unicode()) {
         case 'D':
-            xmlWriter->startElement("text:date");
-            xmlWriter->addTextNode(QDate::currentDate().toString("DD/MM/YYYY"));
-            xmlWriter->endElement();
+            p.add_text_date().addTextNode(QDate::currentDate().toString("DD/MM/YYYY"));
  break;
         case 'T':
-            xmlWriter->startElement("text:time");
-            xmlWriter->addTextNode(QTime::currentTime().toString("HH:MM:SS"));
-            xmlWriter->endElement();
+            p.add_text_time().addTextNode(QTime::currentTime().toString("HH:MM:SS"));
  break;
         case 'P':
-            xmlWriter->startElement("text:page-number");
-            xmlWriter->addTextNode("1");
-            xmlWriter->endElement();
+            p.add_text_page_number().addTextNode("1");
             break;
         case 'N':
-            xmlWriter->startElement("text:page-count");
-            xmlWriter->addTextNode("999");
-            xmlWriter->endElement();
+            p.add_text_page_count().addTextNode("999");
             break;
         case 'F':
-            xmlWriter->startElement("text:title");
-            xmlWriter->addTextNode("???");
-            xmlWriter->endElement();
+            p.add_text_title().addTextNode("???");
             break;
         case 'A':
-            xmlWriter->startElement("text:sheet-name");
-            xmlWriter->addTextNode("???");
-            xmlWriter->endElement();
+            p.add_text_sheet_name().addTextNode("???");
             break;
         case '\"':
         default:
@@ -916,38 +862,34 @@ void ExcelImport::Private::processHeaderFooterStyle(const \
QString& text, KoXmlWr  lastPos = pos;
         pos = text.indexOf('&', lastPos + 1);
         if (!skipUnsupported && (pos > (lastPos + 1)))
-            xmlWriter->addTextNode(text.mid(lastPos + 2, (pos - lastPos - 2)));
+            p.addTextNode(text.mid(lastPos + 2, (pos - lastPos - 2)));
         else if (!skipUnsupported && (pos < 0))  //Remaining text
-            xmlWriter->addTextNode(text.mid(lastPos + 2, len - (lastPos + 2)));
+            p.addTextNode(text.mid(lastPos + 2, len - (lastPos + 2)));
         else
             skipUnsupported = false;
     }
 }
 
 // Processes a column in a sheet.
-void ExcelImport::Private::processColumnForBody(Sheet* sheet, int columnIndex, \
KoXmlWriter* xmlWriter, unsigned& outlineLevel) +void \
ExcelImport::Private::processColumnForBody(Sheet* sheet, int columnIndex, \
group_table_columns_and_groups& table, unsigned& outlineLevel)  {
     Column* column = sheet->column(columnIndex, false);
 
-    if (!xmlWriter) return;
-
     unsigned newOutlineLevel = column ? column->outlineLevel() : 0;
-    while (newOutlineLevel > outlineLevel) {
-        xmlWriter->startElement("table:table-column-group");
+    if (newOutlineLevel > outlineLevel) {
+        table_table_column_group group(table.add_table_table_column_group());
         outlineLevel++;
         if (outlineLevel == newOutlineLevel && column->collapsed())
-            xmlWriter->addAttribute("table:display", "false");
-    }
-    while (newOutlineLevel < outlineLevel) {
-        xmlWriter->endElement(); // table:table-column-group
+            group.set_table_display("false");
+        processColumnForBody(sheet, columnIndex, group, outlineLevel);
         outlineLevel--;
+        return;
     }
 
     if (!column) {
-        xmlWriter->startElement("table:table-column");
+        table_table_column column(table.add_table_table_column());
         Q_ASSERT(defaultColumnStyleIndex < defaultColumnStyles.count());
-        xmlWriter->addAttribute("table:style-name", \
                defaultColumnStyles[defaultColumnStyleIndex] );
-        xmlWriter->endElement();
+        column.set_table_style_name(defaultColumnStyles[defaultColumnStyleIndex] );
         return;
     }
     Q_ASSERT(columnFormatIndex < colStyles.count());
@@ -956,12 +898,11 @@ void ExcelImport::Private::processColumnForBody(Sheet* sheet, \
int columnIndex, K  const QString defaultStyleName = \
colCellStyles[columnFormatIndex];  columnFormatIndex++;
 
-    xmlWriter->startElement("table:table-column");
-    xmlWriter->addAttribute("table:default-cell-style-name", defaultStyleName);
-    xmlWriter->addAttribute("table:visibility", column->visible() ? "visible" : \
                "collapse");
-    //xmlWriter->addAttribute("table:number-columns-repeated", );
-    xmlWriter->addAttribute("table:style-name", styleName);
-    xmlWriter->endElement();  // table:table-column
+    table_table_column c(table.add_table_table_column());
+    c.set_table_default_cell_style_name(defaultStyleName);
+    c.set_table_visibility(column->visible() ? "visible" : "collapse");
+    //c.set_table_number_columns_repeated( );
+    c.set_table_style_name(styleName);
 }
 
 // Processes the style of a column in a sheet.
@@ -985,31 +926,26 @@ void ExcelImport::Private::processColumnForStyle(Sheet* sheet, \
int columnIndex,  }
 
 // Processes a row in a sheet.
-int ExcelImport::Private::processRowForBody(KoOdfWriteStore* store, Sheet* sheet, \
int rowIndex, KoXmlWriter* xmlWriter, unsigned& outlineLevel) +int \
ExcelImport::Private::processRowForBody(Sheet* sheet, int rowIndex, KoXmlWriter* \
xmlWriter, group_table_rows_and_groups& table, unsigned& outlineLevel)  {
     int repeat = 1;
 
-    if (!xmlWriter) return repeat;
     Row *row = sheet->row(rowIndex, false);
 
     unsigned newOutlineLevel = row ? row->outlineLevel() : 0;
-    while (newOutlineLevel > outlineLevel) {
-        xmlWriter->startElement("table:table-row-group");
+    if (newOutlineLevel > outlineLevel) {
+        table_table_row_group group(table.add_table_table_row_group());
         outlineLevel++;
         if (outlineLevel == newOutlineLevel && row->collapsed())
-            xmlWriter->addAttribute("table:display", "false");
-    }
-    while (newOutlineLevel < outlineLevel) {
-        xmlWriter->endElement(); // table:table-row-group
+            group.set_table_display("false");
+        processRowForBody(sheet, rowIndex, xmlWriter, group, outlineLevel);
         outlineLevel--;
+        return repeat;
     }
 
-
     if (!row) {
-        xmlWriter->startElement("table:table-row");
-        xmlWriter->startElement("table:table-cell");
-        xmlWriter->endElement();
-        xmlWriter->endElement();
+        table_table_row row(table.add_table_table_row());
+        row.add_table_table_cell();
         return repeat;
     }
     if (!row->sheet()) return repeat;
@@ -1019,12 +955,12 @@ int ExcelImport::Private::processRowForBody(KoOdfWriteStore* \
store, Sheet* sheet  
     repeat = rowsRepeated(row, rowIndex);
 
-    xmlWriter->startElement("table:table-row");
-    xmlWriter->addAttribute("table:visibility", row->visible() ? "visible" : \
                "collapse");
-    xmlWriter->addAttribute("table:style-name", styleName);
+    table_table_row r(table.add_table_table_row());
+    r.set_table_visibility(row->visible() ? "visible" : "collapse");
+    r.set_table_style_name(styleName);
 
     if(repeat > 1)
-        xmlWriter->addAttribute("table:number-rows-repeated", repeat);
+        r.set_table_number_rows_repeated(repeat);
 
     // find the column of the rightmost cell (if any)
     const int lastCol = row->sheet()->maxCellsInRow(rowIndex);
@@ -1032,16 +968,14 @@ int ExcelImport::Private::processRowForBody(KoOdfWriteStore* \
store, Sheet* sheet  do {
         Cell* cell = row->sheet()->cell(i, row->index(), false);
         if (cell) {
-            processCellForBody(store, cell, repeat, xmlWriter);
+            processCellForBody(cell, xmlWriter, r);
             i += cell->columnRepeat();
         } else { // empty cell
-            xmlWriter->startElement("table:table-cell");
-            xmlWriter->endElement();
+            r.add_table_table_cell();
             ++i;
         }
     } while(i <= lastCol);
 
-    xmlWriter->endElement();  // table:table-row
     addProgress(repeat);
     return repeat;
 }
@@ -1234,151 +1168,158 @@ QString currencyValue(const QString &value)
 }
 
 // Processes a cell within a sheet.
-void ExcelImport::Private::processCellForBody(KoOdfWriteStore* store, Cell* cell, \
int rowsRepeat, KoXmlWriter* xmlWriter) +void \
ExcelImport::Private::processCellForBody(Cell* cell, KoXmlWriter* xmlWriter, \
table_table_row& row)  {
-        Q_UNUSED(store);
-        Q_UNUSED(rowsRepeat);
-
-    if (!cell) return;
-    if (!xmlWriter) return;
-
-    if (cell->isCovered())
-        xmlWriter->startElement("table:covered-table-cell");
-    else
-        xmlWriter->startElement("table:table-cell");
+    CellValue cellValue;
+    if (cell->isCovered()) {
+        table_covered_table_cell c(row.add_table_covered_table_cell());
+        processCellAttributesForBody(cell, c, cellValue);
+        processCellContentForBody(cell, xmlWriter, c, cellValue);
+    } else {
+        table_table_cell c(row.add_table_table_cell());
+        if (cell->columnSpan() > 1)
+            c.set_table_number_columns_spanned(cell->columnSpan());
+        if (cell->rowSpan() > 1)
+            c.set_table_number_rows_spanned(cell->rowSpan());
+        processCellAttributesForBody(cell, c, cellValue);
+        processCellContentForBody(cell, xmlWriter, c, cellValue);
+    }
+}
 
+void ExcelImport::Private::processCellAttributesForBody(Cell* cell, \
group_table_table_cell_attlist& c, CellValue& cellValue) +{
     Q_ASSERT(cellFormatIndex >= 0 && cellFormatIndex < cellStyles.count());
-    xmlWriter->addAttribute("table:style-name", cellStyles[cellFormatIndex]);
+    c.set_table_style_name(cellStyles[cellFormatIndex]);
     cellFormatIndex++;
 
-    if (cell->columnSpan() > 1)
-        xmlWriter->addAttribute("table:number-columns-spanned", cell->columnSpan());
-    if (cell->rowSpan() > 1)
-        xmlWriter->addAttribute("table:number-rows-spanned", cell->rowSpan());
     if (cell->columnRepeat() > 1)
-        xmlWriter->addAttribute("table:number-columns-repeated", \
cell->columnRepeat()); +        \
c.set_table_number_columns_repeated(cell->columnRepeat());  
     const QString formula = cellFormula(cell);
     if (!formula.isEmpty())
-        xmlWriter->addAttribute("table:formula", formula);
+        c.set_table_formula(formula);
 
-    Value value = cell->value();
+    cellValue.value = cell->value();
+    const Value& value = cellValue.value;
 
     if (value.isBoolean()) {
-        xmlWriter->addAttribute("office:value-type", "boolean");
-        xmlWriter->addAttribute("office:boolean-value", value.asBoolean() ? "true" : \
"false"); +        c.set_office_value_type("boolean");
+        c.set_office_boolean_value(value.asBoolean() ? "true" : "false");
     } else if (value.isFloat() || value.isInteger()) {
         const QString valueFormat = cell->format().valueFormat();
 
         if (isPercentageFormat(valueFormat)) {
-            xmlWriter->addAttribute("office:value-type", "percentage");
-            xmlWriter->addAttribute("office:value", value.asFloat());
+            c.set_office_value_type("percentage");
+            c.set_office_value(value.asFloat());
         } else if (isDateFormat(valueFormat)) {
             const QString dateValue = convertDate(value.asFloat(), valueFormat);
-            xmlWriter->addAttribute("office:value-type", "date");
-            xmlWriter->addAttribute("office:date-value", dateValue);
+            c.set_office_value_type("date");
+            c.set_office_date_value(dateValue);
         } else if (value.asFloat() < 1.0 && isTimeFormat(valueFormat)) {
             const QString timeValue = convertTime(value.asFloat(), valueFormat);
-            xmlWriter->addAttribute("office:value-type", "time");
-            xmlWriter->addAttribute("office:time-value", timeValue);
+            c.set_office_value_type("time");
+            c.set_office_time_value(timeValue);
         } else if (isFractionFormat(valueFormat)) {
             const QString fractionValue = convertFraction(value.asFloat(), \
                valueFormat);
-            xmlWriter->addAttribute("office:value-type", "float");
-            xmlWriter->addAttribute("office:value", fractionValue);
+            c.set_office_value_type("float");
+            c.set_office_value(fractionValue);
         } else { // fallback is the generic float format
-            xmlWriter->addAttribute("office:value-type", "float");
-            xmlWriter->addAttribute("office:value", value.asFloat());
+            c.set_office_value_type("float");
+            c.set_office_value(value.asFloat());
         }
     } else if (value.isText() || value.isError()) {
-        QString str = value.asString();
-        QString linkName, linkLocation;
-
-        Hyperlink link = cell->hyperlink();
-        if (link.isValid) {
-            linkLocation = link.location;
-            if(!linkLocation.isEmpty()) {
-                linkName = link.displayName.trimmed();
-                if(linkName.isEmpty())
-                    linkName = str;
-                str.clear(); // at Excel cells with links don't have additional text \
content +        cellValue.str = value.asString();
+
+        cellValue.link = cell->hyperlink();
+        if (cellValue.link.isValid) {
+            cellValue.linkLocation = cellValue.link.location;
+            if(!cellValue.linkLocation.isEmpty()) {
+                cellValue.linkName = cellValue.link.displayName.trimmed();
+                if(cellValue.linkName.isEmpty())
+                    cellValue.linkName = cellValue.str;
+                cellValue.str.clear(); // at Excel cells with links don't have \
additional text content  }
         }
-        if (linkLocation.isEmpty() && value.isString()) {
-            xmlWriter->addAttribute("office:value-type", "string");
-             if (!(cell->format().font().subscript() || \
                cell->format().font().superscript()))
-                 xmlWriter->addAttribute("office:string-value", str);
+        if (cellValue.linkLocation.isEmpty() && value.isString()) {
+            c.set_office_value_type("string");
+            if (!(cell->format().font().subscript() || \
cell->format().font().superscript())) +                \
c.set_office_string_value(cellValue.str); +        }
+    }
+}
+
+void ExcelImport::Private::processCellText(Cell* cell, group_paragraph_content& \
content, CellValue& cellValue) +{
+    const QString& str = cellValue.str;
+    if (cellValue.value.isString()) {
+        content.addTextNode(str);
+    } else {
+        // rich text
+        std::map<unsigned, FormatFont> formatRuns = cellValue.value.formatRuns();
+
+        // add sentinel to list of format runs
+        formatRuns[str.length()] = cell->format().font();
+
+        unsigned index = 0;
+        QString style;
+        for (std::map<unsigned, FormatFont>::iterator it = formatRuns.begin(); it != \
formatRuns.end(); ++it) { +            if (it->first > index) {
+                if (!style.isEmpty()) {
+                    text_span span(content.add_text_span());
+                    span.set_text_style_name(style);
+                    span.addTextNode(str.mid(index, it->first - index));
+                } else {
+                    content.addTextNode(str.mid(index, it->first - index));
+                }
+            }
+
+            index = it->first;
+
+            if (it->second == cell->format().font())
+                style.clear();
+            else {
+                style = fontStyles.value(it->second);
+            }
         }
+    }
+}
 
-        xmlWriter->startElement("text:p", false);
+void ExcelImport::Private::processCellContentForBody(Cell* cell,
+        KoXmlWriter* xmlWriter, group_table_table_cell_content& c,
+        CellValue& cellValue)
+{
+    if (cellValue.value.isText() || cellValue.value.isError()) {
+        text_p p(c.add_text_p());
 
-        if(!str.isEmpty()) {
+        if(!cellValue.str.isEmpty()) {
             if (cell->format().font().subscript() || \
                cell->format().font().superscript()) {
-                xmlWriter->startElement("text:span");
+                text_span span(p.add_text_span());
                 if (cell->format().font().subscript())
-                    xmlWriter->addAttribute("text:style-name", subScriptStyle);
+                    span.set_text_style_name(subScriptStyle);
                 else
-                    xmlWriter->addAttribute("text:style-name", superScriptStyle);
-            }
-
-            if (value.isString()) {
-                xmlWriter->addTextNode(str);
+                    span.set_text_style_name(superScriptStyle);
+                processCellText(cell, span, cellValue);
             } else {
-                // rich text
-                std::map<unsigned, FormatFont> formatRuns = value.formatRuns();
-
-                // add sentinel to list of format runs
-                formatRuns[str.length()] = cell->format().font();
-
-                unsigned index = 0;
-                QString style;
-                for (std::map<unsigned, FormatFont>::iterator it = \
                formatRuns.begin(); it != formatRuns.end(); ++it) {
-                    if (!style.isEmpty() && it->first > index) {
-                        xmlWriter->startElement("text:span");
-                        xmlWriter->addAttribute("text:style-name", style);
-                    }
-                    if (it->first > index)
-                        xmlWriter->addTextNode(str.mid(index, it->first - index));
-                    if (!style.isEmpty() && it->first > index) {
-                        xmlWriter->endElement(); // text:span
-                    }
-
-                    index = it->first;
-
-                    if (it->second == cell->format().font())
-                        style.clear();
-                    else {
-                        style = fontStyles.value(it->second);
-                    }
-                }
+                processCellText(cell, p, cellValue);
             }
-
-            if (cell->format().font().subscript() || \
                cell->format().font().superscript())
-                xmlWriter->endElement(); // text:span
         }
 
-        if (!linkName.isEmpty()) {
-            xmlWriter->startElement("text:a");
-            xmlWriter->addAttribute("xlink:href", linkLocation);
-            const QString targetFrameName = link.targetFrameName;
+        if (!cellValue.linkName.isEmpty()) {
+            text_a a(p.add_text_a(cellValue.linkLocation));
+            const QString targetFrameName = cellValue.link.targetFrameName;
             if (! targetFrameName.isEmpty())
-                xmlWriter->addAttribute("office:target-frame-name", \
                targetFrameName);
-            xmlWriter->addTextNode(linkName);
-            xmlWriter->endElement(); // text:a
+                a.set_office_target_frame_name(targetFrameName);
+            a.addTextNode(cellValue.linkName);
         }
-
-        xmlWriter->endElement(); //  text:p
     }
 
     const QString note = cell->note();
     if (! note.isEmpty()) {
-        xmlWriter->startElement("office:annotation");
-        //xmlWriter->startElement("dc:creator");
-        //xmlWriter->addTextNode(authorName); //TODO
-        //xmlWriter->endElement(); // dc:creator
-        xmlWriter->startElement("text:p");
-        xmlWriter->addTextNode(note);
-        xmlWriter->endElement(); // text:p
-        xmlWriter->endElement(); // office:annotation
+        office_annotation annotation(c.add_office_annotation());
+        //dc_creator creator(annotation.add_dc_creator());
+        //creator.addTextNode(authorName); //TODO
+        text_p p(annotation.add_text_p());
+        p.addTextNode(note);
     }
 
 
@@ -1420,9 +1361,6 @@ void ExcelImport::Private::processCellForBody(KoOdfWriteStore* \
store, Cell* cell  if (!cell->drawObjects().isEmpty()) {
         xmlWriter->addCompleteElement(cellShapes[cell].data());
     }
-
-
-    xmlWriter->endElement(); // table:[covered-]table-cell
 }
 
 void ExcelImport::Private::processCharts(KoXmlWriter* manifestWriter)
@@ -1498,9 +1436,9 @@ QString ExcelImport::Private::processCellFormat(const Format* \
format, const QStr  QBuffer buffer;
                 buffer.open(QIODevice::WriteOnly);
                 KoXmlWriter xmlWriter(&buffer);    // TODO pass indentation level
-                xmlWriter.startElement("number:number");
-                xmlWriter.addAttribute("number:decimal-places", key.decimalCount);
-                xmlWriter.endElement(); // number:number
+                number_number number(&xmlWriter);
+                number.set_number_decimal_places(key.decimalCount);
+                number.end();
                 QString elementContents = QString::fromUtf8(buffer.buffer(), \
buffer.buffer().size());  style.addChildElement("number", elementContents);
                 refName = styles->insert(style, "N");
@@ -1824,12 +1762,12 @@ void ExcelImport::Private::processSheetBackground(Sheet* \
sheet, KoGenStyle& styl  KoXmlWriter writer(&buffer);
 
     //TODO add the manifest entry
-    writer.startElement("style:background-image");
-    writer.addAttribute("xlink:href", sheet->backgroundImage());
-    writer.addAttribute("xlink:type", "simple");
-    writer.addAttribute("xlink:show", "embed");
-    writer.addAttribute("xlink:actuate", "onLoad");
-    writer.endElement();
+    style_background_image bg(&writer);
+    bg.set_xlink_href(sheet->backgroundImage());
+    bg.set_xlink_type("simple");
+    bg.set_xlink_show("embed");
+    bg.set_xlink_actuate("onLoad");
+    bg.end();
 
     buffer.close();
     style.addChildElement("style:background-image", \
                QString::fromUtf8(buffer.buffer(), buffer.buffer().size()));
diff --git a/filters/stage/powerpoint/PptToOdp.cpp \
b/filters/stage/powerpoint/PptToOdp.cpp index 425ac33..0535491 100644
--- a/filters/stage/powerpoint/PptToOdp.cpp
+++ b/filters/stage/powerpoint/PptToOdp.cpp
@@ -18,7 +18,7 @@
    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- * Boston, MA 02110-1301, USA.
+   Boston, MA 02110-1301, USA.
 */
 
 #include "PptToOdp.h"
@@ -35,6 +35,12 @@
 #include <KoOdf.h>
 #include <KoOdfWriteStore.h>
 #include <KoXmlWriter.h>
+#include <writeodf/writeodftext.h>
+#include <writeodf/writeodfstyle.h>
+#include <writeodf/writeodfpresentation.h>
+#include <writeodf/writeodfofficemeta.h>
+#include <writeodf/writeodfofficedc.h>
+#include <writeodf/writeodfdraw.h>
 
 #include <QTime>
 #include <QBuffer>
@@ -47,6 +53,36 @@
 #define FONTSIZE_MAX 4000 //according to MS-PPT
 
 using namespace MSO;
+using namespace writeodf;
+
+/**
+ * This class represents an opened <text:list> tag with an optionally opened
+ * <text:list-item> tag.
+ * Usually, writeodf::text_list instances are on the stack, but in this
+ * class they are on heap. The TextListTag manages the allocation and
+ * deallocation of these instances.
+ */
+class PptToOdp::TextListTag
+{
+public:
+    QString style;
+    QSharedPointer<text_list> list;
+    QSharedPointer<text_list_item> item;
+    TextListTag() {}
+    TextListTag(const QString& style_, KoXmlWriter& out) :style(style_),
+        list(new text_list(&out))
+    {
+    }
+    TextListTag(const QString& style_, text_list_item& item) :style(style_),
+        list(new text_list(item.add_text_list()))
+    {
+    }
+    text_list_item& add_text_list_item()
+    {
+        item = QSharedPointer<text_list_item>(new \
text_list_item(list->add_text_list_item())); +        return *item;
+    }
+};
 
 namespace
 {
@@ -1641,6 +1677,95 @@ QString bulletSizeToSizeString(qint16 value)
     return ret;
 }
 } //namespace
+void PptToOdp::defineListStyleProperties(KoXmlWriter& out, bool imageBullet, const \
QString& bulletSize, +                               const PptTextPFRun& pf) {
+    style_list_level_properties list_level_properties(&out);
+
+    if (imageBullet) {
+        QString pictureSize = bulletSize;
+        if (pictureSize.endsWith(QLatin1Char('%'))) {
+            pictureSize.chop(1);
+            bool ok = false;
+            qreal size = pictureSize.toDouble(&ok);
+            if (!ok) {
+                qDebug() << "defineBulletStyle: error converting" << pictureSize << \
"to double"; +            }
+            size = m_firstChunkFontSize * size / 100.0;
+            pictureSize = pt(size);
+        }
+
+        // fo:text-align
+        // fo:height
+        list_level_properties.set_fo_height(pictureSize);
+        // fo:width
+        list_level_properties.set_fo_width(pictureSize);
+        // style:font-name
+        // style:vertical-pos
+        list_level_properties.set_style_vertical_pos("middle");
+        // style:vertical-rel
+        list_level_properties.set_style_vertical_pos("line");
+        // svg:x
+        // svg:y
+    }
+    quint16 indent = pf.indent();
+    // text:min-label-distance
+    // text:min-label-width
+    list_level_properties.set_text_min_label_width(pptMasterUnitToCm(pf.leftMargin() \
- indent)); +    // text:space-before
+    list_level_properties.set_text_space_before(pptMasterUnitToCm(indent));
+}
+
+void PptToOdp::defineListStyleTextProperties(KoXmlWriter& out, const QString& \
bulletSize, +                                             const PptTextPFRun& pf) {
+
+    //---------------------------------------------
+    // text-properties
+    //---------------------------------------------
+
+    KoGenStyle ts(KoGenStyle::TextStyle);
+    const KoGenStyle::PropertyType text = KoGenStyle::TextType;
+
+    //bulletSize already processed
+    ts.addProperty("fo:font-size", bulletSize, text);
+
+    //default value doesn't make sense
+    QColor color;
+    if (pf.fBulletHasColor()) {
+        color = toQColor(pf.bulletColor());
+        if (color.isValid()) {
+            ts.addProperty("fo:color", color.name(), text);
+        }
+    }
+
+    const MSO::FontEntityAtom* font = 0;
+
+    //MSPowerPoint: UI does NOT enable to change font of a
+    //numbered lists label.
+    if (pf.fBulletHasFont() && !pf.fBulletHasAutoNumber()) {
+        font = getFont(pf.bulletFontRef());
+    }
+
+    //A list label should NOT inherit a symbol font.
+    if (!font && m_firstChunkSymbolAtStart) {
+        font = getFont(m_firstChunkFontRef);
+    }
+
+    if (font) {
+        QString family = QString::fromUtf16(font->lfFaceName.data(), \
font->lfFaceName.size()); +        ts.addProperty("fo:font-family", family, text);
+    }
+
+    //MSPowerPoint: A label does NOT inherit Underline from
+    //text-properties of the 1st text chunk.  A bullet does NOT
+    //inherit properties in {Italics, Bold}.
+    if (!pf.fBulletHasAutoNumber()) {
+        ts.addProperty("fo:font-style", "normal");
+        ts.addProperty("fo:font-weight", "normal");
+    }
+    ts.addProperty("style:text-underline-style", "none");
+
+    ts.writeStyleProperties(&out, text);
+}
 
 void PptToOdp::defineListStyle(KoGenStyle& style, const quint16 depth,
                                const ListStyleInput& i)
@@ -1662,9 +1787,10 @@ void PptToOdp::defineListStyle(KoGenStyle& style, const \
quint16 depth,  
     if (imageBullet) {
         elementName = "text:list-level-style-image";
-        out.startElement("text:list-level-style-image");
-        out.addAttribute("xlink:href", \
                bulletPictureNames.value(i.pf.bulletBlipRef()));
-        out.addAttribute("xlink:type", "simple");
+        text_list_level_style_image image(&out, depth + 1);
+        image.set_xlink_href(bulletPictureNames.value(i.pf.bulletBlipRef()));
+        image.set_xlink_type("simple");
+        defineListStyleProperties(out, imageBullet, bulletSize, i.pf);
     }
     else if (i.pf.fBulletHasAutoNumber() || i.pf.fHasBullet()) {
 
@@ -1675,119 +1801,38 @@ void PptToOdp::defineListStyle(KoGenStyle& style, const \
quint16 depth,  // we assume it's a numbered list
         if (i.pf.fBulletHasAutoNumber() || i.pf.bulletChar() == 0) {
             elementName = "text:list-level-style-number";
-            out.startElement("text:list-level-style-number");
+            text_list_level_style_number number(&out, depth + 1);
             if (!numFormat.isNull()) {
-                out.addAttribute("style:num-format", numFormat);
+                number.set_style_num_format(numFormat);
             }
             // style:display-levels
-            out.addAttribute("text:start-value", i.pf.startNum());
+            number.set_text_start_value(i.pf.startNum());
 
             if (!numPrefix.isNull()) {
-                out.addAttribute("style:num-prefix", numPrefix);
+                number.set_style_num_prefix(numPrefix);
             }
             if (!numSuffix.isNull()) {
-                out.addAttribute("style:num-suffix", numSuffix);
+                number.set_style_num_suffix(numSuffix);
             }
+            defineListStyleProperties(out, imageBullet, bulletSize, i.pf);
+            defineListStyleTextProperties(out, bulletSize, i.pf);
         } else {
             elementName = "text:list-level-style-bullet";
-            out.startElement("text:list-level-style-bullet");
-            out.addAttribute("text:bullet-char", getBulletChar(i.pf));
+            text_list_level_style_bullet bullet(&out, getBulletChar(i.pf), depth + \
1); +            defineListStyleProperties(out, imageBullet, bulletSize, i.pf);
+            defineListStyleTextProperties(out, bulletSize, i.pf);
             // text:bullet-relative-size
         }
     }
     //no bullet exists (i.pf.fHasBullet() == false)
     else {
         elementName = "text:list-level-style-number";
-        out.startElement("text:list-level-style-number");
-        out.addAttribute("style:num-format", "");
+        text_list_level_style_number number(&out, depth + 1);
+        number.set_style_num_format("");
+        defineListStyleProperties(out, imageBullet, bulletSize, i.pf);
+        defineListStyleTextProperties(out, bulletSize, i.pf);
     }
-    out.addAttribute("text:level", depth + 1);
-    out.startElement("style:list-level-properties");
-
-    if (imageBullet) {
-        QString pictureSize = bulletSize;
-        if (pictureSize.endsWith(QLatin1Char('%'))) {
-            pictureSize.chop(1);
-            bool ok = false;
-            qreal size = pictureSize.toDouble(&ok);
-            if (!ok) {
-                qDebug() << "defineBulletStyle: error converting" << pictureSize << \
                "to double";
-            }
-            size = m_firstChunkFontSize * size / 100.0;
-            pictureSize = pt(size);
-        }
 
-        // fo:text-align
-        // fo:height
-        out.addAttribute("fo:height", pictureSize);
-        // fo:width
-        out.addAttribute("fo:width", pictureSize);
-        // style:font-name
-        // style:vertical-pos
-        out.addAttribute("style:vertical-pos", "middle");
-        // style:vertical-rel
-        out.addAttribute("style:vertical-rel", "line");
-        // svg:x
-        // svg:y
-    }
-    quint16 indent = i.pf.indent();
-    // text:min-label-distance
-    // text:min-label-width
-    out.addAttribute("text:min-label-width", pptMasterUnitToCm(i.pf.leftMargin() - \
                indent));
-    // text:space-before
-    out.addAttribute("text:space-before", pptMasterUnitToCm(indent));
-    out.endElement(); // style:list-level-properties
-
-    //---------------------------------------------
-    // text-properties
-    //---------------------------------------------
-
-    if (!imageBullet) {
-        KoGenStyle ts(KoGenStyle::TextStyle);
-        const KoGenStyle::PropertyType text = KoGenStyle::TextType;
-
-        //bulletSize already processed
-        ts.addProperty("fo:font-size", bulletSize, text);
-
-        //default value doesn't make sense
-        QColor color;
-        if (i.pf.fBulletHasColor()) {
-            color = toQColor(i.pf.bulletColor());
-            if (color.isValid()) {
-                ts.addProperty("fo:color", color.name(), text);
-            }
-        }
-
-        const MSO::FontEntityAtom* font = 0;
-
-        //MSPowerPoint: UI does NOT enable to change font of a
-        //numbered lists label.
-        if (i.pf.fBulletHasFont() && !i.pf.fBulletHasAutoNumber()) {
-            font = getFont(i.pf.bulletFontRef());
-        }
-
-        //A list label should NOT inherit a symbol font.
-        if (!font && m_firstChunkSymbolAtStart) {
-            font = getFont(m_firstChunkFontRef);
-        }
-
-        if (font) {
-            QString family = QString::fromUtf16(font->lfFaceName.data(), \
                font->lfFaceName.size());
-            ts.addProperty("fo:font-family", family, text);
-        }
-
-        //MSPowerPoint: A label does NOT inherit Underline from
-        //text-properties of the 1st text chunk.  A bullet does NOT
-        //inherit properties in {Italics, Bold}.
-        if (!i.pf.fBulletHasAutoNumber()) {
-            ts.addProperty("fo:font-style", "normal");
-            ts.addProperty("fo:font-weight", "normal");
-        }
-        ts.addProperty("style:text-underline-style", "none");
-
-        ts.writeStyleProperties(&out, text);
-    }
-    out.endElement();  // text:list-level-style-*
     // serialize the text:list-style element into the properties
     QString contents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
     style.addChildElement(elementName, contents);
@@ -2134,10 +2179,9 @@ void PptToOdp::createMainStyles(KoGenStyles& styles)
         KoXmlWriter writer(&notesBuffer);
         Writer out(writer, styles, true);
 
-        writer.startElement("presentation:notes");
-        writer.addAttribute("style:page-layout-name", notesPageLayoutName);
-        writer.addAttribute("draw:style-name",
-                            drawingPageStyles[p->notesMaster]);
+        presentation_notes notes(&out.xml);
+        notes.set_style_page_layout_name(notesPageLayoutName);
+        notes.set_draw_style_name(drawingPageStyles[p->notesMaster]);
         m_currentMaster = 0;
 
         if (p->notesMaster->drawing.OfficeArtDg.groupShape) {
@@ -2145,7 +2189,6 @@ void PptToOdp::createMainStyles(KoGenStyles& styles)
             drawclient.setDrawClientData(0, 0, p->notesMaster, 0);
             odrawtoodf.processGroupShape(spgr, out);
         }
-        writer.endElement();
     }
     m_processingMasters = true;
 
@@ -2225,27 +2268,24 @@ QByteArray PptToOdp::createContent(KoGenStyles& styles)
     KoXmlWriter contentWriter(&contentBuffer);
 
     contentWriter.startDocument("office:document-content");
-    contentWriter.startElement("office:document-content");
-    contentWriter.addAttribute("xmlns:fo", \
                "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0");
-    contentWriter.addAttribute("xmlns:office", \
                "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
-    contentWriter.addAttribute("xmlns:style", \
                "urn:oasis:names:tc:opendocument:xmlns:style:1.0");
-    contentWriter.addAttribute("xmlns:text", \
                "urn:oasis:names:tc:opendocument:xmlns:text:1.0");
-    contentWriter.addAttribute("xmlns:draw", \
                "urn:oasis:names:tc:opendocument:xmlns:drawing:1.0");
-    contentWriter.addAttribute("xmlns:presentation", \
                "urn:oasis:names:tc:opendocument:xmlns:presentation:1.0");
-    contentWriter.addAttribute("xmlns:svg", \
                "urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0");
-    contentWriter.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
-    contentWriter.addAttribute("office:version", "1.2");
+    {
+    office_document_content content(&contentWriter);
+    content.addAttribute("xmlns:fo", \
"urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"); +    \
content.addAttribute("xmlns:office", \
"urn:oasis:names:tc:opendocument:xmlns:office:1.0"); +    \
content.addAttribute("xmlns:style", \
"urn:oasis:names:tc:opendocument:xmlns:style:1.0"); +    \
content.addAttribute("xmlns:text", "urn:oasis:names:tc:opendocument:xmlns:text:1.0"); \
+    content.addAttribute("xmlns:draw", \
"urn:oasis:names:tc:opendocument:xmlns:drawing:1.0"); +    \
content.addAttribute("xmlns:presentation", \
"urn:oasis:names:tc:opendocument:xmlns:presentation:1.0"); +    \
content.addAttribute("xmlns:svg", \
"urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0"); +    \
content.addAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink");  
     // office:automatic-styles
     styles.saveOdfStyles(KoGenStyles::DocumentAutomaticStyles, &contentWriter);
 
-    // office:body
-    contentWriter.startElement("office:body");
-    contentWriter.startElement("office:presentation");
-    contentWriter.addCompleteElement(&presentationBuffer);
-    contentWriter.endElement();  // office:presentation
-    contentWriter.endElement();  // office:body
-    contentWriter.endElement();  // office:document-content
+    office_body body(content.add_office_body());
+    office_presentation presentation(body.add_office_presentation());
+    presentation.addCompleteElement(&presentationBuffer);
+    }
     contentWriter.endDocument();
     return contentData;
 }
@@ -2258,52 +2298,44 @@ QByteArray PptToOdp::createMeta()
     KoXmlWriter metaWriter(&buff);
 
     metaWriter.startDocument("office:document-meta");
-    metaWriter.startElement("office:document-meta");
-    metaWriter.addAttribute("xmlns:office", \
                "urn:oasis:names:tc:opendocument:xmlns:office:1.0");
-    metaWriter.addAttribute("xmlns:meta", \
                "urn:oasis:names:tc:opendocument:xmlns:meta:1.0");
-    metaWriter.addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/");
-    metaWriter.addAttribute("office:version", "1.2");
-    metaWriter.startElement("office:meta");
-
-    const char *p_str = 0;
+    {
+    office_document_meta document_meta(&metaWriter);
+    document_meta.addAttribute("xmlns:office", \
"urn:oasis:names:tc:opendocument:xmlns:office:1.0"); +    \
document_meta.addAttribute("xmlns:meta", \
"urn:oasis:names:tc:opendocument:xmlns:meta:1.0"); +    \
document_meta.addAttribute("xmlns:dc", "http://purl.org/dc/elements/1.1/"); +    \
office_meta meta(document_meta.add_office_meta()); +
     const MSO::PropertySet &ps = p->summaryInfo.propertySet.propertySet1;
 
     for (uint i = 0; i < ps.numProperties; i++) {
-        switch (ps.propertyIdentifierAndOffset.at(i).propertyIdentifier) {
-        case PIDSI_TITLE:
-            p_str = "dc:title";
-            break;
-        case PIDSI_SUBJECT:
-            p_str = "dc:subject";
-            break;
-        case PIDSI_AUTHOR:
-            p_str = "meta:initial-creator";
-            break;
-        case PIDSI_KEYWORDS:
-            p_str = "meta:keyword";
-            break;
-        case PIDSI_COMMENTS:
-            p_str = "dc:description";
-            break;
-        case PIDSI_LASTAUTHOR:
-            p_str = "dc:creator";
-            break;
-        default:
-            break;
-        }
-        if (p_str) {
-            if (ps.property.at(i).vt_lpstr) {
-                metaWriter.startElement(p_str);
-                metaWriter.addTextNode(ps.property.at(i).vt_lpstr->characters);
-                metaWriter.endElement();
+        const QSharedPointer<CodePageString>& vt_lpstr = ps.property.at(i).vt_lpstr;
+        if (vt_lpstr) {
+            switch (ps.propertyIdentifierAndOffset.at(i).propertyIdentifier) {
+            case PIDSI_TITLE:
+                meta.add_dc_title().addTextNode(vt_lpstr->characters);
+                break;
+            case PIDSI_SUBJECT:
+                meta.add_dc_subject().addTextNode(vt_lpstr->characters);
+                break;
+            case PIDSI_AUTHOR:
+                meta.add_meta_initial_creator().addTextNode(vt_lpstr->characters);
+                break;
+            case PIDSI_KEYWORDS:
+                meta.add_meta_keyword().addTextNode(vt_lpstr->characters);
+                break;
+            case PIDSI_COMMENTS:
+                meta.add_dc_description().addTextNode(vt_lpstr->characters);
+                break;
+            case PIDSI_LASTAUTHOR:
+                meta.add_dc_creator().addTextNode(vt_lpstr->characters);
+                break;
+            default:
+                break;
             }
-            p_str = 0;
         }
     }
-
-    metaWriter.endElement();  // office:meta
-    metaWriter.endElement();  // office:document-meta
-
+    }
+    metaWriter.endDocument();
     return metaData;
 }
 
@@ -2362,7 +2394,7 @@ const TextPFRun *findTextPFRun(const StyleTextPropAtom& style, \
unsigned int pos)  }
 
 void
-writeMeta(const TextContainerMeta& m, bool master, KoXmlWriter& out)
+writeMeta(const TextContainerMeta& m, bool master, text_meta& meta)
 {
     const SlideNumberMCAtom* a = m.meta.get<SlideNumberMCAtom>();
     const DateTimeMCAtom* b = m.meta.get<DateTimeMCAtom>();
@@ -2370,37 +2402,30 @@ writeMeta(const TextContainerMeta& m, bool master, \
KoXmlWriter& out)  const HeaderMCAtom* d = m.meta.get<HeaderMCAtom>();
     const FooterMCAtom* e = m.meta.get<FooterMCAtom>();
     const RTFDateTimeMCAtom* f = m.meta.get<RTFDateTimeMCAtom>();
-    out.startElement("text:meta");
     if (a) {
-        out.startElement("text:page-number");
-        out.endElement();
+        meta.add_text_page_number();
     }
     if (b) {
         // TODO: datetime format
-        out.startElement("text:time");
-        out.endElement();
+        meta.add_text_time();
     }
     if (c) {
         // TODO: datetime format
         if (master) {
-            out.startElement("presentation:date-time");
+            meta.add_presentation_date_time();
         } else {
-            out.startElement("text:date");
+            meta.add_text_date();
         }
-        out.endElement();
     }
     if (d) {
-        out.startElement("presentation:header");
-        out.endElement();
+        meta.add_presentation_header();
     }
     if (e) {
-        out.startElement("presentation:footer");
-        out.endElement();
+        meta.add_presentation_footer();
     }
     if (f) {
         // TODO
     }
-    out.endElement();
 }
 
 template <class T>
@@ -2418,63 +2443,137 @@ int getMeta(const TextContainerMeta& m, const \
TextContainerMeta*& meta,  return end;
 }
 
-/**
-* @brief Write text deindentations the specified amount. Actually it just
-* closes elements.
-*
-* @param xmlWriter XML writer to write closing tags
-* @param count how many lists and list items to leave open
-* @param levels the list of levels to remove from
-*/
-void writeTextObjectDeIndent(KoXmlWriter& xmlWriter, const int count,
-                             QStack<QString>& levels)
-{
-    while (levels.size() > count) {
-        xmlWriter.endElement(); //text:list-item
-        xmlWriter.endElement(); //text:list
-        levels.pop();
-    }
-}
-
 void PptToOdp::addListElement(KoXmlWriter& out, const QString& listStyle,
-                    QStack<QString>& levels, quint16 level,
+                    ListStack& levels, quint16 level,
                     const PptTextPFRun &pf)
 {
-    levels.push(listStyle);
-    out.startElement("text:list");
+    levels.push(TextListTag(listStyle, out));
+    text_list& list = *levels.last().list;
     if (!listStyle.isEmpty()) {
-        out.addAttribute("text:style-name", listStyle);
+        list.set_text_style_name(listStyle);
     } else {
         qDebug() << "Warning: list style name not provided!";
     }
     if (pf.fBulletHasAutoNumber()) {
         QString xmlId = QString("lvl%1").arg(level);
         xmlId.append(QString("_%1").arg(qrand()));
-        out.addAttribute("xml:id", xmlId);
+        list.set_xml_id(xmlId);
 
         if (m_continueListNumbering.contains(level) &&
-            m_continueListNumbering[level]) {
-            out.addAttribute("text:continue-list", m_lvlXmlIdMap[level]);
+                m_continueListNumbering[level]) {
+            list.set_text_continue_list(m_lvlXmlIdMap[level]);
         }
         m_lvlXmlIdMap[level] = xmlId;
     }
-    out.startElement("text:list-item");
+
+    text_list_item& item = levels.last().add_text_list_item();
 
     if (pf.fBulletHasAutoNumber()) {
         if (m_continueListNumbering.contains(level) &&
             (m_continueListNumbering[level] == false)) {
-            out.addAttribute("text:start-value", pf.startNum());
+            item.set_text_start_value(pf.startNum());
         }
         m_continueListNumbering[level] = true;
     }
 
     // add styleless levels to get the right level of indentation
     while (levels.size() < level) {
-        out.startElement("text:list");
-        out.startElement("text:list-item");
-        levels.push("");
+        levels.push(TextListTag("", *levels.last().item));
+        levels.last().add_text_list_item();
+    }
+}
+template <typename T>
+void
+addTab(T& e, int ref) {
+    text_tab tab = e.add_text_tab();
+    if (ref >= 0)
+        tab.set_text_tab_ref(ref);
+}
+
+void addTextSpan(group_paragraph_content& content, const QString& text, const \
QMap<int, int>& tabCache) +{
+    int len = text.length();
+    int nrSpaces = 0; // number of consecutive spaces
+    bool leadingSpace = false;
+    QString str;
+    str.reserve(len);
+
+    // Accumulate chars either in str or in nrSpaces (for spaces).
+    // Flush str when writing a subelement (for spaces or for another reason)
+    // Flush nrSpaces when encountering two or more consecutive spaces
+    for (int i = 0; i < len ; ++i) {
+        QChar ch = text[i];
+        ushort unicode = ch.unicode();
+        if (unicode == ' ') {
+            if (i == 0)
+                leadingSpace = true;
+            ++nrSpaces;
+        } else {
+            if (nrSpaces > 0) {
+                // For the first space we use ' '.
+                // "it is good practice to use (text:s) for the second and all \
following SPACE +                // characters in a sequence." (per the ODF spec)
+                // however, per the HTML spec, "authors should not rely on user \
agents to render +                // white space immediately after a start tag or \
immediately before an end tag" +                // (and both we and OO.o ignore \
leading spaces in <text:p> or <text:h> elements...) +                if \
(!leadingSpace) { +                    str += ' ';
+                    --nrSpaces;
+                }
+                if (nrSpaces > 0) {   // there are more spaces
+                    if (!str.isEmpty())
+                        content.addTextNode(str);
+                    str.clear();
+                    text_s s = content.add_text_s();
+                    if (nrSpaces > 1)   // it's 1 by default
+                        s.set_text_c(nrSpaces);
+                }
+            }
+            nrSpaces = 0;
+            leadingSpace = false;
+
+            switch (unicode) {
+            case '\t':
+                if (!str.isEmpty())
+                    content.addTextNode(str);
+                str.clear();
+                addTab(content, tabCache.contains(i) ?tabCache[i] + 1 :-1);
+                break;
+            // gracefully handle \f form feed in text input.
+            // otherwise the xml will not be valid.
+            // \f can be added e.g. in ascii import filter.
+            case '\f':
+            case '\n':
+            case QChar::LineSeparator:
+                if (!str.isEmpty())
+                    content.addTextNode(str);
+                str.clear();
+                content.add_text_line_break();
+                break;
+            default:
+                // don't add stuff that is not allowed in xml. The stuff we need we \
have already handled above +                if (ch.unicode() >= 0x20) {
+                    str += text[i];
+                }
+                break;
+            }
+        }
+    }
+    // either we still have text in str or we have spaces in nrSpaces
+    if (!str.isEmpty()) {
+        content.addTextNode(str);
+    }
+    if (nrSpaces > 0) {   // there are more spaces
+        text_s s = content.add_text_s();
+        if (nrSpaces > 1)   // it's 1 by default
+            s.set_text_c(nrSpaces);
     }
 }
+void addTextSpan(group_paragraph_content& e, const QString& text)
+{
+    QMap<int, int> tabCache;
+    addTextSpan(e, text, tabCache);
+}
 
 int PptToOdp::processTextSpan(Writer& out, PptTextCFRun& cf, const \
                MSO::TextContainer* tc,
                               const QString& text, const int start, int end, \
quint16* p_fs) @@ -2615,8 +2714,8 @@ int PptToOdp::processTextSpan(Writer& out, \
PptTextCFRun& cf, const MSO::TextCont  KoGenStyle style(KoGenStyle::TextAutoStyle, \
"text");  style.setAutoStyleInStylesDotXml(out.stylesxml);
     defineTextProperties(style, cf, 0, 0, si, isSymbol);
-    out.xml.startElement("text:span", false);
-    out.xml.addAttribute("text:style-name", out.styles.insert(style));
+    text_span span(&out.xml);
+    span.set_text_style_name(out.styles.insert(style));
 
     // [MS-PPT]: exHyperlinkIdRef must be ignored unless action is in
     // {II_JumpAction, II_HyperlinkAction, II_CustomShowAction (0x7)}
@@ -2635,27 +2734,22 @@ int PptToOdp::processTextSpan(Writer& out, PptTextCFRun& cf, \
const MSO::TextCont  }
     }
 
+    QString href;
     if (mouseclick) {
-        out.xml.startElement("text:a", false);
         QPair<QString, QString> link = findHyperlink(
             mouseclick->interactive.interactiveInfoAtom.exHyperlinkIdRef);
         if (!link.second.isEmpty()) { // target
-            out.xml.addAttribute("xlink:href", link.second);
-            out.xml.addAttribute("xlink:type", "simple");
+            href = link.second;
         } else if (!link.first.isEmpty()) {
-            out.xml.addAttribute("xlink:href", link.first);
-            out.xml.addAttribute("xlink:type", "simple");
+            href = link.first;
         }
     } else if (mouseover) {
-        out.xml.startElement("text:a", false);
         QPair<QString, QString> link = findHyperlink(
             mouseover->interactive.interactiveInfoAtom.exHyperlinkIdRef);
         if (!link.second.isEmpty()) { // target
-            out.xml.addAttribute("xlink:href", link.second);
-            out.xml.addAttribute("xlink:type", "simple");
+            href = link.second;
         } else if (!link.first.isEmpty()) {
-            out.xml.addAttribute("xlink:href", link.first);
-            out.xml.addAttribute("xlink:type", "simple");
+            href = link.first;
         }
     } else {
         // count - specifies the number of characters of the
@@ -2670,18 +2764,25 @@ int PptToOdp::processTextSpan(Writer& out, PptTextCFRun& cf, \
const MSO::TextCont  }
 
     if (meta) {
-        writeMeta(*meta, m_processingMasters, out.xml);
+        if (!href.isNull()) {
+            text_a a(span.add_text_a(href));
+            text_meta m(a.add_text_meta());
+            writeMeta(*meta, m_processingMasters, m);
+        } else {
+            text_meta m(span.add_text_meta());
+            writeMeta(*meta, m_processingMasters, m);
+        }
     } else {
         int len = end - start;
         const QString txt = text.mid(start, len).replace('\r', '\n').replace('\v', \
                '\n');
-        out.xml.addTextSpan(txt);
-    }
-
-    if (mouseclick || mouseover) {
-        out.xml.endElement(); //text:a
+        if (!href.isNull()) {
+            text_a a(span.add_text_a(href));
+            addTextSpan(a, txt);
+        } else {
+            addTextSpan(span, txt);
+        }
     }
 
-    out.xml.endElement(); //text:span
     return end;
 } //end processTextSpan()
 
@@ -2719,7 +2820,7 @@ QString PptToOdp::defineAutoListStyle(Writer& out, const \
PptTextPFRun& pf, const  
 void
 PptToOdp::processParagraph(Writer& out,
-                           QStack<QString>& levels,
+                           ListStack& levels,
                            const MSO::OfficeArtClientData* clientData,
                            const MSO::TextContainer* tc,
                            const MSO::TextRuler* tr,
@@ -2795,10 +2896,10 @@ PptToOdp::processParagraph(Writer& out,
         }
 
         QString listStyle = defineAutoListStyle(out, pf, cf);
-	//check if we have the corresponding style for this level, if not then
-	//close the list and create a new one (K.I.S.S.)
-	if (!levels.isEmpty() && (levels.first() != listStyle)) {
-            writeTextObjectDeIndent(out.xml, 0, levels);
+        //check if we have the corresponding style for this level, if not then
+        //close the list and create a new one (K.I.S.S.)
+        if (!levels.isEmpty() && (levels.first().style != listStyle)) {
+            levels.clear();
         }
         if (!pf.fBulletHasAutoNumber()) {
             QList<quint16> levels = m_continueListNumbering.keys();
@@ -2820,18 +2921,16 @@ PptToOdp::processParagraph(Writer& out,
         if (levels.isEmpty()) {
             addListElement(out.xml, listStyle, levels, depth, pf);
         } else {
-            out.xml.endElement(); //text:list-item
-            out.xml.startElement("text:list-item");
+            levels.last().add_text_list_item();
         }
         m_previousListLevel = depth;
     } else {
-        writeTextObjectDeIndent(out.xml, 0, levels);
+        levels.clear();
         m_continueListNumbering.clear();
         m_lvlXmlIdMap.clear();
         m_previousListLevel = 0;
     }
 
-    out.xml.startElement("text:p");
     KoGenStyle style(KoGenStyle::ParagraphAutoStyle, "paragraph");
     style.setAutoStyleInStylesDotXml(out.stylesxml);
     defineParagraphProperties(style, pf, min_fontsize);
@@ -2839,9 +2938,15 @@ PptToOdp::processParagraph(Writer& out,
     if (start == end) {
         defineTextProperties(style, cf, 0, 0, 0);
     }
-    out.xml.addAttribute("text:style-name", out.styles.insert(style));
-    out.xml.addCompleteElement(&spans_buf);
-    out.xml.endElement(); //text:p
+    if (levels.isEmpty()) {
+        text_p p(&out.xml);
+        p.set_text_style_name(out.styles.insert(style));
+        p.addCompleteElement(&spans_buf);
+    } else {
+        text_p p(levels.last().item->add_text_p());
+        p.set_text_style_name(out.styles.insert(style));
+        p.addCompleteElement(&spans_buf);
+    }
 } //end processParagraph()
 
 int PptToOdp::processTextForBody(Writer& out, const MSO::OfficeArtClientData* \
clientData, @@ -2915,7 +3020,7 @@ int PptToOdp::processTextForBody(Writer& out, const \
MSO::OfficeArtClientData* cl  static const QRegExp lineend("[\v\r]");
     qint32 pos = 0, end = 0;
 
-    QStack<QString> levels;
+    ListStack levels;
     levels.reserve(5);
 
     // loop over all the '\r' delimited lines
@@ -2927,8 +3032,6 @@ int PptToOdp::processTextForBody(Writer& out, const \
MSO::OfficeArtClientData* cl  pos = end + 1;
     }
 
-    // close all open text:list elements
-    writeTextObjectDeIndent(out.xml, 0, levels);
     return 0;
 } //end processTextForBody()
 
@@ -2963,17 +3066,17 @@ void PptToOdp::processSlideForBody(unsigned slideNo, Writer& \
out)  nameStr.remove('\r');
     nameStr.remove('\v');
 
-    out.xml.startElement("draw:page");
     QString value = masterNames.value(master);
-    if (!value.isEmpty()) {
-        out.xml.addAttribute("draw:master-page-name", value);
+    if (value.isEmpty()) {
+        value = "unknown";
     }
-    out.xml.addAttribute("draw:name", nameStr);
+    draw_page page(&out.xml, value);
+    page.set_draw_name(nameStr);
     value = drawingPageStyles[slide];
     if (!value.isEmpty()) {
-        out.xml.addAttribute("draw:style-name", value);
+        page.set_draw_style_name(value);
     }
-    //xmlWriter.addAttribute("presentation:presentation-page-layout-name", "AL1T0");
+    //page.set_presentation_presentation_page_layout_name("AL1T0");
 
     const HeadersFootersAtom* headerFooterAtom = 0;
     if (master->anon.is<MainMasterContainer>()) {
@@ -2991,16 +3094,16 @@ void PptToOdp::processSlideForBody(unsigned slideNo, Writer& \
out)  headerFooterAtom = &getSlideHF()->hfAtom;
     }
     if (!usedDateTimeDeclaration.value(slideNo).isEmpty()) {
-        out.xml.addAttribute("presentation:use-date-time-name",
-                               usedDateTimeDeclaration[slideNo]);
+        page.set_presentation_use_date_time_name(
+                    usedDateTimeDeclaration[slideNo]);
     }
     if (!usedHeaderDeclaration.value(slideNo).isEmpty()) {
         if (!usedHeaderDeclaration[slideNo].isEmpty())
-            out.xml.addAttribute("presentation:use-header-name", \
usedHeaderDeclaration[slideNo]); +            \
page.set_presentation_use_header_name(usedHeaderDeclaration[slideNo]);  }
     if (!usedFooterDeclaration.value(slideNo).isEmpty()) {
         if (!usedFooterDeclaration[slideNo].isEmpty())
-            out.xml.addAttribute("presentation:use-footer-name", \
usedFooterDeclaration[slideNo]); +            \
page.set_presentation_use_footer_name(usedFooterDeclaration[slideNo]);  }
 
     m_currentSlideTexts = &p->documentContainer->slideList->rgChildRec[slideNo];
@@ -3029,18 +3132,15 @@ void PptToOdp::processSlideForBody(unsigned slideNo, Writer& \
out)  const NotesContainer* nc = p->notes[slideNo];
     if (nc && nc->drawing.OfficeArtDg.groupShape) {
         m_currentSlideTexts = 0;
-        out.xml.startElement("presentation:notes");
+        presentation_notes notes(page.add_presentation_notes());
         value = drawingPageStyles[nc];
         if (!value.isEmpty()) {
-            out.xml.addAttribute("draw:style-name", value);
+            notes.set_draw_style_name(value);
         }
         const OfficeArtSpgrContainer& spgr = \
                *(nc->drawing.OfficeArtDg.groupShape).data();
         drawclient.setDrawClientData(0, 0, p->notesMaster, nc, m_currentSlideTexts);
         odrawtoodf.processGroupShape(spgr, out);
-        out.xml.endElement();
     }
-
-    out.xml.endElement(); // draw:page
 } //end processSlideForBody()
 
 QString PptToOdp::processParaSpacing(const int value,
@@ -3506,42 +3606,32 @@ void PptToOdp::processDeclaration(KoXmlWriter* xmlWriter)
            QList<QPair<QString, QString> >items = declaration.values(DateTime);
            for( int i = items.size()-1; i >= 0; --i) {
                 QPair<QString, QString > item = items.at(i);
-                xmlWriter->startElement("presentation:date-time-decl");
-                xmlWriter->addAttribute("presentation:name", item.first);
-                xmlWriter->addAttribute("presentation:source", "current-date");
+                presentation_date_time_decl(xmlWriter, item.first, "current-date");
                 //xmlWrite->addAttribute("style:data-style-name", "Dt1");
-                xmlWriter->endElement();  // presentation:date-time-decl
             }
         } else if (slideHF->hfAtom.fHasUserDate) {
             QList<QPair<QString, QString> >items = declaration.values(DateTime);
             for( int i = 0; i < items.size(); ++i) {
                 QPair<QString, QString > item = items.at(i);
-                xmlWriter->startElement("presentation:date-time-decl");
-                xmlWriter->addAttribute("presentation:name", item.first);
-                xmlWriter->addAttribute("presentation:source", "fixed");
-                xmlWriter->addTextNode(item.second);
+                presentation_date_time_decl d(xmlWriter, item.first, "fixed");
+                d.addTextNode(item.second);
                 //Future - Add Fixed date data here
-                xmlWriter->endElement();  //presentation:date-time-decl
             }
         }
         if (headerAtom && slideHF->hfAtom.fHasHeader) {
             QList< QPair < QString, QString > > items = declaration.values(Header);
             for( int i = items.size()-1; i >= 0; --i) {
                 QPair<QString, QString > item = items.value(i);
-                xmlWriter->startElement("presentation:header-decl");
-                xmlWriter->addAttribute("presentation:name", item.first);
-                xmlWriter->addTextNode(item.second);
-                xmlWriter->endElement();  //presentation:header-decl
+                presentation_header_decl hd(xmlWriter, item.first);
+                hd.addTextNode(item.second);
             }
         }
         if (footerAtom && slideHF->hfAtom.fHasFooter) {
             QList< QPair < QString, QString > > items = declaration.values(Footer);
             for( int i = items.size()-1 ; i >= 0; --i) {
                 QPair<QString, QString > item = items.at(i);
-                xmlWriter->startElement("presentation:footer-decl");
-                xmlWriter->addAttribute("presentation:name", item.first);
-                xmlWriter->addTextNode(item.second);
-                xmlWriter->endElement();  //presentation:footer-decl
+                presentation_footer_decl fd(xmlWriter, item.first);
+                fd.addTextNode(item.second);
             }
         }
     }
diff --git a/filters/stage/powerpoint/PptToOdp.h \
b/filters/stage/powerpoint/PptToOdp.h index 8d85c1f..fce083b 100644
--- a/filters/stage/powerpoint/PptToOdp.h
+++ b/filters/stage/powerpoint/PptToOdp.h
@@ -96,6 +96,9 @@ public:
      * @return path
      */
     QString getPicturePath(const quint32 pib) const;
+
+    class TextListTag;
+    typedef QStack<TextListTag> ListStack;
 private:
 
     /**
@@ -310,6 +313,14 @@ private:
                          const quint16 indentLevel,
                          const ListStyleInput& info);
 
+    void defineListStyleProperties(KoXmlWriter& out, bool imageBullet,
+                                   const QString& bulletSize,
+                                   const PptTextPFRun& pf);
+
+    void defineListStyleTextProperties(KoXmlWriter& out_,
+                                       const QString& bulletSize,
+                                       const PptTextPFRun& pf);
+
     /**
      * TODO:
      * @param
@@ -368,7 +379,7 @@ private:
      */
     void addListElement(KoXmlWriter& out,
                         const QString& listStyle,
-                        QStack<QString>& levels,
+                        ListStack& levels,
                         quint16 level,
                         const PptTextPFRun &pf);
 
@@ -411,7 +422,7 @@ private:
      * @param end specifies end of the paragraph in text
      */
     void processParagraph(Writer& out,
-                          QStack<QString>& levels,
+                          ListStack& levels,
                           const MSO::OfficeArtClientData* cd,
                           const MSO::TextContainer* tc,
                           const MSO::TextRuler* tr,
diff --git a/libs/kotext/KoInlineNote.cpp b/libs/kotext/KoInlineNote.cpp
index 6faa9a9..4f7024b 100644
--- a/libs/kotext/KoInlineNote.cpp
+++ b/libs/kotext/KoInlineNote.cpp
@@ -32,6 +32,9 @@
 #include <KoStyleManager.h>
 #include <KoElementReference.h>
 #include <kdebug.h>
+#include <writeodf/writeodftext.h>
+#include <writeodf/writeodfoffice.h>
+#include <writeodf/writeodfdc.h>
 
 #include <QTextDocument>
 #include <QTextFrame>
@@ -43,6 +46,8 @@
 #include <QDateTime>
 #include <QWeakPointer>
 
+using namespace writeodf;
+
 class KoInlineNote::Private
 {
 public:
@@ -239,44 +244,30 @@ void KoInlineNote::saveOdf(KoShapeSavingContext & context)
     KoXmlWriter *writer = &context.xmlWriter();
 
     if (d->type == Footnote || d->type == Endnote) {
-        writer->startElement("text:note", false);
-        if (d->type == Footnote) {
-            writer->addAttribute("text:note-class", "footnote");
-        } else {
-            writer->addAttribute("text:note-class", "endnote");
-        }
-
-        writer->startElement("text:note-citation", false);
+        text_note note(writer, (d->type == Footnote) ?"footnote" :"endnote");
+        text_note_citation cite(note.add_text_note_citation());
         if (!autoNumbering()) {
-            writer->addAttribute("text:label", d->label);
+            cite.set_text_label(d->label);
         }
-        writer->addTextNode(d->label);
-        writer->endElement();
+        cite.addTextNode(d->label);
 
-        writer->startElement("text:note-body", false);
+        text_note_body body(note.add_text_note_body());
         KoTextWriter textWriter(context);
         textWriter.write(d->document, d->textFrame->firstPosition(), \
                d->textFrame->lastPosition());
-        writer->endElement();
-
-        writer->endElement();
     }
     else if (d->type == Annotation) {
-        writer->startElement("office:annotation");
+        office_annotation annotation(writer);
         if (!d->author.isEmpty()) {
-            writer->startElement("dc:creator");
-            writer->addTextNode(d->author);
-            writer->endElement();
+            dc_creator creator(annotation.add_dc_creator());
+            creator.addTextNode(d->author);
         }
         if (d->date.isValid()) {
-            writer->startElement("dc:date");
-            writer->addTextSpan(d->date.toString(Qt::ISODate));
-            writer->endElement();
+            dc_date date(annotation.add_dc_date());
+            date.addTextNode(d->date.toString(Qt::ISODate));
         }
 
         KoTextWriter textWriter(context);
         textWriter.write(d->document, \
                d->textFrame->firstPosition(),d->textFrame->lastPosition());
-
-        writer->endElement();
     }
 }
 
diff --git a/libs/odf/CMakeLists.txt b/libs/odf/CMakeLists.txt
index 8549ace..ad11606 100644
--- a/libs/odf/CMakeLists.txt
+++ b/libs/odf/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_subdirectory( tests )
+add_subdirectory( writeodf )
 
 include_directories( ${KOODF_INCLUDES} )
 
@@ -53,6 +54,7 @@ set(koodf_LIB_SRCS
 )
 
 kde4_add_library(koodf SHARED ${koodf_LIB_SRCS})
+add_dependencies(koodf writeodf.h-target)
 
 target_link_libraries(koodf ${KDE4_KIO_LIBS} ${QT_QTXML_LIBRARY})
 target_link_libraries(koodf LINK_INTERFACE_LIBRARIES ${KDE4_KIO_LIBS})
diff --git a/libs/odf/writeodf/CMakeLists.txt b/libs/odf/writeodf/CMakeLists.txt
new file mode 100644
index 0000000..51fae01
--- /dev/null
+++ b/libs/odf/writeodf/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(RNGFILE ${CMAKE_SOURCE_DIR}/devtools/scripts/OpenDocument-v1.2-cs01-schema-calligra.rng)
 +set(RNGHEADERDIR ${CMAKE_BINARY_DIR}/libs/odf/writeodf)
+add_custom_command(
+    OUTPUT ${RNGHEADERDIR}/writeodf.h
+    COMMAND ${CMAKE_BINARY_DIR}/devtools/rng2cpp/rng2cpp
+    ARGS ${RNGFILE} ${RNGHEADERDIR}
+    DEPENDS rng2cpp ${RNGFILE}
+    WORKING_DIRECTORY ${RNGHEADERDIR}
+)
+add_custom_target(writeodf.h-target DEPENDS writeodf.h)
diff --git a/libs/odf/writeodf/README.txt b/libs/odf/writeodf/README.txt
new file mode 100644
index 0000000..7aacc60
--- /dev/null
+++ b/libs/odf/writeodf/README.txt
@@ -0,0 +1,16 @@
+Generated classes in the writeodf namespace.
+
+The program rng2cpp compiles a given Relax NG (.rng) file into C++ headers. In \
Calligra, this is used in combination with the RNG for OpenDocument Format. +
+The generated code has an API with class names that resemble the names of the ODF \
tags. <text:h/> becomes writeodf::text_h, <office:automatic-styles> becomes \
writeodf::office_automatic_styles. +
+The generated code has advantages of directly using KoXMLWriter.
+ - function names instead of strings gives autocompletion and catches typing errors \
at compile time. + - since elements are added into other elements, the nesting is \
checked at compile time. + - elements are automatically closed when they go out of \
scope (but end() can be called to close them sooner) + - elements are automatically \
closed if another item (text or element or other) is added to its parent +
+Future improvements:
+ - also generate code for reading elements
+ - also generate code from OOXML Relax NG files
+ - check data types (bool, int, string) at compile time
diff --git a/libs/odf/writeodf/helpers.h b/libs/odf/writeodf/helpers.h
new file mode 100644
index 0000000..f546f20
--- /dev/null
+++ b/libs/odf/writeodf/helpers.h
@@ -0,0 +1,59 @@
+#ifndef WRITEODF_ODFWRITER_H
+#define WRITEODF_ODFWRITER_H
+
+#include "writeodf/writeodfconfig.h"
+
+namespace writeodf {
+
+template <class T>
+void addConfigItem(T& config, const QString & configName, const QString& value)
+{
+    config_config_item item(config.add_config_config_item(configName, "string"));
+    item.addTextNode(value);
+}
+
+template <class T>
+void addConfigItem(T& config, const QString & configName, bool value)
+{
+    config_config_item item(config.add_config_config_item(configName, "boolean"));
+    item.addTextNode(value ? "true" : "false");
+}
+
+template <class T>
+void addConfigItem(T& config, const QString & configName, int value)
+{
+    config_config_item item(config.add_config_config_item(configName, "int"));
+    item.addTextNode(QString::number(value));
+}
+
+template <class T>
+void addConfigItem(T& config, const QString & configName, double value)
+{
+    config_config_item item(config.add_config_config_item(configName, "double"));
+    item.addTextNode(QString::number(value));
+}
+
+template <class T>
+void addConfigItem(T& config, const QString & configName, float value)
+{
+    config_config_item item(config.add_config_config_item(configName, "double"));
+    item.addTextNode(QString::number(value));
+}
+
+template <class T>
+void addConfigItem(T& config, const QString & configName, long value)
+{
+    config_config_item item(config.add_config_config_item(configName, "long"));
+    item.addTextNode(QString::number(value));
+}
+
+template <class T>
+void addConfigItem(T& config, const QString & configName, short value)
+{
+    config_config_item item(config.add_config_config_item(configName, "short"));
+    item.addTextNode(QString::number(value));
+}
+
+}
+
+#endif
diff --git a/libs/odf/writeodf/odfwriter.h b/libs/odf/writeodf/odfwriter.h
new file mode 100644
index 0000000..22bc240
--- /dev/null
+++ b/libs/odf/writeodf/odfwriter.h
@@ -0,0 +1,101 @@
+#ifndef ODFWRITER_H
+#define ODFWRITER_H
+
+#include <KoXmlWriter.h>
+#include <QtCore/QUrl>
+#include <QtCore/QDate>
+#include <QtCore/QStringList>
+
+class OdfWriter {
+private:
+    OdfWriter* child;
+    OdfWriter* parent;
+    void operator=(const OdfWriter&);
+protected:
+    mutable KoXmlWriter* xml;
+    OdfWriter(KoXmlWriter* xml_, const char* tag, bool indent) :child(0), parent(0), \
xml(xml_) { +        xml->startElement(tag, indent);
+    }
+    OdfWriter(OdfWriter* p, const char* tag, bool indent) :child(0), parent(p), \
xml(parent->xml) { +        if (parent->child) {
+            parent->child->end();
+        }
+        parent->child = this;
+        xml->startElement(tag, indent);
+    }
+    ~OdfWriter() {
+        end();
+    }
+    void endChild() {
+        if (child) {
+            child->parent = 0;
+            child->end();
+            child = 0;
+        }
+    }
+    // ideally, the copy constructor would never be called
+    OdfWriter(const OdfWriter&o) :child(o.child), parent(o.parent), xml(o.xml) {
+        // disable o and make the parent refer to this new copy
+        o.xml = 0;
+        if (parent && parent->child == &o) {
+            parent->child = this;
+        }
+    }
+public:
+    void end() {
+        if (xml) {
+            endChild();
+            xml->endElement();
+            if (parent) {
+                parent->child = 0;
+            }
+            xml = 0;
+        }
+    }
+    void addTextNode(const QString& str) {
+        endChild();
+        xml->addTextNode(str);
+    }
+    void addAttribute(const char* name, const char* value) {
+        Q_ASSERT(!child);
+        xml->addAttribute(name, value);
+    }
+    void addAttribute(const char* name, const QString& value) {
+        Q_ASSERT(!child);
+        xml->addAttribute(name, value);
+    }
+    void addAttribute(const char* name, quint64 value) {
+        Q_ASSERT(!child);
+        xml->addAttribute(name, QString::number(value));
+    }
+    void addAttribute(const char* name, const QUrl& value) {
+        Q_ASSERT(!child);
+        xml->addAttribute(name, value.toString());
+    }
+    void addAttribute(const char* name, const QDate& value) {
+        Q_ASSERT(!child);
+        xml->addAttribute(name, value.toString(Qt::ISODate));
+    }
+    void addAttribute(const char* name, const QTime& value) {
+        Q_ASSERT(!child);
+        xml->addAttribute(name, value.toString(Qt::ISODate));
+    }
+    void addAttribute(const char* name, const QDateTime& value) {
+        Q_ASSERT(!child);
+        xml->addAttribute(name, value.toString(Qt::ISODate));
+    }
+    void addAttribute(const char* name, const QStringList& /*value*/) {
+        Q_ASSERT(!child);
+        xml->addAttribute(name, "");
+    }
+    void addProcessingInstruction(const char* cstr) {
+        endChild();
+        xml->addProcessingInstruction(cstr);
+    }
+    template <class T>
+    void addCompleteElement(T cstr) {
+        endChild();
+        xml->addCompleteElement(cstr);
+    }
+};
+#endif


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic