Warning, file /office/calligra/libs/store/KoXmlReader.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /* This file is part of the KDE project
0002    Copyright (C) 2005-2006 Ariya Hidayat <ariya@kde.org>
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 
0020 #include "KoXmlReader.h"
0021 #include "KoXmlNS.h"
0022 
0023 /*
0024   This is a memory-efficient DOM implementation for Calligra. See the API
0025   documentation for details.
0026 
0027   IMPORTANT !
0028 
0029   * When you change this stuff, make sure it DOES NOT BREAK the test suite.
0030     Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights
0031     have been sacrificed for this piece of code, do not let those precious
0032     hours wasted!
0033 
0034   * Run koxmlreadertest.cpp WITH Valgrind and make sure NO illegal
0035     memory read/write and any type of leak occurs. If you are not familiar
0036     with Valgrind then RTFM first and come back again later on.
0037 
0038   * The public API shall remain as compatible as QDom.
0039 
0040   * All QDom-compatible methods should behave the same. All QDom-compatible
0041     functions should return the same result. In case of doubt, run
0042     koxmlreadertest.cpp but uncomment KOXML_USE_QDOM in koxmlreader.h
0043     so that the tests are performed with standard QDom.
0044 
0045   Some differences compared to QDom:
0046 
0047   - DOM tree in KoXmlDocument is read-only, you can not modify it. This is
0048     sufficient for Calligra since the tree is only accessed when loading
0049     a document to the application. For saving the document to XML file,
0050     use KoXmlWriter.
0051 
0052   - Because the dynamic loading and unloading, you have to use the
0053     nodes (and therefore also elements) carefully since the whole API
0054  (just like QDom) is reference-based, not pointer-based. If the
0055  parent node is unloaded from memory, the reference is not valid
0056  anymore and may give unpredictable result.
0057  The easiest way: use the node/element in very short time only.
0058 
0059   - Comment node (like QDomComment) is not implemented as comments are
0060     simply ignored.
0061 
0062   - DTD, entity and entity reference are not handled. Thus, the associated
0063     nodes (like QDomDocumentType, QDomEntity, QDomEntityReference) are also
0064     not implemented.
0065 
0066   - Attribute mapping node is not implemented. But of course, functions to
0067     query attributes of an element are available.
0068 
0069 
0070  */
0071 
0072 #include <QTextCodec>
0073 #include <QTextDecoder>
0074 
0075 #ifndef KOXML_USE_QDOM
0076 
0077 #include <QtXml>
0078 #include <QDomDocument>
0079 #include <QXmlStreamReader>
0080 #include <QXmlStreamEntityResolver>
0081 
0082 #include <QBuffer>
0083 #include <QByteArray>
0084 #include <QDataStream>
0085 #include <QHash>
0086 #include <QPair>
0087 #include <QStringList>
0088 #include <QVector>
0089 
0090 /*
0091  Use more compact representation of in-memory nodes.
0092 
0093  Advantages: faster iteration, can facilitate real-time compression.
0094  Disadvantages: still buggy, eat slightly more memory.
0095 */
0096 #define KOXML_COMPACT
0097 
0098 /*
0099  Use real-time compression. Only works in conjuction with KOXML_COMPACT
0100  above because otherwise the non-compact layout will slow down everything.
0101 */
0102 #define KOXML_COMPRESS
0103 
0104 
0105 // prevent mistake, see above
0106 #ifdef KOXML_COMPRESS
0107 #ifndef KOXML_COMPACT
0108 #error Please enable also KOXML_COMPACT
0109 #endif
0110 #endif
0111 
0112 // this is used to quickly get namespaced attribute(s)
0113 typedef QPair<QString, QString> KoXmlStringPair;
0114 
0115 class KoQName {
0116 public:
0117     QString nsURI;
0118     QString name;
0119 
0120     explicit KoQName(const QString& nsURI_, const QString& name_)
0121         : nsURI(nsURI_), name(name_) {}
0122     bool operator==(const KoQName& qname) const {
0123         // local name is more likely to differ, so compare that first
0124         return name == qname.name && nsURI == qname.nsURI;
0125     }
0126 };
0127 
0128 uint qHash(const KoQName& qname)
0129 {
0130     // possibly add a faster hash function that only includes some trailing
0131     // part of the nsURI
0132 
0133     // in case of doubt, use this:
0134     // return qHash(qname.nsURI)^qHash(qname.name);
0135     return qHash(qname.nsURI)^qHash(qname.name);
0136 }
0137 
0138 // this simplistic hash is rather fast-and-furious. it works because
0139 // likely there is very few namespaced attributes per element
0140 static inline uint qHash(const KoXmlStringPair &p, uint /*seed*/ = 0)
0141 {
0142     return qHash(p.second[0].unicode()) ^ 0x1477;
0143 
0144     // in case of doubt, use this:
0145     // return qHash(p.first)^qHash(p.second);
0146 }
0147 
0148 static inline bool operator==(const KoXmlStringPair &a, const KoXmlStringPair &b)
0149 {
0150     return a.second == b.second && a.first == b.first;
0151 }
0152 
0153 // Older versions of OpenOffice.org used different namespaces. This function
0154 // does translate the old namespaces into the new ones.
0155 static QString fixNamespace(const QString &nsURI)
0156 {
0157     static QString office = QString::fromLatin1("http://openoffice.org/2000/office");
0158     static QString text = QString::fromLatin1("http://openoffice.org/2000/text");
0159     static QString style = QString::fromLatin1("http://openoffice.org/2000/style");
0160     static QString fo = QString::fromLatin1("http://www.w3.org/1999/XSL/Format");
0161     static QString table = QString::fromLatin1("http://openoffice.org/2000/table");
0162     static QString drawing = QString::fromLatin1("http://openoffice.org/2000/drawing");
0163     static QString datastyle = QString::fromLatin1("http://openoffice.org/2000/datastyle");
0164     static QString svg = QString::fromLatin1("http://www.w3.org/2000/svg");
0165     static QString chart = QString::fromLatin1("http://openoffice.org/2000/chart");
0166     static QString dr3d = QString::fromLatin1("http://openoffice.org/2000/dr3d");
0167     static QString form = QString::fromLatin1("http://openoffice.org/2000/form");
0168     static QString script = QString::fromLatin1("http://openoffice.org/2000/script");
0169     static QString meta = QString::fromLatin1("http://openoffice.org/2000/meta");
0170     static QString config = QString::fromLatin1("http://openoffice.org/2001/config");
0171     static QString pres = QString::fromLatin1("http://openoffice.org/2000/presentation");
0172     static QString manifest = QString::fromLatin1("http://openoffice.org/2001/manifest");
0173     if (nsURI == text)
0174         return KoXmlNS::text;
0175     if (nsURI == style)
0176         return KoXmlNS::style;
0177     if (nsURI == office)
0178         return KoXmlNS::office;
0179     if (nsURI == fo)
0180         return KoXmlNS::fo;
0181     if (nsURI == table)
0182         return KoXmlNS::table;
0183     if (nsURI == drawing)
0184         return KoXmlNS::draw;
0185     if (nsURI == datastyle)
0186         return KoXmlNS::number;
0187     if (nsURI == svg)
0188         return KoXmlNS::svg;
0189     if (nsURI == chart)
0190         return KoXmlNS::chart;
0191     if (nsURI == dr3d)
0192         return KoXmlNS::dr3d;
0193     if (nsURI == form)
0194         return KoXmlNS::form;
0195     if (nsURI == script)
0196         return KoXmlNS::script;
0197     if (nsURI == meta)
0198         return KoXmlNS::meta;
0199     if (nsURI == config)
0200         return KoXmlNS::config;
0201     if (nsURI == pres)
0202         return KoXmlNS::presentation;
0203     if (nsURI == manifest)
0204         return KoXmlNS::manifest;
0205     return nsURI;
0206 }
0207 
0208 // ==================================================================
0209 //
0210 //         KoXmlPackedItem
0211 //
0212 // ==================================================================
0213 
0214 // 12 bytes on most system 32 bit systems, 16 bytes on 64 bit systems
0215 class KoXmlPackedItem
0216 {
0217 public:
0218 bool attr: 1;
0219 KoXmlNode::NodeType type: 3;
0220 
0221 #ifdef KOXML_COMPACT
0222 quint32 childStart: 28;
0223 #else
0224 unsigned depth: 28;
0225 #endif
0226 
0227     unsigned qnameIndex;
0228     QString value;
0229 
0230     // it is important NOT to have a copy constructor, so that growth is optimal
0231     // see https://doc.qt.io/qt-5/containers.html#growth-strategies
0232 #if 0
0233     KoXmlPackedItem(): attr(false), type(KoXmlNode::NullNode), childStart(0), depth(0) {}
0234 #endif
0235 };
0236 
0237 Q_DECLARE_TYPEINFO(KoXmlPackedItem, Q_MOVABLE_TYPE);
0238 
0239 #ifdef KOXML_COMPRESS
0240 static QDataStream& operator<<(QDataStream& s, const KoXmlPackedItem& item)
0241 {
0242     quint8 flag = item.attr ? 1 : 0;
0243 
0244     s << flag;
0245     s << (quint8) item.type;
0246     s << item.childStart;
0247     s << item.qnameIndex;
0248     s << item.value;
0249 
0250     return s;
0251 }
0252 
0253 static QDataStream& operator>>(QDataStream& s, KoXmlPackedItem& item)
0254 {
0255     quint8 flag;
0256     quint8 type;
0257     quint32 child;
0258     QString value;
0259 
0260     s >> flag;
0261     s >> type;
0262     s >> child;
0263     s >> item.qnameIndex;
0264     s >> value;
0265 
0266     item.attr = (flag != 0);
0267     item.type = (KoXmlNode::NodeType) type;
0268     item.childStart = child;
0269     item.value = value;
0270 
0271     return s;
0272 }
0273 #endif
0274 
0275 // ==================================================================
0276 //
0277 //         KoXmlPackedDocument
0278 //
0279 // ==================================================================
0280 
0281 #ifdef KOXML_COMPRESS
0282 
0283 #include "KoXmlVector.h"
0284 
0285 // when number of buffered items reach this, compression will start
0286 // small value will give better memory usage at the cost of speed
0287 // bigger value will be better in term of speed, but use more memory
0288 #define ITEMS_FULL  (1*256)
0289 
0290 typedef KoXmlVector<KoXmlPackedItem, ITEMS_FULL> KoXmlPackedGroup;
0291 #else
0292 typedef QVector<KoXmlPackedItem> KoXmlPackedGroup;
0293 #endif
0294 
0295 // growth strategy: increase every GROUP_GROW_SIZE items
0296 // this will override standard QVector's growth strategy
0297 #define GROUP_GROW_SHIFT 3
0298 #define GROUP_GROW_SIZE (1 << GROUP_GROW_SHIFT)
0299 
0300 class KoXmlPackedDocument
0301 {
0302 public:
0303     bool processNamespace;
0304 #ifdef KOXML_COMPACT
0305     // map given depth to the list of items
0306     QHash<int, KoXmlPackedGroup> groups;
0307 #else
0308     QVector<KoXmlPackedItem> items;
0309 #endif
0310 
0311     QList<KoQName> qnameList;
0312     QString docType;
0313 
0314 private:
0315     QHash<KoQName, unsigned> qnameHash;
0316 
0317     unsigned cacheQName(const QString& name, const QString& nsURI) {
0318         KoQName qname(nsURI, name);
0319 
0320         const unsigned ii = qnameHash.value(qname, (unsigned)-1);
0321         if (ii != (unsigned)-1)
0322             return ii;
0323 
0324         // not yet declared, so we add it
0325         unsigned i = qnameList.count();
0326         qnameList.append(qname);
0327         qnameHash.insert(qname, i);
0328 
0329         return i;
0330     }
0331 
0332     QHash<QString, unsigned> valueHash;
0333     QStringList valueList;
0334 
0335     QString cacheValue(const QString& value) {
0336         if (value.isEmpty())
0337             return 0;
0338 
0339         const unsigned& ii = valueHash[value];
0340         if (ii > 0)
0341             return valueList[ii];
0342 
0343         // not yet declared, so we add it
0344         unsigned i = valueList.count();
0345         valueList.append(value);
0346         valueHash.insert(value, i);
0347 
0348         return valueList[i];
0349     }
0350 
0351 #ifdef KOXML_COMPACT
0352 public:
0353     const KoXmlPackedItem& itemAt(unsigned depth, unsigned index) {
0354         const KoXmlPackedGroup& group = groups[depth];
0355         return group[index];
0356     }
0357 
0358     unsigned itemCount(unsigned depth) {
0359         const KoXmlPackedGroup& group = groups[depth];
0360         return group.count();
0361     }
0362 
0363     /*
0364        NOTE:
0365           Function clear, newItem, addElement, addAttribute, addText,
0366           addCData, addProcessing are all related. These are all necessary
0367           for stateful manipulation of the document. See also the calls
0368           to these function from parseDocument().
0369 
0370           The state itself is defined by the member variables
0371           currentDepth and the groups (see above).
0372      */
0373 
0374     unsigned currentDepth;
0375 
0376     KoXmlPackedItem& newItem(unsigned depth) {
0377         KoXmlPackedGroup& group = groups[depth];
0378 
0379 #ifdef KOXML_COMPRESS
0380         KoXmlPackedItem& item = group.newItem();
0381 #else
0382         // reserve up front
0383         if ((groups.size() % GROUP_GROW_SIZE) == 0)
0384             group.reserve(GROUP_GROW_SIZE * (1 + (groups.size() >> GROUP_GROW_SHIFT)));
0385         group.resize(group.count() + 1);
0386 
0387         KoXmlPackedItem& item = group[group.count()-1];
0388 #endif
0389 
0390         // this is necessary, because intentionally we don't want to have
0391         // a constructor for KoXmlPackedItem
0392         item.attr = false;
0393         item.type = KoXmlNode::NullNode;
0394         item.qnameIndex = 0;
0395         item.childStart = itemCount(depth + 1);
0396         item.value.clear();
0397 
0398         return item;
0399     }
0400 
0401     void clear() {
0402         currentDepth = 0;
0403         qnameHash.clear();
0404         qnameList.clear();
0405         valueHash.clear();
0406         valueList.clear();
0407         groups.clear();
0408         docType.clear();
0409 
0410         // first node is root
0411         KoXmlPackedItem& rootItem = newItem(0);
0412         rootItem.type = KoXmlNode::DocumentNode;
0413     }
0414 
0415     void finish() {
0416         // won't be needed anymore
0417         qnameHash.clear();
0418         valueHash.clear();
0419         valueList.clear();
0420 
0421         // optimize, see documentation on QVector::squeeze
0422         for (int d = 0; d < groups.count(); ++d) {
0423             KoXmlPackedGroup& group = groups[d];
0424             group.squeeze();
0425         }
0426     }
0427 
0428     // in case namespace processing, 'name' contains the prefix already
0429     void addElement(const QString& name, const QString& nsURI) {
0430         KoXmlPackedItem& item = newItem(currentDepth + 1);
0431         item.type = KoXmlNode::ElementNode;
0432         item.qnameIndex = cacheQName(name, nsURI);
0433 
0434         ++currentDepth;
0435     }
0436 
0437     void closeElement() {
0438         --currentDepth;
0439     }
0440 
0441     void addDTD(const QString& dt) {
0442         docType = dt;
0443     }
0444 
0445     void addAttribute(const QString& name, const QString& nsURI, const QString& value) {
0446         KoXmlPackedItem& item = newItem(currentDepth + 1);
0447         item.attr = true;
0448         item.qnameIndex = cacheQName(name, nsURI);
0449         //item.value = cacheValue( value );
0450         item.value = value;
0451     }
0452 
0453     void addText(const QString& text) {
0454         KoXmlPackedItem& item = newItem(currentDepth + 1);
0455         item.type = KoXmlNode::TextNode;
0456         item.value = text;
0457     }
0458 
0459     void addCData(const QString& text) {
0460         KoXmlPackedItem& item = newItem(currentDepth + 1);
0461         item.type = KoXmlNode::CDATASectionNode;
0462         item.value = text;
0463     }
0464 
0465     void addProcessingInstruction() {
0466         KoXmlPackedItem& item = newItem(currentDepth + 1);
0467         item.type = KoXmlNode::ProcessingInstructionNode;
0468     }
0469 
0470 public:
0471     KoXmlPackedDocument(): processNamespace(false), currentDepth(0) {
0472         clear();
0473     }
0474 
0475 #else
0476 
0477 private:
0478     unsigned elementDepth;
0479 
0480 public:
0481 
0482     KoXmlPackedItem& newItem() {
0483         unsigned count = items.count() + 512;
0484         count = 1024 * (count >> 10);
0485         items.reserve(count);
0486 
0487         items.resize(items.count() + 1);
0488 
0489         // this is necessary, because intentionally we don't want to have
0490         // a constructor for KoXmlPackedItem
0491         KoXmlPackedItem& item = items[items.count()-1];
0492         item.attr = false;
0493         item.type = KoXmlNode::NullNode;
0494         item.qnameIndex = 0;
0495         item.depth = 0;
0496 
0497         return item;
0498     }
0499 
0500     void addElement(const QString& name, const QString& nsURI) {
0501         // we are going one level deeper
0502         ++elementDepth;
0503 
0504         KoXmlPackedItem& item = newItem();
0505 
0506         item.attr = false;
0507         item.type = KoXmlNode::ElementNode;
0508         item.depth = elementDepth;
0509         item.qnameIndex = cacheQName(name, nsURI);
0510     }
0511 
0512     void closeElement() {
0513         // we are going up one level
0514         --elementDepth;
0515     }
0516 
0517     void addDTD(const QString& dt) {
0518         docType = dt;
0519     }
0520 
0521     void addAttribute(const QString& name, const QString& nsURI, const QString& value) {
0522         KoXmlPackedItem& item = newItem();
0523 
0524         item.attr = true;
0525         item.type = KoXmlNode::NullNode;
0526         item.depth = elementDepth;
0527         item.qnameIndex = cacheQName(name, nsURI);
0528         //item.value = cacheValue( value );
0529         item.value = value;
0530     }
0531 
0532     void addText(const QString& str) {
0533         KoXmlPackedItem& item = newItem();
0534 
0535         item.attr = false;
0536         item.type = KoXmlNode::TextNode;
0537         item.depth = elementDepth + 1;
0538         item.qnameIndex = 0;
0539         item.value = str;
0540     }
0541 
0542     void addCData(const QString& str) {
0543         KoXmlPackedItem& item = newItem();
0544 
0545         item.attr = false;
0546         item.type = KoXmlNode::CDATASectionNode;
0547         item.depth = elementDepth + 1;
0548         item.qnameIndex = 0;
0549         item.value = str;
0550     }
0551 
0552     void addProcessingInstruction() {
0553         KoXmlPackedItem& item = newItem();
0554 
0555         item.attr = false;
0556         item.type = KoXmlNode::ProcessingInstructionNode;
0557         item.depth = elementDepth + 1;
0558         item.qnameIndex = 0;
0559         item.value.clear();
0560     }
0561 
0562     void clear() {
0563         qnameHash.clear();
0564         qnameList.clear();
0565         valueHash.clear();
0566         valueList.clear();
0567         items.clear();
0568         elementDepth = 0;
0569 
0570         KoXmlPackedItem& rootItem = newItem();
0571         rootItem.attr = false;
0572         rootItem.type = KoXmlNode::DocumentNode;
0573         rootItem.depth = 0;
0574         rootItem.qnameIndex = 0;
0575     }
0576 
0577     void finish() {
0578         qnameHash.clear();
0579         valueList.clear();
0580         valueHash.clear();
0581         items.squeeze();
0582     }
0583 
0584     KoXmlPackedDocument(): processNamespace(false), elementDepth(0) {
0585     }
0586 
0587 #endif
0588 
0589 };
0590 
0591 namespace {
0592 
0593     class ParseError {
0594     public:
0595         QString errorMsg;
0596         int errorLine;
0597         int errorColumn;
0598         bool error;
0599 
0600         ParseError() :errorLine(-1), errorColumn(-1), error(false) {}
0601     };
0602 
0603     void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true);
0604 
0605     // parse one element as if this were a standalone xml document
0606     ParseError parseDocument(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true)
0607     {
0608         doc.clear();
0609         ParseError error;
0610         xml.readNext();
0611         while (!xml.atEnd() && xml.tokenType() != QXmlStreamReader::EndDocument && !xml.hasError()) {
0612             switch (xml.tokenType()) {
0613             case QXmlStreamReader::StartElement:
0614                 parseElement(xml, doc, stripSpaces);
0615                 break;
0616             case QXmlStreamReader::DTD:
0617                 doc.addDTD(xml.dtdName().toString());
0618                 break;
0619             case QXmlStreamReader::StartDocument:
0620                 if (!xml.documentEncoding().isEmpty() || !xml.documentVersion().isEmpty()) {
0621                     doc.addProcessingInstruction();
0622                 }
0623                 break;
0624             case QXmlStreamReader::ProcessingInstruction:
0625                 doc.addProcessingInstruction();
0626                 break;
0627             default:
0628                 break;
0629             }
0630             xml.readNext();
0631         }
0632         if (xml.hasError()) {
0633             error.error = true;
0634             error.errorMsg = xml.errorString();
0635             error.errorColumn = xml.columnNumber();
0636             error.errorLine = xml.lineNumber();
0637         } else {
0638             doc.finish();
0639         }
0640         return error;
0641     }
0642 
0643     void parseElementContents(QXmlStreamReader &xml, KoXmlPackedDocument &doc)
0644     {
0645         xml.readNext();
0646         QString ws;
0647         while (!xml.atEnd()) {
0648             switch (xml.tokenType()) {
0649             case QXmlStreamReader::EndElement:
0650                 // if an element contains only whitespace, put it in the dom
0651                 if (!ws.isEmpty()) {
0652                     doc.addText(ws);
0653                 }
0654                 return;
0655             case QXmlStreamReader::StartElement:
0656                 // The whitespaces between > and < are also a text element
0657                 if (!ws.isEmpty()) {
0658                     doc.addText(ws);
0659                     ws.clear();
0660                 }
0661                 // Do not strip spaces
0662                 parseElement(xml, doc, false);
0663                 break;
0664             case QXmlStreamReader::Characters:
0665                 if (xml.isCDATA()) {
0666                     doc.addCData(xml.text().toString());
0667                 } else if (!xml.isWhitespace()) {
0668                     doc.addText(xml.text().toString());
0669                 } else {
0670                     ws += xml.text();
0671                 }
0672                 break;
0673             case QXmlStreamReader::ProcessingInstruction:
0674                 doc.addProcessingInstruction();
0675                 break;
0676             default:
0677                 break;
0678             }
0679             xml.readNext();
0680         }
0681     }
0682 
0683     void parseElementContentsStripSpaces(QXmlStreamReader &xml, KoXmlPackedDocument &doc)
0684     {
0685         xml.readNext();
0686         QString ws;
0687         bool sawElement = false;
0688         while (!xml.atEnd()) {
0689             switch (xml.tokenType()) {
0690             case QXmlStreamReader::EndElement:
0691                 // if an element contains only whitespace, put it in the dom
0692                 if (!ws.isEmpty() && !sawElement) {
0693                     doc.addText(ws);
0694                 }
0695                 return;
0696             case QXmlStreamReader::StartElement:
0697                 sawElement = true;
0698                 // Do strip spaces
0699                 parseElement(xml, doc, true);
0700                 break;
0701             case QXmlStreamReader::Characters:
0702                 if (xml.isCDATA()) {
0703                     doc.addCData(xml.text().toString());
0704                 } else if (!xml.isWhitespace()) {
0705                     doc.addText(xml.text().toString());
0706                 } else if (!sawElement) {
0707                     ws += xml.text();
0708                 }
0709                 break;
0710             case QXmlStreamReader::ProcessingInstruction:
0711                 doc.addProcessingInstruction();
0712                 break;
0713             default:
0714                 break;
0715             }
0716             xml.readNext();
0717         }
0718     }
0719 
0720     void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces)
0721     {
0722         // Unfortunately MSVC fails using QXmlStreamReader::const_iterator
0723         // so we apply a for loop instead. https://bugreports.qt.io/browse/QTBUG-45368
0724         doc.addElement(xml.qualifiedName().toString(),
0725                        fixNamespace(xml.namespaceUri().toString()));
0726         QXmlStreamAttributes attr = xml.attributes();
0727         for  (int a = 0; a < attr.count(); a++) {
0728             doc.addAttribute(attr[a].qualifiedName().toString(),
0729                              attr[a].namespaceUri().toString(),
0730                              attr[a].value().toString());
0731         }
0732         if (stripSpaces)
0733           parseElementContentsStripSpaces(xml, doc);
0734         else
0735           parseElementContents(xml, doc);
0736         // reader.tokenType() is now QXmlStreamReader::EndElement
0737         doc.closeElement();
0738     }
0739 }
0740 
0741 
0742 // ==================================================================
0743 //
0744 //         KoXmlNodeData
0745 //
0746 // ==================================================================
0747 
0748 class KoXmlNodeData
0749 {
0750 public:
0751 
0752     explicit KoXmlNodeData(unsigned long initialRefCount = 1);
0753     ~KoXmlNodeData();
0754 
0755     // generic properties
0756     KoXmlNode::NodeType nodeType;
0757     bool loaded;
0758 
0759 #ifdef KOXML_COMPACT
0760     unsigned nodeDepth;
0761 #endif
0762 
0763     QString tagName;
0764     QString namespaceURI;
0765     QString prefix;
0766     QString localName;
0767 
0768     void ref() {
0769         ++refCount;
0770     }
0771     void unref() {
0772         if (!--refCount) {
0773             delete this;
0774         }
0775     }
0776 
0777     // type information
0778     QString nodeName() const;
0779 
0780     // for tree and linked-list
0781     KoXmlNodeData* parent;
0782     KoXmlNodeData* prev;
0783     KoXmlNodeData* next;
0784     KoXmlNodeData* first;
0785     KoXmlNodeData* last;
0786 
0787     QString text();
0788 
0789     // node manipulation
0790     void clear();
0791 
0792     // attributes
0793     inline void setAttribute(const QString& name, const QString& value);
0794     inline QString attribute(const QString& name, const QString& def) const;
0795     inline bool hasAttribute(const QString& name) const;
0796     inline void setAttributeNS(const QString& nsURI, const QString& name, const QString& value);
0797     inline QString attributeNS(const QString& nsURI, const QString& name, const QString& def) const;
0798     inline bool hasAttributeNS(const QString& nsURI, const QString& name) const;
0799     inline void clearAttributes();
0800     inline QStringList attributeNames() const;
0801     inline QList< QPair<QString, QString> > attributeFullNames() const;
0802 
0803 
0804     // for text and CDATA
0805     QString data() const;
0806 
0807     // reference from within the packed doc
0808     KoXmlPackedDocument* packedDoc;
0809     unsigned long nodeIndex;
0810 
0811     // used when doing on-demand (re)parse
0812     void loadChildren(int depth = 1);
0813     void unloadChildren();
0814 
0815     void dump();
0816 
0817     static KoXmlNodeData null;
0818 
0819     // compatibility
0820     void asQDomNode(QDomDocument& ownerDoc) const;
0821 
0822 private:
0823     QHash<QString, QString> attr;
0824     QHash<KoXmlStringPair, QString> attrNS;
0825     QString textData;
0826     // reference counting
0827     unsigned long refCount;
0828     friend class KoXmlElement;
0829 };
0830 
0831 KoXmlNodeData KoXmlNodeData::null;
0832 
0833 
0834 KoXmlNodeData::KoXmlNodeData(unsigned long initialRefCount)
0835     : nodeType(KoXmlNode::NullNode)
0836     , loaded(false)
0837 #ifdef KOXML_COMPACT
0838     , nodeDepth(0)
0839 #endif
0840     , parent(0), prev(0), next(0), first(0), last(0)
0841     , packedDoc(0), nodeIndex(0)
0842     , refCount(initialRefCount)
0843 {
0844 }
0845 
0846 KoXmlNodeData::~KoXmlNodeData()
0847 {
0848     clear();
0849 }
0850 
0851 void KoXmlNodeData::clear()
0852 {
0853     if (first)
0854         for (KoXmlNodeData* node = first; node ;) {
0855             KoXmlNodeData* next = node->next;
0856             node->unref();
0857             node = next;
0858         }
0859 
0860     // only document can delete these
0861     // normal nodes don't "own" them
0862     if (nodeType == KoXmlNode::DocumentNode)
0863         delete packedDoc;
0864 
0865     nodeType = KoXmlNode::NullNode;
0866     tagName.clear();
0867     prefix.clear();
0868     namespaceURI.clear();
0869     textData.clear();
0870     packedDoc = 0;
0871 
0872     attr.clear();
0873     attrNS.clear();
0874 
0875     parent = 0;
0876     prev = next = 0;
0877     first = last = 0;
0878 
0879     loaded = false;
0880 }
0881 
0882 QString KoXmlNodeData::text()
0883 {
0884     QString t;
0885 
0886     loadChildren();
0887 
0888     KoXmlNodeData* node = first;
0889     while (node) {
0890         switch (node->nodeType) {
0891         case KoXmlNode::ElementNode:
0892             t += node->text(); break;
0893         case KoXmlNode::TextNode:
0894             t += node->data(); break;
0895         case KoXmlNode::CDATASectionNode:
0896             t += node->data(); break;
0897         default: break;
0898         }
0899         node = node->next;
0900     }
0901 
0902     return t;
0903 }
0904 
0905 QString KoXmlNodeData::nodeName() const
0906 {
0907     switch (nodeType) {
0908     case KoXmlNode::ElementNode: {
0909         QString n(tagName);
0910         if (!prefix.isEmpty())
0911             n.prepend(':').prepend(prefix);
0912         return n;
0913     }
0914     break;
0915 
0916     case KoXmlNode::TextNode:         return QLatin1String("#text");
0917     case KoXmlNode::CDATASectionNode: return QLatin1String("#cdata-section");
0918     case KoXmlNode::DocumentNode:     return QLatin1String("#document");
0919     case KoXmlNode::DocumentTypeNode: return tagName;
0920 
0921     default: return QString(); break;
0922     }
0923 
0924     // should not happen
0925     return QString();
0926 }
0927 
0928 void KoXmlNodeData::setAttribute(const QString& name, const QString& value)
0929 {
0930     attr.insert(name, value);
0931 }
0932 
0933 QString KoXmlNodeData::attribute(const QString& name, const QString& def) const
0934 {
0935     return attr.value(name, def);
0936 }
0937 
0938 bool KoXmlNodeData::hasAttribute(const QString& name) const
0939 {
0940     return attr.contains(name);
0941 }
0942 
0943 void KoXmlNodeData::setAttributeNS(const QString& nsURI,
0944                                    const QString& name, const QString& value)
0945 {
0946     int i = name.indexOf(':');
0947     if (i != -1) {
0948         QString localName(name.mid(i + 1));
0949         KoXmlStringPair key(nsURI, localName);
0950         attrNS.insert(key, value);
0951     }
0952 }
0953 
0954 QString KoXmlNodeData::attributeNS(const QString& nsURI, const QString& name,
0955                                    const QString& def) const
0956 {
0957     KoXmlStringPair key(nsURI, name);
0958     return attrNS.value(key, def);
0959 }
0960 
0961 bool KoXmlNodeData::hasAttributeNS(const QString& nsURI, const QString& name) const
0962 {
0963     KoXmlStringPair key(nsURI, name);
0964     return attrNS.contains(key);
0965 }
0966 
0967 void KoXmlNodeData::clearAttributes()
0968 {
0969     attr.clear();
0970     attrNS.clear();
0971 }
0972 
0973 // FIXME how about namespaced attributes ?
0974 QStringList KoXmlNodeData::attributeNames() const
0975 {
0976     QStringList result;
0977     result = attr.keys();
0978 
0979     return result;
0980 }
0981 
0982 
0983 QList< QPair<QString, QString> > KoXmlNodeData::attributeFullNames() const
0984 {
0985     QList< QPair<QString, QString> > result;
0986     result = attrNS.keys();
0987 
0988     return result;
0989 }
0990 
0991 QString KoXmlNodeData::data() const
0992 {
0993     return textData;
0994 }
0995 
0996 #ifdef KOXML_COMPACT
0997 
0998 void KoXmlNodeData::loadChildren(int depth)
0999 {
1000     // sanity check
1001     if (!packedDoc) return;
1002 
1003     // already loaded ?
1004     if (loaded && (depth <= 1)) return;
1005 
1006     // in case depth is different
1007     unloadChildren();
1008 
1009 
1010     KoXmlNodeData* lastDat = 0;
1011 
1012     unsigned childStop = 0;
1013     if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1)
1014         childStop = packedDoc->itemCount(nodeDepth + 1);
1015     else {
1016         const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1);
1017         childStop = next.childStart;
1018     }
1019 
1020     const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex);
1021 
1022     for (unsigned i = self.childStart; i < childStop; ++i) {
1023         const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i);
1024         bool textItem = (item.type == KoXmlNode::TextNode);
1025         textItem |= (item.type == KoXmlNode::CDATASectionNode);
1026 
1027         // attribute belongs to this node
1028         if (item.attr) {
1029             KoQName qname = packedDoc->qnameList[item.qnameIndex];
1030             QString value = item.value;
1031 
1032             QString prefix;
1033 
1034             QString qName; // with prefix
1035             QString localName;  // without prefix, i.e. local name
1036 
1037             localName = qName = qname.name;
1038             int i = qName.indexOf(':');
1039             if (i != -1) prefix = qName.left(i);
1040             if (i != -1) localName = qName.mid(i + 1);
1041 
1042             if (packedDoc->processNamespace) {
1043                 setAttributeNS(qname.nsURI, qName, value);
1044                 setAttribute(localName, value);
1045             } else
1046                 setAttribute(qName, value);
1047         } else {
1048             KoQName qname = packedDoc->qnameList[item.qnameIndex];
1049             QString value = item.value;
1050 
1051             QString nodeName = qname.name;
1052             QString localName;
1053             QString prefix;
1054 
1055             if (packedDoc->processNamespace) {
1056                 localName = qname.name;
1057                 int di = qname.name.indexOf(':');
1058                 if (di != -1) {
1059                     localName = qname.name.mid(di + 1);
1060                     prefix = qname.name.left(di);
1061                 }
1062                 nodeName = localName;
1063             }
1064 
1065             // make a node out of this item
1066             KoXmlNodeData* dat = new KoXmlNodeData;
1067             dat->nodeIndex = i;
1068             dat->packedDoc = packedDoc;
1069             dat->nodeDepth = nodeDepth + 1;
1070             dat->nodeType = item.type;
1071             dat->tagName = nodeName;
1072             dat->localName = localName;
1073             dat->prefix = prefix;
1074             dat->namespaceURI = qname.nsURI;
1075             dat->parent = this;
1076             dat->prev = lastDat;
1077             dat->next = 0;
1078             dat->first = 0;
1079             dat->last = 0;
1080             dat->loaded = false;
1081             dat->textData = (textItem) ? value : QString();
1082 
1083             // adjust our linked-list
1084             first = (first) ? first : dat;
1085             last = dat;
1086             if (lastDat)
1087                 lastDat->next = dat;
1088             lastDat = dat;
1089 
1090             // recursive
1091             if (depth > 1)
1092                 dat->loadChildren(depth - 1);
1093         }
1094     }
1095 
1096     loaded = true;
1097 }
1098 
1099 #else
1100 
1101 void KoXmlNodeData::loadChildren(int depth)
1102 {
1103     // sanity check
1104     if (!packedDoc) return;
1105 
1106     // already loaded ?
1107     if (loaded && (depth <= 1)) return;
1108 
1109     // cause we don't know how deep this node's children already loaded are
1110     unloadChildren();
1111 
1112     KoXmlNodeData* lastDat = 0;
1113     int nodeDepth = packedDoc->items[nodeIndex].depth;
1114 
1115     for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) {
1116         KoXmlPackedItem& item = packedDoc->items[i];
1117         bool textItem = (item.type == KoXmlNode::TextNode);
1118         textItem |= (item.type == KoXmlNode::CDATASectionNode);
1119 
1120         // element already outside our depth
1121         if (!item.attr && (item.type == KoXmlNode::ElementNode))
1122             if (item.depth <= (unsigned)nodeDepth)
1123                 break;
1124 
1125         // attribute belongs to this node
1126         if (item.attr && (item.depth == (unsigned)nodeDepth)) {
1127             KoQName qname = packedDoc->qnameList[item.qnameIndex];
1128             QString value = item.value;
1129 
1130             QString prefix;
1131 
1132             QString qName; // with prefix
1133             QString localName;  // without prefix, i.e. local name
1134 
1135             localName = qName = qname.name;
1136             int i = qName.indexOf(':');
1137             if (i != -1) prefix = qName.left(i);
1138             if (i != -1) localName = qName.mid(i + 1);
1139 
1140             if (packedDoc->processNamespace) {
1141                 setAttributeNS(qname.nsURI, qName, value);
1142                 setAttribute(localName, value);
1143             } else
1144                 setAttribute(qname.name, value);
1145         }
1146 
1147         // the child node
1148         if (!item.attr) {
1149             bool instruction = (item.type == KoXmlNode::ProcessingInstructionNode);
1150             bool ok = (textItem || instruction)  ? (item.depth == (unsigned)nodeDepth) : (item.depth == (unsigned)nodeDepth + 1);
1151 
1152             ok = (item.depth == (unsigned)nodeDepth + 1);
1153 
1154             if (ok) {
1155                 KoQName qname = packedDoc->qnameList[item.qnameIndex];
1156                 QString value = item.value;
1157 
1158                 QString nodeName = qname.name;
1159                 QString localName;
1160                 QString prefix;
1161 
1162                 if (packedDoc->processNamespace) {
1163                     localName = qname.name;
1164                     int di = qname.name.indexOf(':');
1165                     if (di != -1) {
1166                         localName = qname.name.mid(di + 1);
1167                         prefix = qname.name.left(di);
1168                     }
1169                     nodeName = localName;
1170                 }
1171 
1172                 // make a node out of this item
1173                 KoXmlNodeData* dat = new KoXmlNodeData;
1174                 dat->nodeIndex = i;
1175                 dat->packedDoc = packedDoc;
1176                 dat->nodeType = item.type;
1177                 dat->tagName = nodeName;
1178                 dat->localName = localName;
1179                 dat->prefix = prefix;
1180                 dat->namespaceURI = qname.nsURI;
1181                 dat->count = 1;
1182                 dat->parent = this;
1183                 dat->prev = lastDat;
1184                 dat->next = 0;
1185                 dat->first = 0;
1186                 dat->last = 0;
1187                 dat->loaded = false;
1188                 dat->textData = (textItem) ? value : QString();
1189 
1190                 // adjust our linked-list
1191                 first = (first) ? first : dat;
1192                 last = dat;
1193                 if (lastDat)
1194                     lastDat->next = dat;
1195                 lastDat = dat;
1196 
1197                 // recursive
1198                 if (depth > 1)
1199                     dat->loadChildren(depth - 1);
1200             }
1201         }
1202     }
1203 
1204     loaded = true;
1205 }
1206 #endif
1207 
1208 void KoXmlNodeData::unloadChildren()
1209 {
1210     // sanity check
1211     if (!packedDoc) return;
1212 
1213     if (!loaded) return;
1214 
1215     if (first)
1216         for (KoXmlNodeData* node = first; node ;) {
1217             KoXmlNodeData* next = node->next;
1218             node->unloadChildren();
1219             node->unref();
1220             node = next;
1221         }
1222 
1223     clearAttributes();
1224     loaded = false;
1225     first = last = 0;
1226 }
1227 
1228 #ifdef KOXML_COMPACT
1229 
1230 
1231 static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc,
1232                                unsigned nodeDepth, unsigned nodeIndex, QDomNode parentNode = QDomNode())
1233 {
1234     // sanity check
1235     if (!packedDoc)
1236         return;
1237 
1238     const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex);
1239 
1240     unsigned childStop = 0;
1241     if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1)
1242         childStop = packedDoc->itemCount(nodeDepth + 1);
1243     else {
1244         const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1);
1245         childStop = next.childStart;
1246     }
1247 
1248     // nothing to do here
1249     if (self.type == KoXmlNode::NullNode)
1250         return;
1251 
1252     // create the element properly
1253     if (self.type == KoXmlNode::ElementNode) {
1254         QDomElement element;
1255 
1256         KoQName qname = packedDoc->qnameList[self.qnameIndex];
1257         qname.nsURI = fixNamespace(qname.nsURI);
1258 
1259         if (packedDoc->processNamespace)
1260             element = ownerDoc.createElementNS(qname.nsURI, qname.name);
1261         else
1262             element = ownerDoc.createElement(qname.name);
1263 
1264         if ( parentNode.isNull() ) {
1265             ownerDoc.appendChild( element );
1266         } else {
1267             parentNode.appendChild( element );
1268         }
1269         // check all subnodes for attributes
1270         for (unsigned i = self.childStart; i < childStop; ++i) {
1271             const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i);
1272             bool textItem = (item.type == KoXmlNode::TextNode);
1273             textItem |= (item.type == KoXmlNode::CDATASectionNode);
1274 
1275             // attribute belongs to this node
1276             if (item.attr) {
1277                 KoQName qname = packedDoc->qnameList[item.qnameIndex];
1278                 qname.nsURI = fixNamespace(qname.nsURI );
1279                 QString value = item.value;
1280 
1281                 QString prefix;
1282 
1283                 QString qName; // with prefix
1284                 QString localName;  // without prefix, i.e. local name
1285 
1286                 localName = qName = qname.name;
1287                 int i = qName.indexOf(':');
1288                 if (i != -1) prefix = qName.left(i);
1289                 if (i != -1) localName = qName.mid(i + 1);
1290 
1291                 if (packedDoc->processNamespace) {
1292                     element.setAttributeNS(qname.nsURI, qName, value);
1293                     element.setAttribute(localName, value);
1294                 } else
1295                     element.setAttribute(qname.name, value);
1296             } else {
1297                 // add it recursively
1298                 itemAsQDomNode(ownerDoc, packedDoc, nodeDepth + 1, i, element);
1299             }
1300         }
1301         return;
1302     }
1303 
1304     // create the text node
1305     if (self.type == KoXmlNode::TextNode) {
1306         QString text = self.value;
1307 
1308         // FIXME: choose CDATA when the value contains special characters
1309         QDomText textNode = ownerDoc.createTextNode(text);
1310         if ( parentNode.isNull() ) {
1311             ownerDoc.appendChild( textNode );
1312         } else {
1313             parentNode.appendChild( textNode );
1314         }
1315         return;
1316     }
1317     // nothing matches? strange...
1318 }
1319 
1320 void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const
1321 {
1322     itemAsQDomNode(ownerDoc, packedDoc, nodeDepth, nodeIndex);
1323 }
1324 
1325 #else
1326 
1327 static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc,
1328                                unsigned nodeIndex, QDomNode parentNode = QDomNode())
1329 {
1330     // sanity check
1331     if (!packedDoc)
1332         return;
1333 
1334     KoXmlPackedItem& item = packedDoc->items[nodeIndex];
1335 
1336     // nothing to do here
1337     if (item.type == KoXmlNode::NullNode)
1338         return;
1339 
1340     // create the element properly
1341     if (item.type == KoXmlNode::ElementNode) {
1342         QDomElement element;
1343 
1344         KoQName qname = packedDoc->qnameList[item.qnameIndex];
1345         qname.nsURI = fixNamespace(qname.nsURI);
1346 
1347         if (packedDoc->processNamespace)
1348             element = ownerDoc.createElementNS(qname.nsURI, qname.name);
1349         else
1350             element = ownerDoc.createElement(qname.name);
1351 
1352         if ( parentNode.isNull() ) {
1353             ownerDoc.appendChild( element );
1354         } else {
1355             parentNode.appendChild( element );
1356         }
1357         // check all subnodes for attributes
1358         int nodeDepth = item.depth;
1359         for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) {
1360             KoXmlPackedItem& item = packedDoc->items[i];
1361             bool textItem = (item.type == KoXmlNode::TextNode);
1362             textItem |= (item.type == KoXmlNode::CDATASectionNode);
1363 
1364             // element already outside our depth
1365             if (!item.attr && (item.type == KoXmlNode::ElementNode))
1366                 if (item.depth <= (unsigned)nodeDepth)
1367                     break;
1368 
1369             // attribute belongs to this node
1370             if (item.attr && (item.depth == (unsigned)nodeDepth)) {
1371                 KoQName qname = packedDoc->qnameList[item.qnameIndex];
1372                 qname.nsURI = fixNamespace(qname.nsURI);
1373                 QString value = item.value;
1374                 QString prefix;
1375 
1376                 QString qName; // with prefix
1377                 QString localName;  // without prefix, i.e. local name
1378 
1379                 localName = qName = qname.name;
1380                 int i = qName.indexOf(':');
1381                 if (i != -1) prefix = qName.left(i);
1382                 if (i != -1) localName = qName.mid(i + 1);
1383 
1384                 if (packedDoc->processNamespace) {
1385                     element.setAttributeNS(qname.nsURI, qName, value);
1386                     element.setAttribute(localName, value);
1387                 } else
1388                     element.setAttribute(qname.name, value);
1389             }
1390 
1391             // direct child of this node
1392             if (!item.attr && (item.depth == (unsigned)nodeDepth + 1)) {
1393                 // add it recursively
1394                 itemAsQDomNode(ownerDoc, packedDoc, i, element);
1395             }
1396         }
1397         return;
1398     }
1399 
1400     // create the text node
1401     if (item.type == KoXmlNode::TextNode) {
1402         QString text = item.value;
1403         // FIXME: choose CDATA when the value contains special characters
1404         QDomText textNode = ownerDoc.createTextNode(text);
1405         if ( parentNode.isNull() ) {
1406             ownerDoc.appendChild( textNode );
1407         } else {
1408             parentNode.appendChild( textNode );
1409         }
1410         return;
1411     }
1412 
1413     // nothing matches? strange...
1414 }
1415 
1416 void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const
1417 {
1418     itemAsQDomNode(ownerDoc, packedDoc, nodeIndex);
1419 }
1420 
1421 #endif
1422 
1423 void KoXmlNodeData::dump()
1424 {
1425     printf("NodeData %p\n", (void*)this);
1426 
1427     printf("  nodeIndex: %d\n", (int)nodeIndex);
1428     printf("  packedDoc: %p\n", (void*)packedDoc);
1429 
1430     printf("  nodeType : %d\n", (int)nodeType);
1431     printf("  tagName: %s\n", qPrintable(tagName));
1432     printf("  namespaceURI: %s\n", qPrintable(namespaceURI));
1433     printf("  prefix: %s\n", qPrintable(prefix));
1434     printf("  localName: %s\n", qPrintable(localName));
1435 
1436     printf("  parent : %p\n", (void*)parent);
1437     printf("  prev : %p\n", (void*)prev);
1438     printf("  next : %p\n", (void*)next);
1439     printf("  first : %p\n", (void*)first);
1440     printf("  last : %p\n", (void*)last);
1441 
1442     printf("  refCount: %ld\n", refCount);
1443 
1444     if (loaded)
1445         printf("  loaded: TRUE\n");
1446     else
1447         printf("  loaded: FALSE\n");
1448 }
1449 
1450 
1451 // ==================================================================
1452 //
1453 //         KoXmlNodeData
1454 //
1455 // ==================================================================
1456 
1457 class KoXmlDocumentData : public KoXmlNodeData
1458 {
1459 public:
1460 
1461     KoXmlDocumentData(unsigned long initialRefCount = 1);
1462     ~KoXmlDocumentData();
1463 
1464     bool setContent(QXmlStreamReader *reader,
1465                     QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0);
1466 
1467     KoXmlDocumentType dt;
1468 
1469     bool emptyDocument :1;
1470     // to read the xml with or without spaces
1471     bool stripSpaces :1;
1472 };
1473 
1474 #define KOXMLDOCDATA(d)  static_cast<KoXmlDocumentData*>(d)
1475 
1476 
1477 KoXmlDocumentData::KoXmlDocumentData(unsigned long initialRefCount)
1478     : KoXmlNodeData(initialRefCount)
1479     , emptyDocument(true)
1480     , stripSpaces(true)
1481 {
1482 }
1483 
1484 KoXmlDocumentData::~KoXmlDocumentData()
1485 {
1486 }
1487 
1488 bool KoXmlDocumentData::setContent(QXmlStreamReader* reader, QString* errorMsg, int* errorLine, int* errorColumn)
1489 {
1490     // sanity checks
1491     if (!reader) return false;
1492 
1493     if (nodeType != KoXmlNode::DocumentNode)
1494         return false;
1495 
1496     clear();
1497     nodeType = KoXmlNode::DocumentNode;
1498 
1499     packedDoc = new KoXmlPackedDocument;
1500     packedDoc->processNamespace = reader->namespaceProcessing();
1501 
1502     ParseError error = parseDocument(*reader, *packedDoc, stripSpaces);
1503     if (error.error) {
1504         // parsing error has occurred
1505         if (errorMsg) *errorMsg = error.errorMsg;
1506         if (errorLine) *errorLine = error.errorLine;
1507         if (errorColumn)  *errorColumn = error.errorColumn;
1508         return false;
1509     }
1510 
1511     // initially load
1512     loadChildren();
1513 
1514     KoXmlNodeData *typeData = new KoXmlNodeData(0);
1515     typeData->nodeType = KoXmlNode::DocumentTypeNode;
1516     typeData->tagName = packedDoc->docType;
1517     typeData->parent = this;
1518     dt = KoXmlDocumentType(typeData);
1519 
1520     return true;
1521 }
1522 
1523 // ==================================================================
1524 //
1525 //         KoXmlNode
1526 //
1527 // ==================================================================
1528 
1529 // Creates a null node
1530 KoXmlNode::KoXmlNode()
1531 {
1532     d = &KoXmlNodeData::null;
1533     d->ref();
1534 }
1535 
1536 // Destroys this node
1537 KoXmlNode::~KoXmlNode()
1538 {
1539     d->unref();
1540 }
1541 
1542 // Creates a copy of another node
1543 KoXmlNode::KoXmlNode(const KoXmlNode& node)
1544 {
1545     d = node.d;
1546     d->ref();
1547 }
1548 
1549 // Creates a node for specific implementation
1550 KoXmlNode::KoXmlNode(KoXmlNodeData* data)
1551 {
1552     d = data;
1553     data->ref();
1554 }
1555 
1556 // Creates a shallow copy of another node
1557 KoXmlNode& KoXmlNode::operator=(const KoXmlNode & node)
1558 {
1559     if (this != &node) {
1560         d->unref();
1561         d = node.d;
1562         d->ref();
1563     }
1564     return *this;
1565 }
1566 
1567 // Note: two null nodes are always equal
1568 bool KoXmlNode::operator==(const KoXmlNode& node) const
1569 {
1570     if (isNull() && node.isNull()) return true;
1571     return(d == node.d);
1572 }
1573 
1574 // Note: two null nodes are always equal
1575 bool KoXmlNode::operator!=(const KoXmlNode& node) const
1576 {
1577     if (isNull() && !node.isNull()) return true;
1578     if (!isNull() && node.isNull()) return true;
1579     if (isNull() && node.isNull()) return false;
1580     return(d != node.d);
1581 }
1582 
1583 KoXmlNode::NodeType KoXmlNode::nodeType() const
1584 {
1585     return d->nodeType;
1586 }
1587 
1588 bool KoXmlNode::isNull() const
1589 {
1590     return d->nodeType == NullNode;
1591 }
1592 
1593 bool KoXmlNode::isElement() const
1594 {
1595     return d->nodeType == ElementNode;
1596 }
1597 
1598 bool KoXmlNode::isText() const
1599 {
1600     return (d->nodeType == TextNode) || isCDATASection();
1601 }
1602 
1603 bool KoXmlNode::isCDATASection() const
1604 {
1605     return d->nodeType == CDATASectionNode;
1606 }
1607 
1608 bool KoXmlNode::isDocument() const
1609 {
1610     return d->nodeType == DocumentNode;
1611 }
1612 
1613 bool KoXmlNode::isDocumentType() const
1614 {
1615     return d->nodeType == DocumentTypeNode;
1616 }
1617 
1618 void KoXmlNode::clear()
1619 {
1620     d->unref();
1621     d = new KoXmlNodeData;
1622 }
1623 
1624 QString KoXmlNode::nodeName() const
1625 {
1626     return d->nodeName();
1627 }
1628 
1629 QString KoXmlNode::prefix() const
1630 {
1631     return isElement() ? d->prefix : QString();
1632 }
1633 
1634 QString KoXmlNode::namespaceURI() const
1635 {
1636     return isElement() ? d->namespaceURI : QString();
1637 }
1638 
1639 QString KoXmlNode::localName() const
1640 {
1641     return isElement() ? d->localName : QString();
1642 }
1643 
1644 KoXmlDocument KoXmlNode::ownerDocument() const
1645 {
1646     KoXmlNodeData* node = d;
1647     while (node->parent) node = node->parent;
1648 
1649     if (node->nodeType == DocumentNode) {
1650         return KoXmlDocument(static_cast<KoXmlDocumentData*>(node));
1651     }
1652     return KoXmlDocument();
1653 }
1654 
1655 KoXmlNode KoXmlNode::parentNode() const
1656 {
1657     return d->parent ? KoXmlNode(d->parent) : KoXmlNode();
1658 }
1659 
1660 bool KoXmlNode::hasChildNodes() const
1661 {
1662     if (isText())
1663         return false;
1664 
1665     if (!d->loaded)
1666         d->loadChildren();
1667 
1668     return d->first != 0 ;
1669 }
1670 
1671 int KoXmlNode::childNodesCount() const
1672 {
1673     if (isText())
1674         return 0;
1675 
1676     if (!d->loaded)
1677         d->loadChildren();
1678 
1679     KoXmlNodeData* node = d->first;
1680     int count = 0;
1681     while (node) {
1682         ++count;
1683         node = node->next;
1684     }
1685 
1686     return count;
1687 }
1688 
1689 QStringList KoXmlNode::attributeNames() const
1690 {
1691     if (!d->loaded)
1692         d->loadChildren();
1693 
1694     return d->attributeNames();
1695 }
1696 
1697 QList< QPair<QString, QString> > KoXmlNode::attributeFullNames() const
1698 {
1699     if (!d->loaded)
1700         d->loadChildren();
1701 
1702     return d->attributeFullNames();
1703 }
1704 
1705 KoXmlNode KoXmlNode::firstChild() const
1706 {
1707     if (!d->loaded)
1708         d->loadChildren();
1709     return d->first ? KoXmlNode(d->first) : KoXmlNode();
1710 }
1711 
1712 KoXmlElement KoXmlNode::firstChildElement() const
1713 {
1714     KoXmlElement element;
1715     forEachElement (element, (*this)) {
1716         return element;
1717     }
1718     return KoXmlElement();
1719 }
1720 
1721 KoXmlNode KoXmlNode::lastChild() const
1722 {
1723     if (!d->loaded)
1724         d->loadChildren();
1725     return d->last ? KoXmlNode(d->last) : KoXmlNode();
1726 }
1727 
1728 KoXmlNode KoXmlNode::nextSibling() const
1729 {
1730     return d->next ? KoXmlNode(d->next) : KoXmlNode();
1731 }
1732 
1733 KoXmlNode KoXmlNode::previousSibling() const
1734 {
1735     return d->prev ? KoXmlNode(d->prev) : KoXmlNode();
1736 }
1737 
1738 KoXmlNode KoXmlNode::namedItem(const QString& name) const
1739 {
1740     if (!d->loaded)
1741         d->loadChildren();
1742 
1743     for (KoXmlNodeData* node = d->first; node; node = node->next) {
1744         if (node->nodeName() == name)
1745             return KoXmlNode(node);
1746     }
1747 
1748     // not found
1749     return KoXmlNode();
1750 }
1751 
1752 KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name) const
1753 {
1754     if (!d->loaded)
1755         d->loadChildren();
1756 
1757     for (KoXmlNodeData* node = d->first; node; node = node->next) {
1758         if (node->nodeType == KoXmlNode::ElementNode
1759                  && node->localName == name
1760                  && node->namespaceURI == nsURI
1761                  ) {
1762             return KoXmlNode(node);
1763         }
1764     }
1765 
1766     // not found
1767     return KoXmlNode();
1768 }
1769 
1770 KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const
1771 {
1772     if (!d->loaded)
1773         d->loadChildren();
1774 
1775     for (KoXmlNodeData* node = d->first; node; node = node->next) {
1776         if (node->nodeType != KoXmlNode::ElementNode)
1777             continue;
1778         if (node->localName == name && node->namespaceURI == nsURI) {
1779             return KoXmlNode(node);
1780         }
1781         bool isPrelude = false;
1782         switch (type) {
1783             case KoXmlTextContentPrelude:
1784                 isPrelude =
1785                     (node->localName == "tracked-changes" && node->namespaceURI == KoXmlNS::text) ||
1786                     (node->localName == "variable-decls" && node->namespaceURI == KoXmlNS::text) ||
1787                     (node->localName == "user-field-decls" && node->namespaceURI == KoXmlNS::text) ||
1788                     (node->localName == "user-field-decl" && node->namespaceURI == KoXmlNS::text) ||
1789                     (node->localName == "sequence-decls" && node->namespaceURI == KoXmlNS::text) ||
1790                     (node->localName == "sequence-decl" && node->namespaceURI == KoXmlNS::text) ||
1791                     (node->localName == "dde-connection-decls" && node->namespaceURI == KoXmlNS::text) ||
1792                     (node->localName == "alphabetical-index-auto-mark-file" && node->namespaceURI == KoXmlNS::text) ||
1793                     (node->localName == "forms" && node->namespaceURI == KoXmlNS::office);
1794                 break;
1795         }
1796         if (!isPrelude) {
1797             return KoXmlNode(); // no TextContentPrelude means it follows TextContentMain, so stop here.
1798         }
1799     }
1800 
1801     // not found
1802     return KoXmlNode();
1803 }
1804 
1805 KoXmlElement KoXmlNode::toElement() const
1806 {
1807     return isElement() ? KoXmlElement(d) : KoXmlElement();
1808 }
1809 
1810 KoXmlText KoXmlNode::toText() const
1811 {
1812     return isText() ? KoXmlText(d) : KoXmlText();
1813 }
1814 
1815 KoXmlCDATASection KoXmlNode::toCDATASection() const
1816 {
1817     return isCDATASection() ? KoXmlCDATASection(d) : KoXmlCDATASection();
1818 }
1819 
1820 KoXmlDocument KoXmlNode::toDocument() const
1821 {
1822     if (isDocument()) {
1823         return KoXmlDocument(static_cast<KoXmlDocumentData*>(d));
1824     }
1825     return KoXmlDocument();
1826 }
1827 
1828 void KoXmlNode::load(int depth)
1829 {
1830     d->loadChildren(depth);
1831 }
1832 
1833 void KoXmlNode::unload()
1834 {
1835     d->unloadChildren();
1836 }
1837 
1838 void KoXmlNode::asQDomNode(QDomDocument& ownerDoc) const
1839 {
1840     Q_ASSERT(!isDocument());
1841     d->asQDomNode(ownerDoc);
1842 }
1843 
1844 // ==================================================================
1845 //
1846 //         KoXmlElement
1847 //
1848 // ==================================================================
1849 
1850 // Creates an empty element
1851 KoXmlElement::KoXmlElement(): KoXmlNode()
1852 {
1853 }
1854 
1855 KoXmlElement::~KoXmlElement()
1856 {
1857 }
1858 
1859 // Creates a shallow copy of another element
1860 KoXmlElement::KoXmlElement(const KoXmlElement& element): KoXmlNode(element.d)
1861 {
1862 }
1863 
1864 KoXmlElement::KoXmlElement(KoXmlNodeData* data): KoXmlNode(data)
1865 {
1866 }
1867 
1868 // Copies another element
1869 KoXmlElement& KoXmlElement::operator=(const KoXmlElement & element)
1870 {
1871     KoXmlNode::operator=(element);
1872     return *this;
1873 }
1874 
1875 bool KoXmlElement::operator== (const KoXmlElement& element) const
1876 {
1877     if (isNull() || element.isNull()) return false;
1878     return (d == element.d);
1879 }
1880 
1881 bool KoXmlElement::operator!= (const KoXmlElement& element) const
1882 {
1883     if (isNull() && element.isNull()) return false;
1884     if (isNull() || element.isNull()) return true;
1885     return (d != element.d);
1886 }
1887 
1888 QString KoXmlElement::tagName() const
1889 {
1890     return isElement() ? d->tagName : QString();
1891 }
1892 
1893 QString KoXmlElement::text() const
1894 {
1895     return d->text();
1896 }
1897 
1898 QString KoXmlElement::attribute(const QString& name) const
1899 {
1900     if (!isElement())
1901         return QString();
1902 
1903     if (!d->loaded)
1904         d->loadChildren();
1905 
1906     return d->attribute(name, QString());
1907 }
1908 
1909 QString KoXmlElement::attribute(const QString& name,
1910                                 const QString& defaultValue) const
1911 {
1912     if (!isElement())
1913         return defaultValue;
1914 
1915     if (!d->loaded)
1916         d->loadChildren();
1917 
1918     return d->attribute(name, defaultValue);
1919 }
1920 
1921 QString KoXmlElement::attributeNS(const QString& namespaceURI,
1922                                   const QString& localName, const QString& defaultValue) const
1923 {
1924     if (!isElement())
1925         return defaultValue;
1926 
1927     if (!d->loaded)
1928         d->loadChildren();
1929 
1930     KoXmlStringPair key(namespaceURI, localName);
1931     return d->attrNS.value(key, defaultValue);
1932 
1933 //  return d->attributeNS( namespaceURI, localName, defaultValue );
1934 }
1935 
1936 bool KoXmlElement::hasAttribute(const QString& name) const
1937 {
1938     if (!d->loaded)
1939         d->loadChildren();
1940 
1941     return isElement() ? d->hasAttribute(name) : false;
1942 }
1943 
1944 bool KoXmlElement::hasAttributeNS(const QString& namespaceURI,
1945                                   const QString& localName) const
1946 {
1947     if (!d->loaded)
1948         d->loadChildren();
1949 
1950     return isElement() ? d->hasAttributeNS(namespaceURI, localName) : false;
1951 }
1952 
1953 // ==================================================================
1954 //
1955 //         KoXmlText
1956 //
1957 // ==================================================================
1958 
1959 KoXmlText::KoXmlText(): KoXmlNode()
1960 {
1961 }
1962 
1963 KoXmlText::~KoXmlText()
1964 {
1965 }
1966 
1967 KoXmlText::KoXmlText(const KoXmlText& text): KoXmlNode(text.d)
1968 {
1969 }
1970 
1971 KoXmlText::KoXmlText(KoXmlNodeData* data): KoXmlNode(data)
1972 {
1973 }
1974 
1975 bool KoXmlText::isText() const
1976 {
1977     return true;
1978 }
1979 
1980 QString KoXmlText::data() const
1981 {
1982     return d->data();
1983 }
1984 
1985 KoXmlText& KoXmlText::operator=(const KoXmlText & element)
1986 {
1987     KoXmlNode::operator=(element);
1988     return *this;
1989 }
1990 
1991 // ==================================================================
1992 //
1993 //         KoXmlCDATASection
1994 //
1995 // ==================================================================
1996 
1997 KoXmlCDATASection::KoXmlCDATASection(): KoXmlText()
1998 {
1999 }
2000 
2001 KoXmlCDATASection::KoXmlCDATASection(const KoXmlCDATASection& cdata)
2002         : KoXmlText(cdata)
2003 {
2004 }
2005 
2006 KoXmlCDATASection::~KoXmlCDATASection()
2007 {
2008 }
2009 
2010 KoXmlCDATASection::KoXmlCDATASection(KoXmlNodeData* cdata):
2011         KoXmlText(cdata)
2012 {
2013 }
2014 
2015 bool KoXmlCDATASection::isCDATASection() const
2016 {
2017     return true;
2018 }
2019 
2020 KoXmlCDATASection& KoXmlCDATASection::operator=(const KoXmlCDATASection & cdata)
2021 {
2022     KoXmlNode::operator=(cdata);
2023     return *this;
2024 }
2025 
2026 // ==================================================================
2027 //
2028 //         KoXmlDocumentType
2029 //
2030 // ==================================================================
2031 
2032 KoXmlDocumentType::KoXmlDocumentType(): KoXmlNode()
2033 {
2034 }
2035 
2036 KoXmlDocumentType::~KoXmlDocumentType()
2037 {
2038 }
2039 
2040 KoXmlDocumentType::KoXmlDocumentType(const KoXmlDocumentType& dt):
2041         KoXmlNode(dt.d)
2042 {
2043 }
2044 
2045 QString KoXmlDocumentType::name() const
2046 {
2047     return nodeName();
2048 }
2049 
2050 KoXmlDocumentType::KoXmlDocumentType(KoXmlNodeData* dt): KoXmlNode(dt)
2051 {
2052 }
2053 
2054 KoXmlDocumentType& KoXmlDocumentType::operator=(const KoXmlDocumentType & dt)
2055 {
2056     KoXmlNode::operator=(dt);
2057     return *this;
2058 }
2059 
2060 // ==================================================================
2061 //
2062 //         KoXmlDocument
2063 //
2064 // ==================================================================
2065 
2066 KoXmlDocument::KoXmlDocument(bool stripSpaces): KoXmlNode(new KoXmlDocumentData(0))
2067 {
2068     KOXMLDOCDATA(d)->emptyDocument = false;
2069     KOXMLDOCDATA(d)->stripSpaces = stripSpaces;
2070 }
2071 
2072 KoXmlDocument::~KoXmlDocument()
2073 {
2074 }
2075 
2076 KoXmlDocument::KoXmlDocument(KoXmlDocumentData* data): KoXmlNode(data)
2077 {
2078     KOXMLDOCDATA(d)->emptyDocument = true;
2079 }
2080 
2081 // Creates a copy of another document
2082 KoXmlDocument::KoXmlDocument(const KoXmlDocument& doc): KoXmlNode(doc.d)
2083 {
2084 }
2085 
2086 // Creates a shallow copy of another document
2087 KoXmlDocument& KoXmlDocument::operator=(const KoXmlDocument & doc)
2088 {
2089     KoXmlNode::operator=(doc);
2090     return *this;
2091 }
2092 
2093 // Checks if this document and doc are equals
2094 bool KoXmlDocument::operator==(const KoXmlDocument& doc) const
2095 {
2096     return(d == doc.d);
2097 }
2098 
2099 // Checks if this document and doc are not equals
2100 bool KoXmlDocument::operator!=(const KoXmlDocument& doc) const
2101 {
2102     return(d != doc.d);
2103 }
2104 
2105 KoXmlElement KoXmlDocument::documentElement() const
2106 {
2107     if (!d->loaded)
2108         d->loadChildren();
2109 
2110     for (KoXmlNodeData* node = d->first; node; node = node->next) {
2111         if (node->nodeType == KoXmlNode::ElementNode) {
2112             return KoXmlElement(node);
2113         }
2114     }
2115 
2116     return KoXmlElement();
2117 }
2118 
2119 KoXmlDocumentType KoXmlDocument::doctype() const
2120 {
2121     return KOXMLDOCDATA(d)->dt;
2122 }
2123 
2124 QString KoXmlDocument::nodeName() const
2125 {
2126     return (KOXMLDOCDATA(d)->emptyDocument) ? QString::fromLatin1("#document") : QString();
2127 }
2128 
2129 void KoXmlDocument::clear()
2130 {
2131     d->unref();
2132     KoXmlDocumentData *dat = new KoXmlDocumentData;
2133     dat->emptyDocument = false;
2134     d = dat;
2135 }
2136 
2137 namespace {
2138     /* Use an entity resolver that ignores undefined entities and simply
2139        returns an empty string for them.
2140        */
2141     class DumbEntityResolver : public QXmlStreamEntityResolver {
2142     public:
2143         QString resolveUndeclaredEntity ( const QString &) override { return ""; }
2144     };
2145 
2146 }
2147 
2148 bool KoXmlDocument::setContent(QXmlStreamReader *reader,
2149                                QString* errorMsg, int* errorLine, int* errorColumn)
2150 {
2151     if (d->nodeType != KoXmlNode::DocumentNode) {
2152         const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
2153         d->unref();
2154         KoXmlDocumentData *dat = new KoXmlDocumentData;
2155         dat->nodeType = KoXmlNode::DocumentNode;
2156         dat->stripSpaces = stripSpaces;
2157         d = dat;
2158     }
2159 
2160     const bool result = KOXMLDOCDATA(d)->setContent(reader, errorMsg, errorLine, errorColumn);
2161 
2162     return result;
2163 }
2164 
2165 // no namespace processing
2166 bool KoXmlDocument::setContent(QIODevice* device, QString* errorMsg,
2167                                int* errorLine, int* errorColumn)
2168 {
2169     return setContent(device, false, errorMsg, errorLine, errorColumn);
2170 }
2171 
2172 bool KoXmlDocument::setContent(QIODevice* device, bool namespaceProcessing,
2173                                QString* errorMsg, int* errorLine, int* errorColumn)
2174 {
2175     if (d->nodeType != KoXmlNode::DocumentNode) {
2176         const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
2177         d->unref();
2178         KoXmlDocumentData *dat = new KoXmlDocumentData;
2179         dat->nodeType = KoXmlNode::DocumentNode;
2180         dat->stripSpaces = stripSpaces;
2181         d = dat;
2182     }
2183 
2184     if (!device->isOpen()) device->open(QIODevice::ReadOnly);
2185     QXmlStreamReader reader(device);
2186     reader.setNamespaceProcessing(namespaceProcessing);
2187     DumbEntityResolver entityResolver;
2188     reader.setEntityResolver(&entityResolver);
2189 
2190     const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn);
2191 
2192     return result;
2193 }
2194 
2195 bool KoXmlDocument::setContent(const QByteArray& text, bool namespaceProcessing,
2196                                QString *errorMsg, int *errorLine, int *errorColumn)
2197 {
2198     QBuffer buffer;
2199     buffer.setData(text);
2200     return setContent(&buffer, namespaceProcessing, errorMsg, errorLine, errorColumn);
2201 }
2202 
2203 bool KoXmlDocument::setContent(const QString& text, bool namespaceProcessing,
2204                                QString *errorMsg, int *errorLine, int *errorColumn)
2205 {
2206     if (d->nodeType != KoXmlNode::DocumentNode) {
2207         const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
2208         d->unref();
2209         KoXmlDocumentData *dat = new KoXmlDocumentData;
2210         dat->nodeType = KoXmlNode::DocumentNode;
2211         dat->stripSpaces = stripSpaces;
2212         d = dat;
2213     }
2214 
2215     QXmlStreamReader reader(text);
2216     reader.setNamespaceProcessing(namespaceProcessing);
2217     DumbEntityResolver entityResolver;
2218     reader.setEntityResolver(&entityResolver);
2219 
2220     const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn);
2221 
2222     return result;
2223 }
2224 
2225 bool KoXmlDocument::setContent(const QString& text,
2226                                QString *errorMsg, int *errorLine, int *errorColumn)
2227 {
2228     return setContent(text, false, errorMsg, errorLine, errorColumn);
2229 }
2230 
2231 void  KoXmlDocument::setWhitespaceStripping(bool stripSpaces)
2232 {
2233     KOXMLDOCDATA(d)->stripSpaces = stripSpaces;
2234 }
2235 
2236 
2237 #endif
2238 
2239 // ==================================================================
2240 //
2241 //         functions in KoXml namespace
2242 //
2243 // ==================================================================
2244 
2245 KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI,
2246                                 const QString& localName)
2247 {
2248 #ifdef KOXML_USE_QDOM
2249     // David's solution for namedItemNS, only for QDom stuff
2250     KoXmlNode n = node.firstChild();
2251     for (; !n.isNull(); n = n.nextSibling()) {
2252         if (n.isElement() && n.localName() == localName &&
2253                 n.namespaceURI() == nsURI)
2254             return n.toElement();
2255     }
2256     return KoXmlElement();
2257 #else
2258     return node.namedItemNS(nsURI, localName).toElement();
2259 #endif
2260 }
2261 
2262 KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI,
2263                                 const QString& localName, KoXmlNamedItemType type)
2264 {
2265 #ifdef KOXML_USE_QDOM
2266 Q_ASSERT(false);
2267     return namedItemNS(node, nsURI, localName);
2268 #else
2269     return node.namedItemNS(nsURI, localName, type).toElement();
2270 #endif
2271 }
2272 
2273 void KoXml::load(KoXmlNode& node, int depth)
2274 {
2275 #ifdef KOXML_USE_QDOM
2276     // do nothing, QDom has no on-demand loading
2277     Q_UNUSED(node);
2278     Q_UNUSED(depth);
2279 #else
2280     node.load(depth);
2281 #endif
2282 }
2283 
2284 
2285 void KoXml::unload(KoXmlNode& node)
2286 {
2287 #ifdef KOXML_USE_QDOM
2288     // do nothing, QDom has no on-demand unloading
2289     Q_UNUSED(node);
2290 #else
2291     node.unload();
2292 #endif
2293 }
2294 
2295 int KoXml::childNodesCount(const KoXmlNode& node)
2296 {
2297 #ifdef KOXML_USE_QDOM
2298     return node.childNodes().count();
2299 #else
2300     // compatibility function, because no need to implement
2301     // a class like QDomNodeList
2302     return node.childNodesCount();
2303 #endif
2304 }
2305 
2306 QStringList KoXml::attributeNames(const KoXmlNode& node)
2307 {
2308 #ifdef KOXML_USE_QDOM
2309     QStringList result;
2310 
2311     QDomNamedNodeMap attrMap = node.attributes();
2312     for (int i = 0; i < attrMap.count(); ++i)
2313         result += attrMap.item(i).toAttr().name();
2314 
2315     return result;
2316 #else
2317     // compatibility function, because no need to implement
2318     // a class like QDomNamedNodeMap
2319     return node.attributeNames();
2320 #endif
2321 }
2322 
2323 void KoXml::asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node)
2324 {
2325     Q_ASSERT(!node.isDocument());
2326 #ifdef KOXML_USE_QDOM
2327     ownerDoc.appendChild(ownerDoc.importNode(node));
2328 #else
2329     node.asQDomNode(ownerDoc);
2330 #endif
2331 }
2332 
2333 void KoXml::asQDomElement(QDomDocument &ownerDoc, const KoXmlElement& element)
2334 {
2335     KoXml::asQDomNode(ownerDoc, element);
2336 }
2337 
2338 QDomDocument KoXml::asQDomDocument(const KoXmlDocument& document)
2339 {
2340 #ifdef KOXML_USE_QDOM
2341     return document;
2342 #else
2343     QDomDocument qdoc( document.nodeName() );
2344     if ( document.hasChildNodes() ) {
2345         for ( KoXmlNode n = document.firstChild(); ! n.isNull(); n = n.nextSibling() ) {
2346             KoXml::asQDomNode(qdoc, n);
2347         }
2348     }
2349     return qdoc;
2350 #endif
2351 }
2352 
2353 bool KoXml::setDocument(KoXmlDocument& doc, QIODevice* device,
2354                         bool namespaceProcessing, QString* errorMsg, int* errorLine,
2355                         int* errorColumn)
2356 {
2357     QXmlStreamReader reader(device);
2358     reader.setNamespaceProcessing(namespaceProcessing);
2359     bool result = doc.setContent(&reader, errorMsg, errorLine, errorColumn);
2360     return result;
2361 }