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 }