File indexing completed on 2024-05-12 16:28:27

0001 /* This file is part of the KDE project
0002    Copyright (C) 2013 Jos van den Oever <jos@vandenoever.info>
0003 
0004    This library is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU Library General Public
0006    License as published by the Free Software Foundation; either
0007    version 2 of the License, or (at your option) any later version.
0008 
0009    This library is distributed in the hope that it will be useful,
0010    but WITHOUT ANY WARRANTY; without even the implied warranty of
0011    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0012    Library General Public License for more details.
0013 
0014    You should have received a copy of the GNU Library General Public License
0015    along with this library; see the file COPYING.LIB.  If not, write to
0016    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0017    Boston, MA 02110-1301, USA.
0018 */
0019 #include <algorithm>
0020 
0021 #include <QFile>
0022 #include <QDebug>
0023 #include <QDomDocument>
0024 #include <QMap>
0025 #include <QtGlobal>
0026 #include <QSharedPointer>
0027 #include <QStringList>
0028 #include <QSet>
0029 #include <QVector>
0030 
0031 static QString ns() {
0032     return QStringLiteral("writeodf");
0033 }
0034 
0035 class RNGItem;
0036 //typedef QSharedPointer<RNGItem> RNGItemPtr;
0037 typedef RNGItem* RNGItemPtr;
0038 typedef QSet<RNGItemPtr> RNGItems;
0039 typedef QVector<RNGItemPtr> RNGItemList;
0040 
0041 /**
0042  * Helper class for writing fatal messages of the form fatal() << "error!";
0043  */
0044 class fatal
0045 {
0046 private:
0047     QString msg;
0048 public:
0049     fatal() {}
0050     ~fatal()
0051     {
0052         qFatal("%s", qPrintable(msg));
0053     }
0054     template<class T>
0055     fatal& operator<<(T s)
0056     {
0057         msg += s;
0058         return *this;
0059     }
0060 };
0061 
0062 /**
0063  * RNG Datatype has either a constant value or a type.
0064  */
0065 class Datatype
0066 {
0067 public:
0068     /**
0069      * Standard comparator for Datatype.
0070      */
0071     bool operator==(const Datatype& a) const {
0072         return constant == a.constant
0073                 && type == a.type;
0074     }
0075     /**
0076      * Standard comparator for Datatype.
0077      */
0078     bool operator!=(const Datatype& a) const {
0079         return !(*this == a);
0080     }
0081     /**
0082      * The data type is a constant string, this string may be "".
0083      * To check if this value has been set, call constant.isNull().
0084      */
0085     QString constant;
0086     /**
0087      * The data type name, this string may be "".
0088      * To check if this value has been set, call constant.isNull().
0089      */
0090     QString type;
0091 };
0092 
0093 /**
0094  * Define a qHash so Datatype can be put into a QSet.
0095  */
0096 uint qHash(const Datatype& d)
0097 {
0098     return qHash(d.constant) ^ qHash(d.type);
0099 }
0100 
0101 /**
0102  * @brief The RNGItem class
0103  * Generic class that describes any of a number of concepts from an RNG file.
0104  * Any <define/>, <element/> or <attribute/> class is parsed into an instance
0105  * of RNGItem.
0106  */
0107 class RNGItem
0108 {
0109 public:
0110     enum ItemType { Define, Start, Element, Attribute };
0111 private:
0112     RNGItem(const RNGItem&);
0113     void operator=(const RNGItem&);
0114 protected:
0115     /**
0116      * @brief type
0117      */
0118     const ItemType m_type;
0119     const QString m_name;
0120     /**
0121      * Internal constructor.
0122      */
0123     RNGItem(ItemType type, const QString &name = QString()) :m_type(type),
0124         m_name(name), mixedContent(false)
0125     {
0126         if (type != Start && name.isEmpty()) {
0127             fatal() << "name is empty";
0128         }
0129     }
0130 public:
0131     /**
0132      * true if this item may contain text nodes
0133      */
0134     bool mixedContent;
0135     /**
0136      * name attribute of the <define/> element
0137      */
0138     const QString& name() const { return m_name; }
0139     /**
0140      * transformed name that is used in generated C++ code
0141      */
0142     QString cppName;
0143     /**
0144      * items that are allowed to be used in this item
0145      */
0146     RNGItems allowedItems;
0147     /**
0148      * items that must to be used in this item
0149      */
0150     RNGItems requiredItems;
0151     /**
0152      * names of items that are used in this item
0153      * This list is resolved into allowedItems after parsing.
0154      */
0155     QSet<QString> referencedDeclares;
0156     /**
0157      * names of items that must be used in this item
0158      * This list is resolved into allowedItems after parsing.
0159      */
0160     QSet<QString> requiredReferencedDeclares;
0161     /**
0162      * Collection of possible datatypes for this item.
0163      */
0164     QSet<Datatype> datatype;
0165     /**
0166      * true if this is item corresponds to a <element/>
0167      */
0168     bool isElement() const { return m_type == Element; }
0169     /**
0170      * true if this is item corresponds to a <attribute/>
0171      */
0172     bool isAttribute() const { return m_type == Attribute; }
0173     /**
0174      * Return a string value if this item can only contain a single constant value.
0175      * For example "1.2" is the only allowed, but required value for the
0176      * office:version attribute.
0177      */
0178     QString singleConstant() const
0179     {
0180         return datatype.size() == 1 ?datatype.constBegin()->constant :QString();
0181     }
0182     /**
0183      * Return a string with the datatype if only one datatype is possible for
0184      * this item.
0185      */
0186     QString singleType() const
0187     {
0188         return datatype.size() == 1 ?datatype.constBegin()->type :QString();
0189     }
0190     /**
0191      * true if this is item corresponds to a <define/>
0192      */
0193     bool isDefine() const
0194     {
0195         bool isdefine = m_type == Define || m_type == Start;
0196         return isdefine;
0197     }
0198     /**
0199      * true if this is item corresponds to a <start/>
0200      */
0201     bool isStart() const
0202     {
0203         return m_type == Start;
0204     }
0205     bool operator==(const RNGItem& a) const;
0206 };
0207 
0208 /**
0209  * Specialization of RNGItem that is an element.
0210  */
0211 class Element : public RNGItem
0212 {
0213 public:
0214     Element(const QString& name) :RNGItem(RNGItem::Element, name)
0215     {
0216     }
0217 };
0218 
0219 /**
0220  * Specialization of RNGItem that is an attribute.
0221  */
0222 class Attribute : public RNGItem
0223 {
0224 public:
0225     Attribute(const QString& name) :RNGItem(RNGItem::Attribute, name)
0226     {
0227     }
0228 };
0229 
0230 /**
0231  * Specialization of RNGItem that is a a define.
0232  */
0233 class Start : public RNGItem
0234 {
0235 public:
0236     Start() :RNGItem(RNGItem::Start)
0237     {
0238     }
0239 };
0240 
0241 /**
0242  * Specialization of RNGItem that is a a define.
0243  */
0244 class Define : public RNGItem
0245 {
0246 public:
0247     Define(const QString& name) :RNGItem(RNGItem::Define, name)
0248     {
0249     }
0250 };
0251 
0252 /**
0253  * Simple helper class for collecting information about whether an RNGItem
0254  * may contain a mix of text nodes and elements.
0255  */
0256 class MixedCheck
0257 {
0258 public:
0259     int elementCount;
0260     bool mixed;
0261     MixedCheck() :elementCount(0), mixed(false) {}
0262 };
0263 
0264 /**
0265  * Determine if the RNGItem item may contain a mix of text nodes and elements.
0266  * @param item item to be investigated
0267  * @param seen items that were already seen, needed to avoid infinite recursion
0268  * @param mc data that is being collected
0269  */
0270 void isMixed(const RNGItemPtr& item, RNGItems& seen, MixedCheck& mc)
0271 {
0272     if (item->isAttribute() || seen.contains(item)) {
0273         return;
0274     }
0275     seen.insert(item);
0276     mc.mixed = mc.mixed || item->mixedContent;
0277     RNGItems::ConstIterator i = item->allowedItems.constBegin();
0278     RNGItems::ConstIterator end = item->allowedItems.constEnd();
0279     while (i != end) {
0280         if ((*i)->isDefine()) {
0281             isMixed(*i, seen, mc);
0282         } else if ((*i)->isElement()) {
0283             ++mc.elementCount;
0284         }
0285         ++i;
0286     }
0287 }
0288 
0289 /**
0290  * Determine if the RNGItem item may contain a mix of text nodes and elements.
0291  * This function call a helper function that inspects the item recursively.
0292  * @param item item to be investigated
0293  * @return true if item may contain a mix of text nodes and elements
0294  */
0295 bool isMixed(const RNGItemPtr& item)
0296 {
0297     RNGItems seen;
0298     MixedCheck mc;
0299     isMixed(item, seen, mc);
0300     return mc.mixed || mc.elementCount == 0;
0301 }
0302 
0303 /**
0304  * Merge item b in to item a.
0305  */
0306 void merge(RNGItemPtr& a, const RNGItemPtr& b)
0307 {
0308     if (b->mixedContent) {
0309         a->mixedContent = true;
0310     }
0311     Q_ASSERT(a->allowedItems.contains(b));
0312     if (a->requiredItems.contains(b)) {
0313         foreach(RNGItemPtr i, b->requiredItems) {
0314             a->requiredItems.insert(i);
0315         }
0316         a->requiredItems.remove(b);
0317     }
0318     foreach(RNGItemPtr i, b->allowedItems) {
0319         a->allowedItems.insert(i);
0320     }
0321     a->allowedItems.remove(b);
0322 }
0323 
0324 /**
0325  * Sort function to sort the items in a nice way.
0326  * <define/> items (including <start/> go first.
0327  * <element/> items come next.
0328  * <attribute/> items go last.
0329  * Items of similar type are compared by their names.
0330  */
0331 bool rngItemPtrLessThan(const RNGItemPtr &a, const RNGItemPtr &b)
0332 {
0333     if (a->isDefine()) {
0334         if (b->isDefine()) {
0335             return a->name() < b->name();
0336         }
0337         return true;
0338     }
0339     if (b->isDefine()) {
0340         return false;
0341     }
0342     if (a->isElement()) {
0343         if (b->isElement()) {
0344             if (a->name() == b->name()) {
0345                 // cppname maybe different, it can have e.g. a number appended
0346                 return a->cppName < b->cppName;
0347             }
0348             return a->name() < b->name();
0349         }
0350         return true;
0351     }
0352     if (b->isElement()) {
0353         return false;
0354     }
0355     if (a->name() == b->name()) {
0356         return a->cppName < b->cppName;
0357     }
0358     return a->name() < b->name();
0359 }
0360 
0361 /**
0362  * Class that has a separate open header file for each namespace and each
0363  * combination of namespaces.
0364  * This object is passed around the code generating functions instead of
0365  * a single output stream for a single header file.
0366  */
0367 class Files
0368 {
0369     /**
0370      * List of open files.
0371      */
0372     QMap<QString,QMap<QString,QTextStream*> > files;
0373     /**
0374      * Directory into which to write all the header files.
0375      */
0376     const QString outdir;
0377 public:
0378     Files(const QString& outdir_) :outdir(outdir_) {}
0379     /**
0380      * Close all open files after writing the closing '#endif'
0381      */
0382     ~Files() {
0383         typedef const QMap<QString,QTextStream*> map;
0384         foreach (map& m, files) {
0385             foreach (QTextStream* out, m) {
0386                 *out << "#endif\n";
0387                 out->device()->close();
0388                 delete out->device();
0389                 delete out;
0390             }
0391         }
0392     }
0393     QTextStream& getFile(const QString& tag, const QString& tag2);
0394     void closeNamespace();
0395 };
0396 
0397 /**
0398  * Create a map that maps each Relax NG type to a Qt/C++ type.
0399  */
0400 QMap<QString, QString> createTypeMap()
0401 {
0402     QMap<QString, QString> map;
0403     map.insert("string", "const QString&");
0404     map.insert("date", "const QDate&");
0405     map.insert("time", "const QTime&");
0406     map.insert("dateTime", "const QDateTime&");
0407     map.insert("duration", "Duration");
0408     map.insert("integer", "qint64");
0409     map.insert("nonNegativeInteger", "quint64");
0410     map.insert("positiveInteger", "quint64");
0411     map.insert("double", "double");
0412     map.insert("anyURI", "const QUrl&");
0413     map.insert("base64Binary", "const QByteArray&");
0414     map.insert("ID", "const QString&");
0415     map.insert("IDREF", "const QString&");
0416     map.insert("IDREFS", "const QStringList&");
0417     map.insert("NCName", "const QString&");
0418     map.insert("language", "const QString&");
0419     map.insert("token", "const QString&");
0420     map.insert("QName", "const QString&");
0421     map.insert("decimal", "double");
0422     return map;
0423 }
0424 
0425 /**
0426  * Return a Qt/C++ type for each Relax NG type.
0427  */
0428 QString mapType(const QString& type)
0429 {
0430     static const QMap<QString, QString> map = createTypeMap();
0431     if (!map.contains(type)) {
0432         fatal() << "Unknown data type " << type;
0433     }
0434     return map.value(type);
0435 }
0436 
0437 /**
0438  * see below
0439  */
0440 void parseContent(const QDomElement& content, RNGItem& item, bool required);
0441 
0442 /**
0443  * Get a list of names for the attribute or element.
0444  */
0445 QStringList getNames(const QDomElement& e)
0446 {
0447     QStringList names;
0448     QString name = e.attribute("name");
0449     if (name.isEmpty()) {
0450         QDomElement ce = e.firstChildElement();
0451         if (ce.localName() == "choice") {
0452             ce = ce.firstChildElement();
0453             while (!ce.isNull()) {
0454                 if (ce.localName() == "name") {
0455                     names << ce.text();
0456                 } else {
0457                     fatal() << "Found element without comprehensible name.";
0458                 }
0459                 ce = ce.nextSiblingElement();
0460             }
0461         } else if (ce.localName() != "anyName") {
0462             fatal() << "Found element without comprehensible name.";
0463         }
0464     } else {
0465         names << name;
0466     }
0467     return names;
0468 }
0469 
0470 /**
0471  * Parse an <element/> element.
0472  */
0473 void parseElement(const QDomElement& e, RNGItem& parent, bool required)
0474 {
0475     QStringList names = getNames(e);
0476     foreach (const QString& name, names) {
0477         RNGItemPtr element = RNGItemPtr(new Element(name));
0478         parseContent(e, *element, true);
0479         parent.allowedItems.insert(element);
0480         if (required) {
0481             parent.requiredItems.insert(element);
0482         }
0483     }
0484 }
0485 
0486 /**
0487  * Parse an <attribute/> element.
0488  */
0489 void parseAttribute(const QDomElement& e, RNGItem& parent, bool required)
0490 {
0491     QStringList names = getNames(e);
0492     foreach (const QString& name, names) {
0493         RNGItemPtr attribute = RNGItemPtr(new Attribute(name));
0494         parseContent(e, *attribute, true);
0495         parent.allowedItems.insert(attribute);
0496         if (required) {
0497             parent.requiredItems.insert(attribute);
0498         }
0499     }
0500 }
0501 
0502 /**
0503  * Parse the contents of any Relax NG element.
0504  */
0505 void parseContent(const QDomElement& content, RNGItem& item, bool required)
0506 {
0507     QDomElement e = content.firstChildElement();
0508     while (!e.isNull()) {
0509         QString type = e.localName();
0510         QString name = e.attribute("name");
0511         if (type == "interleave" || type == "oneOrMore" || type == "group") {
0512             parseContent(e, item, required);
0513         } else if (type == "optional" || type == "choice"
0514                    || type == "zeroOrMore") {
0515             parseContent(e, item, false);
0516         } else if (type == "ref") {
0517             item.referencedDeclares.insert(name);
0518             if (required) {
0519                 item.requiredReferencedDeclares.insert(name);
0520             }
0521         } else if (type == "empty") {
0522         } else if (type == "data") {
0523             Datatype d;
0524             d.type = mapType(e.attribute("type"));
0525             item.datatype.insert(d);
0526         } else if (type == "list") {
0527         } else if (type == "description") {
0528         } else if (type == "attribute") {
0529             parseAttribute(e, item, required);
0530         } else if (type == "element") {
0531             parseElement(e, item, required);
0532         } else if (type == "text") {
0533             item.mixedContent = true;
0534         } else if (type == "value") {
0535             Datatype d;
0536             d.constant = e.text();
0537             item.datatype.insert(d);
0538         } else if (type == "name") {
0539         } else if (type == "anyName") {
0540         } else if (type == "mixed") {
0541         } else {
0542             fatal() << "Unknown element " << type;
0543         }
0544         e = e.nextSiblingElement();
0545     }
0546 }
0547 
0548 /**
0549  * Parse the contents of a <define/> or <start/> element.
0550  */
0551 RNGItemPtr parseDefine(const QDomElement& defineElement, RNGItems& items, bool isstart)
0552 {
0553     RNGItemPtr item;
0554     if (isstart) {
0555         item = RNGItemPtr(new Start());
0556     } else {
0557         item = RNGItemPtr(new Define(defineElement.attribute("name")));
0558     }
0559     parseContent(defineElement, *item, true);
0560     items.insert(item);
0561     return item;
0562 }
0563 
0564 /**
0565  * Parse all top level Relax NG elements.
0566  */
0567 RNGItemPtr getDefines(QDomElement e, RNGItems& items)
0568 {
0569     RNGItemPtr start = RNGItemPtr(0);
0570     e = e.firstChildElement();
0571     while (!e.isNull()) {
0572         if (e.localName() == "define") {
0573             parseDefine(e, items, false);
0574         } else if (e.localName() == "start") {
0575             Q_ASSERT_X(!start, "getDefines", "Multiple start elements.");
0576             start = parseDefine(e, items, true);
0577         } else {
0578             fatal() << "Unknown element " << e.localName();
0579         }
0580         e = e.nextSiblingElement();
0581     }
0582     return start;
0583 }
0584 
0585 /**
0586  * Load an XML from disk into a DOMDocument instance.
0587  */
0588 QDomDocument loadDOM(const QString& url)
0589 {
0590     QFile f(url);
0591     f.open(QIODevice::ReadOnly);
0592     QByteArray data = f.readAll();
0593     f.close();
0594 
0595     QDomDocument dom;
0596     QString err;
0597     if (!dom.setContent(data, true, &err)) {
0598         fatal() << err;
0599     }
0600     return dom;
0601 }
0602 
0603 /**
0604  * Look through a set of RNGitems to find one that is the same.
0605  * This can be used after parsing to find definitions that are the same.
0606  * Such deduplication can reduce the size of the generated code.
0607  */
0608 RNGItemPtr findEqualItem(const RNGItemPtr&i, const RNGItems& items)
0609 {
0610     foreach (const RNGItemPtr& j, items) {
0611         if (*i == *j) {
0612             return j;
0613         }
0614     }
0615     return RNGItemPtr();
0616 }
0617 
0618 /**
0619  * Compare two RNGItem instances.
0620  */
0621 bool RNGItem::operator==(const RNGItem& a) const
0622 {
0623     bool unequal = m_type != a.m_type
0624             || m_name != a.m_name
0625             || mixedContent != a.mixedContent
0626             || cppName != a.cppName
0627             || referencedDeclares != a.referencedDeclares
0628             || requiredReferencedDeclares != a.requiredReferencedDeclares
0629             || allowedItems.size() != a.allowedItems.size()
0630             || requiredItems.size() != a.requiredItems.size()
0631             || datatype != a.datatype;
0632     if (unequal) {
0633         return false;
0634     }
0635     foreach (const RNGItemPtr& i, allowedItems) {
0636         RNGItemPtr j = findEqualItem(i, a.allowedItems);
0637         if (!j) {
0638             return false;
0639         }
0640     }
0641     foreach (const RNGItemPtr& i, requiredItems) {
0642         RNGItemPtr j = findEqualItem(i, a.requiredItems);
0643         if (!j) {
0644             return false;
0645         }
0646     }
0647     return true;
0648 }
0649 
0650 /**
0651  * Move all member items in the global list.
0652  * If there is already a global member that is equal, use that in the item.
0653  */
0654 void collect(RNGItem& item, RNGItems& collected)
0655 {
0656     typedef QPair<RNGItemPtr,RNGItemPtr> Pair;
0657     QList<Pair> toSwap;
0658     foreach (const RNGItemPtr& i, item.allowedItems) {
0659         RNGItemPtr j = findEqualItem(i, collected);
0660         if (!j) {
0661             collected.insert(i);
0662             collect(*i, collected);
0663         } else if (i != j) {
0664             toSwap.append(qMakePair(i, j));
0665         }
0666     }
0667     foreach (const Pair& i, toSwap) {
0668         RNGItemPtr toRemove = i.first;
0669         RNGItemPtr toAdd = i.second;
0670         if (item.requiredItems.contains(toRemove)) {
0671             item.requiredItems.remove(toRemove);
0672             item.requiredItems.insert(toAdd);
0673         }
0674         item.allowedItems.remove(toRemove);
0675         item.allowedItems.insert(toAdd);
0676     }
0677 }
0678 
0679 /**
0680  * Move all member items in the global list.
0681  * If there is already a global member that is equal, use that in the item.
0682  */
0683 void collect(const RNGItems& items, RNGItems& collected)
0684 {
0685     foreach (const RNGItemPtr& item, items) {
0686         collect(*item, collected);
0687     }
0688 }
0689 
0690 /**
0691  * Count how often a particular item is used by other items or itself.
0692  */
0693 void countUsage(RNGItem& item, QHash<RNGItemPtr,int>& usageCount)
0694 {
0695     foreach (const RNGItemPtr& i, item.allowedItems) {
0696         if (usageCount.contains(i)) {
0697             usageCount[i]++;
0698         } else {
0699             usageCount[i] = 1;
0700         }
0701     }
0702 }
0703 
0704 /**
0705  * Remove items that are not used and merge items that are used in only one
0706  * place into their parent if possible.
0707  * This reduces the number of classes in the generated headers.
0708  */
0709 int reduce(RNGItems& items)
0710 {
0711     QHash<RNGItemPtr,int> usageCount;
0712     foreach (const RNGItemPtr& item, items) {
0713         countUsage(*item, usageCount);
0714     }
0715     RNGItems toRemove;
0716     foreach (RNGItemPtr item, items) {
0717         if (usageCount[item] <= 1 && !item->isStart() && item->isDefine()) {
0718             RNGItemPtr user = RNGItemPtr(0);
0719             foreach (const RNGItemPtr& i, items) {
0720                 if (i->allowedItems.contains(item)) {
0721                     Q_ASSERT(!user);
0722                     user = i;
0723                 }
0724             }
0725             if (user) {
0726                 merge(user, item);
0727             }
0728             toRemove.insert(item);
0729             break;
0730         }
0731     }
0732     foreach (const RNGItemPtr& item, toRemove) {
0733         items.remove(item);
0734     }
0735     return toRemove.size();
0736 }
0737 
0738 /**
0739  * Collect items that are contained in other items into a list with
0740  * all the items.
0741  * Relax NG is a hierarchical file format and this function creates a flat list
0742  * with all items.
0743  */
0744 int expand(RNGItems& items)
0745 {
0746     RNGItems toAdd;
0747     foreach (RNGItemPtr item, items) {
0748         foreach (const RNGItemPtr& i, item->allowedItems) {
0749             if (!items.contains(i)) {
0750                 toAdd.insert(i);
0751             }
0752         }
0753     }
0754     foreach (RNGItemPtr item, toAdd) {
0755         items.insert(item);
0756     }
0757     return toAdd.size();
0758 }
0759 
0760 /**
0761  * Find the <define/> item by name.
0762  */
0763 RNGItemPtr getDefine(const QString& name, const RNGItems& items)
0764 {
0765     RNGItemPtr item = RNGItemPtr(0);
0766     foreach (RNGItemPtr i, items) {
0767         if (i->name() == name) {
0768             Q_ASSERT_X(!item, "getDefine", qPrintable("Doubly defined element" + name + "."));
0769             item = i;
0770         }
0771     }
0772     Q_ASSERT_X(item, "getDefine", qPrintable("Define not found " + name));
0773     return item;
0774 }
0775 
0776 /**
0777  * Resolve all <define/> references.
0778  * After parsing, the <ref/> instances should be replaced by the actual
0779  * items.
0780  */
0781 void resolveDefines(RNGItemPtr start, const RNGItems& items, RNGItems& resolved)
0782 {
0783     if (resolved.contains(start)) {
0784         return;
0785     }
0786     resolved.insert(start);
0787     foreach (const QString& name, start->referencedDeclares) {
0788         RNGItemPtr i = getDefine(name, items);
0789         if (start->requiredReferencedDeclares.contains(name)) {
0790             start->requiredItems.insert(i);
0791         }
0792         start->allowedItems.insert(i);
0793     }
0794     start->referencedDeclares.clear();
0795     start->requiredReferencedDeclares.clear();
0796 
0797     foreach (RNGItemPtr item, start->allowedItems) {
0798         resolveDefines(item, items, resolved);
0799     }
0800 }
0801 
0802 /**
0803  * Create a C++ name from the item name.
0804  */
0805 QString makeCppName(const RNGItemPtr&item)
0806 {
0807     QString name;
0808     if (item->isElement() || item->isAttribute()) {
0809         name = item->name();
0810     } else {
0811         name = "group_" + item->name();
0812     }
0813     name.replace(':', '_');
0814     name.replace('-', '_');
0815     return name;
0816 }
0817 
0818 /**
0819  * Create a new name from the item name.
0820  * The new name will not clash with the names from takenNames.
0821  */
0822 QString makeUniqueCppName(const RNGItemPtr&item, QSet<QString>& takenNames)
0823 {
0824     QString n = makeCppName(item);
0825     QString name = n;
0826     int i = 0;
0827     while (!name.isEmpty() && takenNames.contains(name)) {
0828         name = n + "_" + QString::number(++i);
0829     }
0830     takenNames.insert(name);
0831     return name;
0832 }
0833 
0834 /**
0835  * Create all the C++ names corresponding with the Relax NG items.
0836  */
0837 void makeCppNames(RNGItemList& items)
0838 {
0839     QSet<QString> cppnames;
0840     // handle elements first so they have the nicest names
0841     foreach (RNGItemPtr item, items) {
0842         if (item->isElement()) {
0843             item->cppName = makeUniqueCppName(item, cppnames);
0844         }
0845     }
0846     // next handle the attributes
0847     foreach (RNGItemPtr item, items) {
0848         if (item->isAttribute()) {
0849             item->cppName = makeUniqueCppName(item, cppnames);
0850         }
0851     }
0852     // give the remaining declares names
0853     foreach (RNGItemPtr item, items) {
0854         if (item->isDefine()) {
0855             item->cppName = makeUniqueCppName(item, cppnames);
0856         }
0857     }
0858 }
0859 
0860 /**
0861  * Check if an element or attribute is defined below this item.
0862  */
0863 bool hasElementOrAttribute(const RNGItemPtr& item, RNGItems& items)
0864 {
0865     if (items.contains(item)) {
0866         return false;
0867     }
0868     foreach (const RNGItemPtr& i, item->allowedItems) {
0869         if (!i->isDefine() || hasElementOrAttribute(i, items)) {
0870             return true;
0871         }
0872     }
0873     return false;
0874 }
0875 
0876 /**
0877  * Check if an element or attribute is defined below this item.
0878  */
0879 bool hasElementOrAttribute(const RNGItemPtr& item)
0880 {
0881     RNGItems items;
0882     return hasElementOrAttribute(item, items);
0883 }
0884 
0885 static RNGItemList toVector(const RNGItems& list) {
0886     RNGItemList l;
0887     l.reserve(list.size());
0888     std::copy(list.constBegin(), list.constEnd(), std::back_inserter(l));
0889     return l;
0890 }
0891 
0892 /**
0893  * Find all the items that are used in this item but are not element or
0894  * attributes.
0895  * These items will be base classes to a class that corresponds to an element.
0896  */
0897 RNGItemList getBasesList(RNGItemPtr item)
0898 {
0899     RNGItems list;
0900     RNGItems antilist;
0901     foreach (RNGItemPtr i, item->allowedItems) {
0902         if (i->isDefine() && hasElementOrAttribute(i)) {
0903             list.insert(i);
0904             foreach (RNGItemPtr j, i->allowedItems) {
0905                 if (j->isDefine() && j != i) {
0906                     antilist.insert(j);
0907                 }
0908             }
0909         }
0910     }
0911     list.subtract(antilist);
0912     RNGItemList l = toVector(list);
0913     std::stable_sort(l.begin(), l.end(), rngItemPtrLessThan);
0914     return l;
0915 }
0916 
0917 /**
0918  * Sort items in the set.
0919  * This is helpful in making the output reproducible.
0920  */
0921 RNGItemList list(const RNGItems& items)
0922 {
0923     RNGItemList list = toVector(items);
0924     std::stable_sort(list.begin(), list.end(), rngItemPtrLessThan);
0925     return list;
0926 }
0927 
0928 /**
0929  * Collect the data types of the attribute item.
0930  */
0931 void resolveType(const RNGItemPtr& item, QSet<Datatype>& type)
0932 {
0933     type.unite(item->datatype);
0934     foreach (const RNGItemPtr& i, item->allowedItems) {
0935         resolveType(i, type);
0936     }
0937 }
0938 
0939 /**
0940  * Collect the data types of the attributes.
0941  */
0942 void resolveAttributeDataTypes(const RNGItems& items)
0943 {
0944     foreach (const RNGItemPtr& i, items) {
0945         if (i->isAttribute()) {
0946             resolveType(i, i->datatype);
0947         }
0948     }
0949 }
0950 
0951 /**
0952  * Create a ordered list of items.
0953  * The order is such that dependencies of an item precede the item in the list.
0954  */
0955 void addInOrder(RNGItemList& undefined, RNGItemList& defined)
0956 {
0957     int last = -1;
0958     while (last != undefined.size()) {
0959         last = undefined.size();
0960         for (int i = 0; i < undefined.size(); ++i) {
0961             const RNGItemPtr& ii = undefined[i];
0962             bool missingDependency = false;
0963             foreach (const RNGItemPtr& j, list(ii->allowedItems)) {
0964                 if (j->isDefine() && !defined.contains(j) && j != ii) {
0965                     if (undefined.contains(j)) {
0966                         missingDependency = true;
0967                     } else if (j->name().isEmpty()) {
0968                         ii->allowedItems.remove(j);
0969                         ii->requiredItems.remove(j);
0970                     }
0971                 }
0972             }
0973             if (!missingDependency) {
0974                 defined.append(ii);
0975                 undefined.remove(i);
0976             } else {
0977                 ++i;
0978             }
0979         }
0980     }
0981     if (undefined.size()) {
0982         fatal() << undefined.size() << " missing dependencies";
0983         undefined.clear();
0984     }
0985     // Q_ASSERT(undefined.size() == 0);
0986 }
0987 
0988 /**
0989  * Helper structure to collect required arguments.
0990  */
0991 struct RequiredArgsList
0992 {
0993     int length;
0994     QString args;
0995     QString vals;
0996 };
0997 
0998 /**
0999  * Write lists of required arguments that can be used in generated code.
1000  * This list only covers the required attributes, not required elements.
1001  */
1002 RequiredArgsList makeRequiredArgsList(const RNGItemPtr& item)
1003 {
1004     RequiredArgsList r;
1005     r.length = 0;
1006     foreach (RNGItemPtr i, list(item->requiredItems)) {
1007         if (i->isAttribute() && i->singleConstant().isNull()) {
1008             QString name = makeCppName(i);
1009             QString type = i->singleType();
1010             if (type.isNull()) {
1011                 type = "const QString&";
1012             }
1013             r.args += type + " " + name + ", ";
1014             r.vals += ", " + name;
1015             ++r.length;
1016         }
1017     }
1018     r.args.chop(2);
1019     return r;
1020 }
1021 
1022 /**
1023  * Recursively find the items that are required for the given item.
1024  */
1025 RNGItemList getAllRequiredAttributes(const RNGItemPtr& item, RNGItemList& list, int depth = 0)
1026 {
1027     if (depth > 10) {
1028         return list;
1029     }
1030     foreach (RNGItemPtr i, item->allowedItems) {
1031         if (item->requiredItems.contains(i)) {
1032             if (i->isAttribute() && i->singleConstant().isNull()) {
1033                 list.append(i);
1034             } else if (i->isDefine()) {
1035                 getAllRequiredAttributes(i, list, depth + 1);
1036             }
1037         }
1038     }
1039     return list;
1040 }
1041 
1042 /**
1043  * Write full lists of required arguments that can be used in generated code.
1044  */
1045 RequiredArgsList makeFullRequiredArgsList(const RNGItemPtr& item)
1046 {
1047     RequiredArgsList r;
1048     r.length = 0;
1049     RNGItemList list;
1050     getAllRequiredAttributes(item, list);
1051     std::stable_sort(list.begin(), list.end(), rngItemPtrLessThan);
1052     foreach (RNGItemPtr i, list) {
1053         QString name = makeCppName(i);
1054         QString type = i->singleType();
1055         if (type.isNull()) {
1056             type = "const QString&";
1057         }
1058         r.args += type + " " + name + ", ";
1059         r.vals += ", " + name;
1060         ++r.length;
1061     }
1062     r.args.chop(2);
1063     return r;
1064 }
1065 
1066 /**
1067  * Write C++ code to set the required attribute values.
1068  */
1069 void setRequiredAttributes(QTextStream& out, const RNGItemPtr& item)
1070 {
1071     QString o;
1072     if (!item->isElement()) {
1073         o = "xml.";
1074     }
1075     foreach (RNGItemPtr i, list(item->requiredItems)) {
1076         if (i->isAttribute()) {
1077             out << "        " << o << "addAttribute(\"" + i->name() + "\", ";
1078             QString constant = i->singleConstant();
1079             if (constant.isNull()) {
1080                 out << makeCppName(i);
1081             } else {
1082                 out << "\"" << constant << "\"";
1083             }
1084             out << ");\n";
1085         }
1086     }
1087 }
1088 
1089 /**
1090  * Write the class definition for a class that corresponds to an xml element.
1091  */
1092 void defineElement(QTextStream& out, const RNGItemPtr& item)
1093 {
1094     const RNGItemList bases = getBasesList(item);
1095     out << "class " << item->cppName << " : public OdfWriter";
1096     for (auto i = bases.begin(); i != bases.end(); ++i) {
1097         out << ", public " << (*i)->cppName;
1098     }
1099     out << " {\n";
1100     out << "public:" << "\n";
1101     RequiredArgsList r = makeFullRequiredArgsList(item);
1102     if (r.args.length()) {
1103         r.args = ", " + r.args;
1104     }
1105     out << "    " << item->cppName << "(OdfWriter* x" << r.args
1106         << ") :OdfWriter(x, \"" << item->name() << "\", "
1107         << (isMixed(item) ?"false" :"true") << ")";
1108     for (auto i = bases.begin(); i != bases.end(); ++i) {
1109         RequiredArgsList r;
1110         if (item->requiredItems.contains(*i)) {
1111             r = makeFullRequiredArgsList(*i);
1112         }
1113         out << ", " << (*i)->cppName << "(*static_cast<OdfWriter*>(this)" << r.vals << ")";
1114     }
1115     out << " {\n";
1116     setRequiredAttributes(out, item);
1117     out << "    }\n";
1118     out << "    " << item->cppName << "(KoXmlWriter* x" << r.args
1119         << ") :OdfWriter(x, \"" << item->name() << "\", "
1120         << (isMixed(item) ?"false" :"true") << ")";
1121     for (auto i = bases.begin(); i != bases.end(); ++i) {
1122         RequiredArgsList r;
1123         if (item->requiredItems.contains(*i)) {
1124             r = makeFullRequiredArgsList(*i);
1125         }
1126         out << ", " << (*i)->cppName << "(*static_cast<OdfWriter*>(this)" << r.vals << ")";
1127     }
1128     out << " {\n";
1129     setRequiredAttributes(out, item);
1130     out << "    }\n";
1131     QSet<QString> doneA;
1132     QSet<QString> doneE;
1133     foreach (RNGItemPtr i, list(item->allowedItems)) {
1134         QString name = makeCppName(i);
1135         if (i->isAttribute() && !item->requiredItems.contains(i) && !doneA.contains(name)) {
1136             QString type = i->singleType();
1137             if (type.isNull()) {
1138                 out << "    template<class T>\n";
1139                 type = "const T&";
1140             }
1141             out << "    void set_" << name << "(" + type + " value) {\n";
1142             out << "        addAttribute(\"" + i->name() + "\", value);\n";
1143             out << "    }\n";
1144             doneA.insert(name);
1145         } else if (i->isElement() && !doneE.contains(name)) {
1146             RequiredArgsList r = makeFullRequiredArgsList(i);
1147             out << "    " << i->cppName << " add_" << name << "(" + r.args + ");\n";
1148             doneE.insert(name);
1149         }
1150     }
1151     if (isMixed(item)) {
1152         out << "    void addTextNode(const QString& data) {\n";
1153         out << "        OdfWriter::addTextNode(data);\n";
1154         out << "    }\n";
1155     }
1156     out << "};\n";
1157 }
1158 
1159 /**
1160  * Write the class definition for a class that corresponds to a Relax NG group.
1161  * These groups are bases to classes that correspond to elements.
1162  */
1163 void defineGroup(QTextStream& out, const RNGItemPtr& item)
1164 {
1165     if (!hasElementOrAttribute(item)) {
1166         return;
1167     }
1168     const RNGItemList bases = getBasesList(item);
1169     out << "class " << item->cppName;
1170     if (bases.size()) {
1171         RNGItemList::const_iterator i = bases.begin();
1172         out << " : public " << (*i)->cppName;
1173         while (++i != bases.end()) {
1174             out << ", public " << (*i)->cppName;
1175         }
1176     }
1177     out << " {\n";
1178     out << "private:\n";
1179     out << "    OdfWriter& xml;\n";
1180     out << "public:\n";
1181     RequiredArgsList r = makeFullRequiredArgsList(item);
1182     if (r.args.length()) {
1183         r.args = ", " + r.args;
1184     }
1185     out << "    " << item->cppName << "(OdfWriter& x" + r.args + ") :";
1186     foreach (const RNGItemPtr& i, bases) {
1187         RequiredArgsList r;
1188         if (item->requiredItems.contains(i)) {
1189             r = makeFullRequiredArgsList(i);
1190         }
1191         out << i->cppName << "(x" + r.vals + "), ";
1192     }
1193     out << "xml(x) {\n";
1194     setRequiredAttributes(out, item);
1195     out << "    }\n";
1196     if (r.length) {
1197         out << "    " << item->cppName << "(OdfWriter& x) :";
1198         foreach (const RNGItemPtr& i, bases) {
1199             out << i->cppName << "(x), ";
1200         }
1201         out << "xml(x) {}\n";
1202     }
1203     QSet<QString> done;
1204     foreach (RNGItemPtr i, list(item->allowedItems)) {
1205         QString name = makeCppName(i);
1206         // also allow setting of required elements, because the might need to be
1207         // set in elements where the group is optional
1208         if (i->isAttribute() && !done.contains(name)) {
1209             QString type = i->singleType();
1210             if (type.isNull()) {
1211                 out << "    template<class T>\n";
1212                 type = "const T&";
1213             }
1214             out << "    void set_" << name << "(" << type << " value) {\n";
1215             out << "        xml.addAttribute(\"" + i->name() + "\", value);\n";
1216             out << "    }\n";
1217             done.insert(name);
1218         } else if (i->isElement()) {
1219             RequiredArgsList r = makeFullRequiredArgsList(i);
1220             out << "    " << i->cppName << " add_" << name << "(" + r.args + ");\n";
1221         }
1222     }
1223     if (isMixed(item)) {
1224         out << "    void addTextNode(const QString& data) {\n";
1225         out << "        xml.addTextNode(data);\n";
1226         out << "    }\n";
1227     }
1228     out << "};\n";
1229 }
1230 
1231 /**
1232  * Write the definition for a member function to add an element to another
1233  * element.
1234  */
1235 void writeAdderDefinition(const RNGItemPtr& item, const RNGItemPtr& i, QTextStream& out)
1236 {
1237     QString name = makeCppName(i);
1238     RequiredArgsList r = makeFullRequiredArgsList(i);
1239     out << "inline ";
1240     if (!ns().isEmpty()) {
1241         out << ns() << "::";
1242     }
1243     out << i->cppName << "\n";
1244     if (!ns().isEmpty()) {
1245         out << ns() << "::";
1246     }
1247     out << item->cppName << "::add_" << name << "(";
1248     out << r.args << ") {\n";
1249     out << "    return " << ns() << "::" << i->cppName << "(";
1250     if (item->isElement()) {
1251         out << "this";
1252     } else {
1253         out << "&xml";
1254     }
1255     out << r.vals << ");\n";
1256     out << "}\n";
1257 }
1258 
1259 /**
1260  * Write the definitions for member functions to add elements to other
1261  * element.
1262  */
1263 void writeAdderDefinitions(const RNGItemPtr& item, Files& files)
1264 {
1265     QSet<QString> done;
1266     foreach (RNGItemPtr i, list(item->allowedItems)) {
1267         QString name = makeCppName(i);
1268         if (i->isElement() && !done.contains(name)) {
1269             QString tag1 = (item->isElement()) ?item->name() :QString();
1270             QTextStream& out = files.getFile(tag1, i->name());
1271             writeAdderDefinition(item, i, out);
1272             done.insert(name);
1273         }
1274     }
1275 }
1276 
1277 /**
1278  * Write the definitions for member functions to add elements to other
1279  * element.
1280  */
1281 void writeAdderDefinitions(const RNGItemList& items, Files& files)
1282 {
1283     foreach (RNGItemPtr item, items) {
1284         writeAdderDefinitions(item, files);
1285     }
1286 }
1287 
1288 /**
1289  * Retrieve the namespace prefix from the tag name.
1290  * @param tag string with a tag, may be null.
1291  */
1292 QString getPrefix(const QString& tag)
1293 {
1294     QString prefix = tag.left(tag.indexOf(":"));
1295     if (prefix.isNull()) {
1296         prefix = "";
1297     }
1298     return prefix;
1299 }
1300 
1301 /**
1302  * Get the stream for the combination of the two tags.
1303  * For tag1 = "office:text" and tag2 = "text:p", this returns a stream to a file
1304  * "writeodfofficetext.h".
1305  */
1306 QTextStream& Files::getFile(const QString& tag1, const QString& tag2 = QString())
1307 {
1308     // each tag can result in either no prefix or a prefix
1309     // if one if the prefixes is empty and the other is not, then the first
1310     // prefix is given a value
1311     QString prefix = getPrefix(tag1);
1312     QString prefix2 = getPrefix(tag2);
1313     if (prefix.isEmpty() || prefix == prefix2) {
1314         prefix = prefix2;
1315         prefix2 = "";
1316     }
1317     if (files.contains(prefix) && files[prefix].contains(prefix2)) {
1318         return *files[prefix][prefix2];
1319     }
1320     QString name = "writeodf" + prefix + prefix2 + ".h";
1321     QString path = outdir + "/" + name;
1322     QFile* file = new QFile(path);
1323     if (!file->open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) {
1324         fatal() << file->errorString();
1325     }
1326     QTextStream* out = new QTextStream(file);
1327     name = name.replace(".", "_").toUpper();
1328 
1329     *out << "#ifndef " + name + "\n";
1330     *out << "#define " + name + "\n";
1331     if (name == "WRITEODF_H") {
1332         *out << "#include \"writeodf/odfwriter.h\"\n";
1333     } else {
1334         *out << "#include \"writeodf.h\"\n";
1335     }
1336     if (!prefix2.isEmpty() && prefix2 != prefix) {
1337         *out << "#include \"writeodf" + prefix + ".h\"\n";
1338         *out << "#include \"writeodf" + prefix2 + ".h\"\n";
1339     } else {
1340         *out << "namespace " << ns() << " {\n";
1341     }
1342     files[prefix][prefix2] = out;
1343     return *out;
1344 }
1345 
1346 /**
1347  * Close the namespace if it was opened previously.
1348  */
1349 void Files::closeNamespace()
1350 {
1351     typedef const QMap<QString,QTextStream*> map;
1352     foreach (map& m, files) {
1353         map::ConstIterator i = m.begin();
1354         while (i != m.end()) {
1355             if (!i.key().isNull() && !ns().isEmpty()) {
1356                 *i.value() << "}\n";
1357             }
1358             ++i;
1359         }
1360     }
1361 }
1362 
1363 /**
1364  * Write the header files.
1365  */
1366 void write(const RNGItemList& items, const QString &outdir)
1367 {
1368     Files files(outdir);
1369 
1370     QTextStream& out = files.getFile("", "");
1371     RNGItemList undefined = items;
1372     RNGItemList defined;
1373     addInOrder(undefined, defined);
1374     // declare all classes
1375     foreach (RNGItemPtr item, defined) {
1376         if (item->isElement() || (item->isDefine() && hasElementOrAttribute(item))) {
1377             out << "class " << item->cppName << ";\n";
1378         }
1379     }
1380     foreach (RNGItemPtr item, defined) {
1381         if (item->isElement()) {
1382             defineElement(files.getFile(item->name()), item);
1383         } else if (item->isDefine()) {
1384             defineGroup(out, item);
1385         }
1386     }
1387     files.closeNamespace();
1388     writeAdderDefinitions(defined, files);
1389 }
1390 
1391 /**
1392  * Convert the given rng file to a collection of header files.
1393  */
1394 void convert(const QString& rngfile, const QString& outdir)
1395 {
1396     QDomDocument dom = loadDOM(rngfile);
1397     RNGItems items;
1398     RNGItemPtr start = getDefines(dom.documentElement(), items);
1399     RNGItems collected;
1400     //qDebug() << "define " << items.size();
1401     //collect(items, collected);
1402     collected = items;
1403     //qDebug() << "collect " << collected.size();
1404     RNGItems resolved;
1405     resolveDefines(start, collected, resolved);
1406     //qDebug() << "resolve " << resolved.size();
1407     //while (expand(resolved)) {}
1408     resolved.remove(start);
1409     //qDebug() << "expand " << resolved.size();
1410     resolveAttributeDataTypes(resolved);
1411     while (reduce(resolved)) {}
1412     //qDebug() << "reduce " << resolved.size();
1413     RNGItemList list = toVector(resolved);
1414     //qDebug() << "filteredItems " << list.size();
1415     std::stable_sort(list.begin(), list.end(), rngItemPtrLessThan);
1416     makeCppNames(list);
1417     write(list, outdir);
1418     //qDebug() << list.size();
1419 }
1420 
1421 int main(int argc, char *argv[])
1422 {
1423     QString rngfile;
1424     QString outdir;
1425     if (argc != 3) {
1426         fatal() << "Usage " << argv[0] << " rngfile outputdir";
1427     } else {
1428         rngfile = argv[1];
1429         outdir = argv[2];
1430     }
1431     convert(rngfile, outdir);
1432     return 0;
1433 }