1 /* This file is part of the KDE project
2    Copyright (C) 2005-2006 Ariya Hidayat <ariya@kde.org>
3 
4    This library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Library General Public
6    License as published by the Free Software Foundation; either
7    version 2 of the License, or (at your option) any later version.
8 
9    This library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Library General Public License for more details.
13 
14    You should have received a copy of the GNU Library General Public License
15    along with this library; see the file COPYING.LIB.  If not, write to
16    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17    Boston, MA 02110-1301, USA.
18 */
19 
20 // clazy:excludeall=qstring-arg
21 #include "KoXmlReader.h"
22 #include "KoXmlNS.h"
23 
24 /*
25   This is a memory-efficient DOM implementation for Calligra. See the API
26   documentation for details.
27 
28   IMPORTANT !
29 
30   * When you change this stuff, make sure it DOES NOT BREAK the test suite.
31     Build tests/koxmlreadertest.cpp and verify it. Many sleepless nights
32     have been sacrificed for this piece of code, do not let those precious
33     hours wasted!
34 
35   * Run koxmlreadertest.cpp WITH Valgrind and make sure NO illegal
36     memory read/write and any type of leak occurs. If you are not familiar
37     with Valgrind then RTFM first and come back again later on.
38 
39   * The public API shall remain as compatible as QDom.
40 
41   * All QDom-compatible methods should behave the same. All QDom-compatible
42     functions should return the same result. In case of doubt, run
43     koxmlreadertest.cpp but uncomment KOXML_USE_QDOM in koxmlreader.h
44     so that the tests are performed with standard QDom.
45 
46   Some differences compared to QDom:
47 
48   - DOM tree in KoXmlDocument is read-only, you can not modify it. This is
49     sufficient for Calligra since the tree is only accessed when loading
50     a document to the application. For saving the document to XML file,
51     use KoXmlWriter.
52 
53   - Because the dynamic loading and unloading, you have to use the
54     nodes (and therefore also elements) carefully since the whole API
55  (just like QDom) is reference-based, not pointer-based. If the
56  parent node is unloaded from memory, the reference is not valid
57  anymore and may give unpredictable result.
58  The easiest way: use the node/element in very short time only.
59 
60   - Comment node (like QDomComment) is not implemented as comments are
61     simply ignored.
62 
63   - DTD, entity and entity reference are not handled. Thus, the associated
64     nodes (like QDomDocumentType, QDomEntity, QDomEntityReference) are also
65     not implemented.
66 
67   - Attribute mapping node is not implemented. But of course, functions to
68     query attributes of an element are available.
69 
70 
71  */
72 
73 #include <QTextCodec>
74 #include <QTextDecoder>
75 
76 #ifndef KOXML_USE_QDOM
77 
78 #include <QtXml>
79 #include <QDomDocument>
80 #include <QXmlStreamReader>
81 #include <QXmlStreamEntityResolver>
82 
83 #include <QBuffer>
84 #include <QByteArray>
85 #include <QDataStream>
86 #include <QHash>
87 #include <QPair>
88 #include <QStringList>
89 #include <QVector>
90 
91 /*
92  Use more compact representation of in-memory nodes.
93 
94  Advantages: faster iteration, can facilitate real-time compression.
95  Disadvantages: still buggy, eat slightly more memory.
96 */
97 #define KOXML_COMPACT
98 
99 /*
100  Use real-time compression. Only works in conjunction with KOXML_COMPACT
101  above because otherwise the non-compact layout will slow down everything.
102 */
103 #define KOXML_COMPRESS
104 
105 
106 // prevent mistake, see above
107 #ifdef KOXML_COMPRESS
108 #ifndef KOXML_COMPACT
109 #error Please enable also KOXML_COMPACT
110 #endif
111 #endif
112 
113 // this is used to quickly get namespaced attribute(s)
114 typedef QPair<QString, QString> KoXmlStringPair;
115 
116 class KoQName {
117 public:
118     QString nsURI;
119     QString name;
120 
KoQName(const QString & nsURI_,const QString & name_)121     explicit KoQName(const QString& nsURI_, const QString& name_)
122         : nsURI(nsURI_), name(name_) {}
operator ==(const KoQName & qname) const123     bool operator==(const KoQName& qname) const {
124         // local name is more likely to differ, so compare that first
125         return name == qname.name && nsURI == qname.nsURI;
126     }
127 };
128 
qHash(const KoQName & qname)129 uint qHash(const KoQName& qname)
130 {
131     // possibly add a faster hash function that only includes some trailing
132     // part of the nsURI
133 
134     // in case of doubt, use this:
135     // return qHash(qname.nsURI)^qHash(qname.name);
136     return qHash(qname.nsURI)^qHash(qname.name);
137 }
138 
139 // this simplistic hash is rather fast-and-furious. it works because
140 // likely there is very few namespaced attributes per element
qHash(const KoXmlStringPair & p,uint=0)141 static inline uint qHash(const KoXmlStringPair &p, uint /*seed*/ = 0)
142 {
143     return qHash(p.second[0].unicode()) ^ 0x1477;
144 
145     // in case of doubt, use this:
146     // return qHash(p.first)^qHash(p.second);
147 }
148 
operator ==(const KoXmlStringPair & a,const KoXmlStringPair & b)149 static inline bool operator==(const KoXmlStringPair &a, const KoXmlStringPair &b)
150 {
151     return a.second == b.second && a.first == b.first;
152 }
153 
154 // Older versions of OpenOffice.org used different namespaces. This function
155 // does translate the old namespaces into the new ones.
fixNamespace(const QString & nsURI)156 static QString fixNamespace(const QString &nsURI)
157 {
158     static QString office = QString::fromLatin1("http://openoffice.org/2000/office");
159     static QString text = QString::fromLatin1("http://openoffice.org/2000/text");
160     static QString style = QString::fromLatin1("http://openoffice.org/2000/style");
161     static QString fo = QString::fromLatin1("http://www.w3.org/1999/XSL/Format");
162     static QString table = QString::fromLatin1("http://openoffice.org/2000/table");
163     static QString drawing = QString::fromLatin1("http://openoffice.org/2000/drawing");
164     static QString datastyle = QString::fromLatin1("http://openoffice.org/2000/datastyle");
165     static QString svg = QString::fromLatin1("http://www.w3.org/2000/svg");
166     static QString chart = QString::fromLatin1("http://openoffice.org/2000/chart");
167     static QString dr3d = QString::fromLatin1("http://openoffice.org/2000/dr3d");
168     static QString form = QString::fromLatin1("http://openoffice.org/2000/form");
169     static QString script = QString::fromLatin1("http://openoffice.org/2000/script");
170     static QString meta = QString::fromLatin1("http://openoffice.org/2000/meta");
171     static QString config = QString::fromLatin1("http://openoffice.org/2001/config");
172     static QString pres = QString::fromLatin1("http://openoffice.org/2000/presentation");
173     static QString manifest = QString::fromLatin1("http://openoffice.org/2001/manifest");
174     if (nsURI == text)
175         return KoXmlNS::text;
176     if (nsURI == style)
177         return KoXmlNS::style;
178     if (nsURI == office)
179         return KoXmlNS::office;
180     if (nsURI == fo)
181         return KoXmlNS::fo;
182     if (nsURI == table)
183         return KoXmlNS::table;
184     if (nsURI == drawing)
185         return KoXmlNS::draw;
186     if (nsURI == datastyle)
187         return KoXmlNS::number;
188     if (nsURI == svg)
189         return KoXmlNS::svg;
190     if (nsURI == chart)
191         return KoXmlNS::chart;
192     if (nsURI == dr3d)
193         return KoXmlNS::dr3d;
194     if (nsURI == form)
195         return KoXmlNS::form;
196     if (nsURI == script)
197         return KoXmlNS::script;
198     if (nsURI == meta)
199         return KoXmlNS::meta;
200     if (nsURI == config)
201         return KoXmlNS::config;
202     if (nsURI == pres)
203         return KoXmlNS::presentation;
204     if (nsURI == manifest)
205         return KoXmlNS::manifest;
206     return nsURI;
207 }
208 
209 // ==================================================================
210 //
211 //         KoXmlPackedItem
212 //
213 // ==================================================================
214 
215 // 12 bytes on most system 32 bit systems, 16 bytes on 64 bit systems
216 class KoXmlPackedItem
217 {
218 public:
219 bool attr: 1;
220 KoXmlNode::NodeType type: 3;
221 
222 #ifdef KOXML_COMPACT
223 quint32 childStart: 28;
224 #else
225 unsigned depth: 28;
226 #endif
227 
228     unsigned qnameIndex;
229     QString value;
230 
231     // it is important NOT to have a copy constructor, so that growth is optimal
232     // see https://doc.qt.io/qt-5/containers.html#growth-strategies
233 #if 0
234     KoXmlPackedItem(): attr(false), type(KoXmlNode::NullNode), childStart(0), depth(0) {}
235 #endif
236 };
237 
238 Q_DECLARE_TYPEINFO(KoXmlPackedItem, Q_MOVABLE_TYPE);
239 
240 #ifdef KOXML_COMPRESS
operator <<(QDataStream & s,const KoXmlPackedItem & item)241 static QDataStream& operator<<(QDataStream& s, const KoXmlPackedItem& item)
242 {
243     quint8 flag = item.attr ? 1 : 0;
244 
245     s << flag;
246     s << (quint8) item.type;
247     s << item.childStart;
248     s << item.qnameIndex;
249     s << item.value;
250 
251     return s;
252 }
253 
operator >>(QDataStream & s,KoXmlPackedItem & item)254 static QDataStream& operator>>(QDataStream& s, KoXmlPackedItem& item)
255 {
256     quint8 flag;
257     quint8 type;
258     quint32 child;
259     QString value;
260 
261     s >> flag;
262     s >> type;
263     s >> child;
264     s >> item.qnameIndex;
265     s >> value;
266 
267     item.attr = (flag != 0);
268     item.type = (KoXmlNode::NodeType) type;
269     item.childStart = child;
270     item.value = value;
271 
272     return s;
273 }
274 #endif
275 
276 // ==================================================================
277 //
278 //         KoXmlPackedDocument
279 //
280 // ==================================================================
281 
282 #ifdef KOXML_COMPRESS
283 
284 #include "KoXmlVector.h"
285 
286 // when number of buffered items reach this, compression will start
287 // small value will give better memory usage at the cost of speed
288 // bigger value will be better in term of speed, but use more memory
289 #define ITEMS_FULL  (1*256)
290 
291 typedef KoXmlVector<KoXmlPackedItem, ITEMS_FULL> KoXmlPackedGroup;
292 #else
293 typedef QVector<KoXmlPackedItem> KoXmlPackedGroup;
294 #endif
295 
296 // growth strategy: increase every GROUP_GROW_SIZE items
297 // this will override standard QVector's growth strategy
298 #define GROUP_GROW_SHIFT 3
299 #define GROUP_GROW_SIZE (1 << GROUP_GROW_SHIFT)
300 
301 class KoXmlPackedDocument
302 {
303 public:
304     bool processNamespace;
305 #ifdef KOXML_COMPACT
306     // map given depth to the list of items
307     QHash<int, KoXmlPackedGroup> groups;
308 #else
309     QVector<KoXmlPackedItem> items;
310 #endif
311 
312     QList<KoQName> qnameList;
313     QString docType;
314 
315 private:
316     QHash<KoQName, unsigned> qnameHash;
317 
cacheQName(const QString & name,const QString & nsURI)318     unsigned cacheQName(const QString& name, const QString& nsURI) {
319         KoQName qname(nsURI, name);
320 
321         const unsigned ii = qnameHash.value(qname, (unsigned)-1);
322         if (ii != (unsigned)-1)
323             return ii;
324 
325         // not yet declared, so we add it
326         unsigned i = qnameList.count();
327         qnameList.append(qname);
328         qnameHash.insert(qname, i);
329 
330         return i;
331     }
332 
333     QHash<QString, unsigned> valueHash;
334     QStringList valueList;
335 
cacheValue(const QString & value)336     QString cacheValue(const QString& value) {
337         if (value.isEmpty())
338             return 0;
339 
340         const unsigned& ii = valueHash[value];
341         if (ii > 0)
342             return valueList[ii];
343 
344         // not yet declared, so we add it
345         unsigned i = valueList.count();
346         valueList.append(value);
347         valueHash.insert(value, i);
348 
349         return valueList[i];
350     }
351 
352 #ifdef KOXML_COMPACT
353 public:
itemAt(unsigned depth,unsigned index)354     const KoXmlPackedItem& itemAt(unsigned depth, unsigned index) {
355         const KoXmlPackedGroup& group = groups[depth];
356         return group[index];
357     }
358 
itemCount(unsigned depth)359     unsigned itemCount(unsigned depth) {
360         const KoXmlPackedGroup& group = groups[depth];
361         return group.count();
362     }
363 
364     /*
365        NOTE:
366           Function clear, newItem, addElement, addAttribute, addText,
367           addCData, addProcessing are all related. These are all necessary
368           for stateful manipulation of the document. See also the calls
369           to these function from parseDocument().
370 
371           The state itself is defined by the member variables
372           currentDepth and the groups (see above).
373      */
374 
375     unsigned currentDepth;
376 
newItem(unsigned depth)377     KoXmlPackedItem& newItem(unsigned depth) {
378         KoXmlPackedGroup& group = groups[depth];
379 
380 #ifdef KOXML_COMPRESS
381         KoXmlPackedItem& item = group.newItem();
382 #else
383         // reserve up front
384         if ((groups.size() % GROUP_GROW_SIZE) == 0)
385             group.reserve(GROUP_GROW_SIZE * (1 + (groups.size() >> GROUP_GROW_SHIFT)));
386         group.resize(group.count() + 1);
387 
388         KoXmlPackedItem& item = group[group.count()-1];
389 #endif
390 
391         // this is necessary, because intentionally we don't want to have
392         // a constructor for KoXmlPackedItem
393         item.attr = false;
394         item.type = KoXmlNode::NullNode;
395         item.qnameIndex = 0;
396         item.childStart = itemCount(depth + 1);
397         item.value.clear();
398 
399         return item;
400     }
401 
clear()402     void clear() {
403         currentDepth = 0;
404         qnameHash.clear();
405         qnameList.clear();
406         valueHash.clear();
407         valueList.clear();
408         groups.clear();
409         docType.clear();
410 
411         // first node is root
412         KoXmlPackedItem& rootItem = newItem(0);
413         rootItem.type = KoXmlNode::DocumentNode;
414     }
415 
finish()416     void finish() {
417         // won't be needed anymore
418         qnameHash.clear();
419         valueHash.clear();
420         valueList.clear();
421 
422         // optimize, see documentation on QVector::squeeze
423         for (int d = 0; d < groups.count(); ++d) {
424             KoXmlPackedGroup& group = groups[d];
425             group.squeeze();
426         }
427     }
428 
429     // in case namespace processing, 'name' contains the prefix already
addElement(const QString & name,const QString & nsURI)430     void addElement(const QString& name, const QString& nsURI) {
431         KoXmlPackedItem& item = newItem(currentDepth + 1);
432         item.type = KoXmlNode::ElementNode;
433         item.qnameIndex = cacheQName(name, nsURI);
434 
435         ++currentDepth;
436     }
437 
closeElement()438     void closeElement() {
439         --currentDepth;
440     }
441 
addDTD(const QString & dt)442     void addDTD(const QString& dt) {
443         docType = dt;
444     }
445 
addAttribute(const QString & name,const QString & nsURI,const QString & value)446     void addAttribute(const QString& name, const QString& nsURI, const QString& value) {
447         KoXmlPackedItem& item = newItem(currentDepth + 1);
448         item.attr = true;
449         item.qnameIndex = cacheQName(name, nsURI);
450         //item.value = cacheValue(value);
451         item.value = value;
452     }
453 
addText(const QString & text)454     void addText(const QString& text) {
455         KoXmlPackedItem& item = newItem(currentDepth + 1);
456         item.type = KoXmlNode::TextNode;
457         item.value = text;
458     }
459 
addCData(const QString & text)460     void addCData(const QString& text) {
461         KoXmlPackedItem& item = newItem(currentDepth + 1);
462         item.type = KoXmlNode::CDATASectionNode;
463         item.value = text;
464     }
465 
addProcessingInstruction()466     void addProcessingInstruction() {
467         KoXmlPackedItem& item = newItem(currentDepth + 1);
468         item.type = KoXmlNode::ProcessingInstructionNode;
469     }
470 
471 public:
KoXmlPackedDocument()472     KoXmlPackedDocument(): processNamespace(false), currentDepth(0) {
473         clear();
474     }
475 
476 #else
477 
478 private:
479     unsigned elementDepth;
480 
481 public:
482 
newItem()483     KoXmlPackedItem& newItem() {
484         unsigned count = items.count() + 512;
485         count = 1024 * (count >> 10);
486         items.reserve(count);
487 
488         items.resize(items.count() + 1);
489 
490         // this is necessary, because intentionally we don't want to have
491         // a constructor for KoXmlPackedItem
492         KoXmlPackedItem& item = items[items.count()-1];
493         item.attr = false;
494         item.type = KoXmlNode::NullNode;
495         item.qnameIndex = 0;
496         item.depth = 0;
497 
498         return item;
499     }
500 
addElement(const QString & name,const QString & nsURI)501     void addElement(const QString& name, const QString& nsURI) {
502         // we are going one level deeper
503         ++elementDepth;
504 
505         KoXmlPackedItem& item = newItem();
506 
507         item.attr = false;
508         item.type = KoXmlNode::ElementNode;
509         item.depth = elementDepth;
510         item.qnameIndex = cacheQName(name, nsURI);
511     }
512 
closeElement()513     void closeElement() {
514         // we are going up one level
515         --elementDepth;
516     }
517 
addDTD(const QString & dt)518     void addDTD(const QString& dt) {
519         docType = dt;
520     }
521 
addAttribute(const QString & name,const QString & nsURI,const QString & value)522     void addAttribute(const QString& name, const QString& nsURI, const QString& value) {
523         KoXmlPackedItem& item = newItem();
524 
525         item.attr = true;
526         item.type = KoXmlNode::NullNode;
527         item.depth = elementDepth;
528         item.qnameIndex = cacheQName(name, nsURI);
529         //item.value = cacheValue(value);
530         item.value = value;
531     }
532 
addText(const QString & str)533     void addText(const QString& str) {
534         KoXmlPackedItem& item = newItem();
535 
536         item.attr = false;
537         item.type = KoXmlNode::TextNode;
538         item.depth = elementDepth + 1;
539         item.qnameIndex = 0;
540         item.value = str;
541     }
542 
addCData(const QString & str)543     void addCData(const QString& str) {
544         KoXmlPackedItem& item = newItem();
545 
546         item.attr = false;
547         item.type = KoXmlNode::CDATASectionNode;
548         item.depth = elementDepth + 1;
549         item.qnameIndex = 0;
550         item.value = str;
551     }
552 
addProcessingInstruction()553     void addProcessingInstruction() {
554         KoXmlPackedItem& item = newItem();
555 
556         item.attr = false;
557         item.type = KoXmlNode::ProcessingInstructionNode;
558         item.depth = elementDepth + 1;
559         item.qnameIndex = 0;
560         item.value.clear();
561     }
562 
clear()563     void clear() {
564         qnameHash.clear();
565         qnameList.clear();
566         valueHash.clear();
567         valueList.clear();
568         items.clear();
569         elementDepth = 0;
570 
571         KoXmlPackedItem& rootItem = newItem();
572         rootItem.attr = false;
573         rootItem.type = KoXmlNode::DocumentNode;
574         rootItem.depth = 0;
575         rootItem.qnameIndex = 0;
576     }
577 
finish()578     void finish() {
579         qnameHash.clear();
580         valueList.clear();
581         valueHash.clear();
582         items.squeeze();
583     }
584 
KoXmlPackedDocument()585     KoXmlPackedDocument(): processNamespace(false), elementDepth(0) {
586     }
587 
588 #endif
589 
590 };
591 
592 namespace {
593 
594     class ParseError {
595     public:
596         QString errorMsg;
597         int errorLine;
598         int errorColumn;
599         bool error;
600 
ParseError()601         ParseError() :errorLine(-1), errorColumn(-1), error(false) {}
602     };
603 
604     void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true);
605 
606     // parse one element as if this were a standalone xml document
parseDocument(QXmlStreamReader & xml,KoXmlPackedDocument & doc,bool stripSpaces=true)607     ParseError parseDocument(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces = true)
608     {
609         doc.clear();
610         ParseError error;
611         xml.readNext();
612         while (!xml.atEnd() && xml.tokenType() != QXmlStreamReader::EndDocument && !xml.hasError()) {
613             switch (xml.tokenType()) {
614             case QXmlStreamReader::StartElement:
615                 parseElement(xml, doc, stripSpaces);
616                 break;
617             case QXmlStreamReader::DTD:
618                 doc.addDTD(xml.dtdName().toString());
619                 break;
620             case QXmlStreamReader::StartDocument:
621                 if (!xml.documentEncoding().isEmpty() || !xml.documentVersion().isEmpty()) {
622                     doc.addProcessingInstruction();
623                 }
624                 break;
625             case QXmlStreamReader::ProcessingInstruction:
626                 doc.addProcessingInstruction();
627                 break;
628             default:
629                 break;
630             }
631             xml.readNext();
632         }
633         if (xml.hasError()) {
634             error.error = true;
635             error.errorMsg = xml.errorString();
636             error.errorColumn = xml.columnNumber();
637             error.errorLine = xml.lineNumber();
638         } else {
639             doc.finish();
640         }
641         return error;
642     }
643 
parseElementContents(QXmlStreamReader & xml,KoXmlPackedDocument & doc)644     void parseElementContents(QXmlStreamReader &xml, KoXmlPackedDocument &doc)
645     {
646         xml.readNext();
647         QString ws;
648         while (!xml.atEnd()) {
649             switch (xml.tokenType()) {
650             case QXmlStreamReader::EndElement:
651                 // if an element contains only whitespace, put it in the dom
652                 if (!ws.isEmpty()) {
653                     doc.addText(ws);
654                 }
655                 return;
656             case QXmlStreamReader::StartElement:
657                 // The whitespaces between > and < are also a text element
658                 if (!ws.isEmpty()) {
659                     doc.addText(ws);
660                     ws.clear();
661                 }
662                 // Do not strip spaces
663                 parseElement(xml, doc, false);
664                 break;
665             case QXmlStreamReader::Characters:
666                 if (xml.isCDATA()) {
667                     doc.addCData(xml.text().toString());
668                 } else if (!xml.isWhitespace()) {
669                     doc.addText(xml.text().toString());
670                 } else {
671                     ws += xml.text();
672                 }
673                 break;
674             case QXmlStreamReader::ProcessingInstruction:
675                 doc.addProcessingInstruction();
676                 break;
677             default:
678                 break;
679             }
680             xml.readNext();
681         }
682     }
683 
parseElementContentsStripSpaces(QXmlStreamReader & xml,KoXmlPackedDocument & doc)684     void parseElementContentsStripSpaces(QXmlStreamReader &xml, KoXmlPackedDocument &doc)
685     {
686         xml.readNext();
687         QString ws;
688         bool sawElement = false;
689         while (!xml.atEnd()) {
690             switch (xml.tokenType()) {
691             case QXmlStreamReader::EndElement:
692                 // if an element contains only whitespace, put it in the dom
693                 if (!ws.isEmpty() && !sawElement) {
694                     doc.addText(ws);
695                 }
696                 return;
697             case QXmlStreamReader::StartElement:
698                 sawElement = true;
699                 // Do strip spaces
700                 parseElement(xml, doc, true);
701                 break;
702             case QXmlStreamReader::Characters:
703                 if (xml.isCDATA()) {
704                     doc.addCData(xml.text().toString());
705                 } else if (!xml.isWhitespace()) {
706                     doc.addText(xml.text().toString());
707                 } else if (!sawElement) {
708                     ws += xml.text();
709                 }
710                 break;
711             case QXmlStreamReader::ProcessingInstruction:
712                 doc.addProcessingInstruction();
713                 break;
714             default:
715                 break;
716             }
717             xml.readNext();
718         }
719     }
720 
parseElement(QXmlStreamReader & xml,KoXmlPackedDocument & doc,bool stripSpaces)721     void parseElement(QXmlStreamReader &xml, KoXmlPackedDocument &doc, bool stripSpaces)
722     {
723         // Unfortunately MSVC fails using QXmlStreamReader::const_iterator
724         // so we apply a for loop instead. https://bugreports.qt.io/browse/QTBUG-45368
725         doc.addElement(xml.qualifiedName().toString(),
726                        fixNamespace(xml.namespaceUri().toString()));
727         QXmlStreamAttributes attr = xml.attributes();
728         for  (int a = 0; a < attr.count(); a++) {
729             doc.addAttribute(attr[a].qualifiedName().toString(),
730                              attr[a].namespaceUri().toString(),
731                              attr[a].value().toString());
732         }
733         if (stripSpaces)
734           parseElementContentsStripSpaces(xml, doc);
735         else
736           parseElementContents(xml, doc);
737         // reader.tokenType() is now QXmlStreamReader::EndElement
738         doc.closeElement();
739     }
740 }
741 
742 
743 // ==================================================================
744 //
745 //         KoXmlNodeData
746 //
747 // ==================================================================
748 
749 class KoXmlNodeData
750 {
751 public:
752 
753     explicit KoXmlNodeData(unsigned long initialRefCount = 1);
754     ~KoXmlNodeData();
755 
756     // generic properties
757     KoXmlNode::NodeType nodeType;
758     bool loaded;
759 
760 #ifdef KOXML_COMPACT
761     unsigned nodeDepth;
762 #endif
763 
764     QString tagName;
765     QString namespaceURI;
766     QString prefix;
767     QString localName;
768 
ref()769     void ref() {
770         ++refCount;
771     }
unref()772     void unref() {
773         if (!--refCount) {
774             delete this;
775         }
776     }
777 
778     // type information
779     QString nodeName() const;
780 
781     // for tree and linked-list
782     KoXmlNodeData* parent;
783     KoXmlNodeData* prev;
784     KoXmlNodeData* next;
785     KoXmlNodeData* first;
786     KoXmlNodeData* last;
787 
788     QString text();
789 
790     // node manipulation
791     void clear();
792 
793     // attributes
794     inline void setAttribute(const QString& name, const QString& value);
795     inline QString attribute(const QString& name, const QString& def) const;
796     inline bool hasAttribute(const QString& name) const;
797     inline void setAttributeNS(const QString& nsURI, const QString& name, const QString& value);
798     inline QString attributeNS(const QString& nsURI, const QString& name, const QString& def) const;
799     inline bool hasAttributeNS(const QString& nsURI, const QString& name) const;
800     inline void clearAttributes();
801     inline QStringList attributeNames() const;
802     inline QList< QPair<QString, QString> > attributeFullNames() const;
803 
804 
805     // for text and CDATA
806     QString data() const;
807 
808     // reference from within the packed doc
809     KoXmlPackedDocument* packedDoc;
810     unsigned long nodeIndex;
811 
812     // used when doing on-demand (re)parse
813     void loadChildren(int depth = 1);
814     void unloadChildren();
815 
816     void dump();
817 
818     static KoXmlNodeData null;
819 
820     // compatibility
821     void asQDomNode(QDomDocument& ownerDoc) const;
822 
823 private:
824     QHash<QString, QString> attr;
825     QHash<KoXmlStringPair, QString> attrNS;
826     QString textData;
827     // reference counting
828     unsigned long refCount;
829     friend class KoXmlElement;
830 };
831 
832 KoXmlNodeData KoXmlNodeData::null;
833 
834 
KoXmlNodeData(unsigned long initialRefCount)835 KoXmlNodeData::KoXmlNodeData(unsigned long initialRefCount)
836     : nodeType(KoXmlNode::NullNode)
837     , loaded(false)
838 #ifdef KOXML_COMPACT
839     , nodeDepth(0)
840 #endif
841     , parent(0), prev(0), next(0), first(0), last(0)
842     , packedDoc(0), nodeIndex(0)
843     , refCount(initialRefCount)
844 {
845 }
846 
~KoXmlNodeData()847 KoXmlNodeData::~KoXmlNodeData()
848 {
849     clear();
850 }
851 
clear()852 void KoXmlNodeData::clear()
853 {
854     if (first)
855         for (KoXmlNodeData* node = first; node ;) {
856             KoXmlNodeData* next = node->next;
857             node->unref();
858             node = next;
859         }
860 
861     // only document can delete these
862     // normal nodes don't "own" them
863     if (nodeType == KoXmlNode::DocumentNode)
864         delete packedDoc;
865 
866     nodeType = KoXmlNode::NullNode;
867     tagName.clear();
868     prefix.clear();
869     namespaceURI.clear();
870     textData.clear();
871     packedDoc = 0;
872 
873     attr.clear();
874     attrNS.clear();
875 
876     parent = 0;
877     prev = next = 0;
878     first = last = 0;
879 
880     loaded = false;
881 }
882 
text()883 QString KoXmlNodeData::text()
884 {
885     QString t;
886 
887     loadChildren();
888 
889     KoXmlNodeData* node = first;
890     while (node) {
891         switch (node->nodeType) {
892         case KoXmlNode::ElementNode:
893             t += node->text(); break;
894         case KoXmlNode::TextNode:
895             t += node->data(); break;
896         case KoXmlNode::CDATASectionNode:
897             t += node->data(); break;
898         default: break;
899         }
900         node = node->next;
901     }
902 
903     return t;
904 }
905 
nodeName() const906 QString KoXmlNodeData::nodeName() const
907 {
908     switch (nodeType) {
909     case KoXmlNode::ElementNode: {
910         QString n(tagName);
911         if (!prefix.isEmpty())
912             n.prepend(':').prepend(prefix);
913         return n;
914     }
915     break;
916 
917     case KoXmlNode::TextNode:         return QLatin1String("#text");
918     case KoXmlNode::CDATASectionNode: return QLatin1String("#cdata-section");
919     case KoXmlNode::DocumentNode:     return QLatin1String("#document");
920     case KoXmlNode::DocumentTypeNode: return tagName;
921 
922     default: return QString(); break;
923     }
924 
925     // should not happen
926     return QString();
927 }
928 
setAttribute(const QString & name,const QString & value)929 void KoXmlNodeData::setAttribute(const QString& name, const QString& value)
930 {
931     attr.insert(name, value);
932 }
933 
attribute(const QString & name,const QString & def) const934 QString KoXmlNodeData::attribute(const QString& name, const QString& def) const
935 {
936     return attr.value(name, def);
937 }
938 
hasAttribute(const QString & name) const939 bool KoXmlNodeData::hasAttribute(const QString& name) const
940 {
941     return attr.contains(name);
942 }
943 
setAttributeNS(const QString & nsURI,const QString & name,const QString & value)944 void KoXmlNodeData::setAttributeNS(const QString& nsURI,
945                                    const QString& name, const QString& value)
946 {
947     int i = name.indexOf(':');
948     if (i != -1) {
949         QString localName(name.mid(i + 1));
950         KoXmlStringPair key(nsURI, localName);
951         attrNS.insert(key, value);
952     }
953 }
954 
attributeNS(const QString & nsURI,const QString & name,const QString & def) const955 QString KoXmlNodeData::attributeNS(const QString& nsURI, const QString& name,
956                                    const QString& def) const
957 {
958     KoXmlStringPair key(nsURI, name);
959     return attrNS.value(key, def);
960 }
961 
hasAttributeNS(const QString & nsURI,const QString & name) const962 bool KoXmlNodeData::hasAttributeNS(const QString& nsURI, const QString& name) const
963 {
964     KoXmlStringPair key(nsURI, name);
965     return attrNS.contains(key);
966 }
967 
clearAttributes()968 void KoXmlNodeData::clearAttributes()
969 {
970     attr.clear();
971     attrNS.clear();
972 }
973 
974 // FIXME how about namespaced attributes ?
attributeNames() const975 QStringList KoXmlNodeData::attributeNames() const
976 {
977     QStringList result;
978     result = attr.keys();
979 
980     return result;
981 }
982 
983 
attributeFullNames() const984 QList< QPair<QString, QString> > KoXmlNodeData::attributeFullNames() const
985 {
986     QList< QPair<QString, QString> > result;
987     result = attrNS.keys();
988 
989     return result;
990 }
991 
data() const992 QString KoXmlNodeData::data() const
993 {
994     return textData;
995 }
996 
997 #ifdef KOXML_COMPACT
998 
loadChildren(int depth)999 void KoXmlNodeData::loadChildren(int depth)
1000 {
1001     // sanity check
1002     if (!packedDoc) return;
1003 
1004     // already loaded ?
1005     if (loaded && (depth <= 1)) return;
1006 
1007     // in case depth is different
1008     unloadChildren();
1009 
1010 
1011     KoXmlNodeData* lastDat = 0;
1012 
1013     unsigned childStop = 0;
1014     if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1)
1015         childStop = packedDoc->itemCount(nodeDepth + 1);
1016     else {
1017         const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1);
1018         childStop = next.childStart;
1019     }
1020 
1021     const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex);
1022 
1023     for (unsigned i = self.childStart; i < childStop; ++i) {
1024         const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i);
1025         bool textItem = (item.type == KoXmlNode::TextNode);
1026         textItem |= (item.type == KoXmlNode::CDATASectionNode);
1027 
1028         // attribute belongs to this node
1029         if (item.attr) {
1030             KoQName qname = packedDoc->qnameList[item.qnameIndex];
1031             QString value = item.value;
1032 
1033             QString prefix;
1034 
1035             QString qName; // with prefix
1036             QString localName;  // without prefix, i.e. local name
1037 
1038             localName = qName = qname.name;
1039             int i = qName.indexOf(':');
1040             if (i != -1) prefix = qName.left(i);
1041             if (i != -1) localName = qName.mid(i + 1);
1042 
1043             if (packedDoc->processNamespace) {
1044                 setAttributeNS(qname.nsURI, qName, value);
1045                 setAttribute(localName, value);
1046             } else
1047                 setAttribute(qName, value);
1048         } else {
1049             KoQName qname = packedDoc->qnameList[item.qnameIndex];
1050             QString value = item.value;
1051 
1052             QString nodeName = qname.name;
1053             QString localName;
1054             QString prefix;
1055 
1056             if (packedDoc->processNamespace) {
1057                 localName = qname.name;
1058                 int di = qname.name.indexOf(':');
1059                 if (di != -1) {
1060                     localName = qname.name.mid(di + 1);
1061                     prefix = qname.name.left(di);
1062                 }
1063                 nodeName = localName;
1064             }
1065 
1066             // make a node out of this item
1067             KoXmlNodeData* dat = new KoXmlNodeData;
1068             dat->nodeIndex = i;
1069             dat->packedDoc = packedDoc;
1070             dat->nodeDepth = nodeDepth + 1;
1071             dat->nodeType = item.type;
1072             dat->tagName = nodeName;
1073             dat->localName = localName;
1074             dat->prefix = prefix;
1075             dat->namespaceURI = qname.nsURI;
1076             dat->parent = this;
1077             dat->prev = lastDat;
1078             dat->next = 0;
1079             dat->first = 0;
1080             dat->last = 0;
1081             dat->loaded = false;
1082             dat->textData = (textItem) ? value : QString();
1083 
1084             // adjust our linked-list
1085             first = (first) ? first : dat;
1086             last = dat;
1087             if (lastDat)
1088                 lastDat->next = dat;
1089             lastDat = dat;
1090 
1091             // recursive
1092             if (depth > 1)
1093                 dat->loadChildren(depth - 1);
1094         }
1095     }
1096 
1097     loaded = true;
1098 }
1099 
1100 #else
1101 
loadChildren(int depth)1102 void KoXmlNodeData::loadChildren(int depth)
1103 {
1104     // sanity check
1105     if (!packedDoc) return;
1106 
1107     // already loaded ?
1108     if (loaded && (depth <= 1)) return;
1109 
1110     // cause we don't know how deep this node's children already loaded are
1111     unloadChildren();
1112 
1113     KoXmlNodeData* lastDat = 0;
1114     int nodeDepth = packedDoc->items[nodeIndex].depth;
1115 
1116     for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) {
1117         KoXmlPackedItem& item = packedDoc->items[i];
1118         bool textItem = (item.type == KoXmlNode::TextNode);
1119         textItem |= (item.type == KoXmlNode::CDATASectionNode);
1120 
1121         // element already outside our depth
1122         if (!item.attr && (item.type == KoXmlNode::ElementNode))
1123             if (item.depth <= (unsigned)nodeDepth)
1124                 break;
1125 
1126         // attribute belongs to this node
1127         if (item.attr && (item.depth == (unsigned)nodeDepth)) {
1128             KoQName qname = packedDoc->qnameList[item.qnameIndex];
1129             QString value = item.value;
1130 
1131             QString prefix;
1132 
1133             QString qName; // with prefix
1134             QString localName;  // without prefix, i.e. local name
1135 
1136             localName = qName = qname.name;
1137             int i = qName.indexOf(':');
1138             if (i != -1) prefix = qName.left(i);
1139             if (i != -1) localName = qName.mid(i + 1);
1140 
1141             if (packedDoc->processNamespace) {
1142                 setAttributeNS(qname.nsURI, qName, value);
1143                 setAttribute(localName, value);
1144             } else
1145                 setAttribute(qname.name, value);
1146         }
1147 
1148         // the child node
1149         if (!item.attr) {
1150             bool instruction = (item.type == KoXmlNode::ProcessingInstructionNode);
1151             bool ok = (textItem || instruction)  ? (item.depth == (unsigned)nodeDepth) : (item.depth == (unsigned)nodeDepth + 1);
1152 
1153             ok = (item.depth == (unsigned)nodeDepth + 1);
1154 
1155             if (ok) {
1156                 KoQName qname = packedDoc->qnameList[item.qnameIndex];
1157                 QString value = item.value;
1158 
1159                 QString nodeName = qname.name;
1160                 QString localName;
1161                 QString prefix;
1162 
1163                 if (packedDoc->processNamespace) {
1164                     localName = qname.name;
1165                     int di = qname.name.indexOf(':');
1166                     if (di != -1) {
1167                         localName = qname.name.mid(di + 1);
1168                         prefix = qname.name.left(di);
1169                     }
1170                     nodeName = localName;
1171                 }
1172 
1173                 // make a node out of this item
1174                 KoXmlNodeData* dat = new KoXmlNodeData;
1175                 dat->nodeIndex = i;
1176                 dat->packedDoc = packedDoc;
1177                 dat->nodeType = item.type;
1178                 dat->tagName = nodeName;
1179                 dat->localName = localName;
1180                 dat->prefix = prefix;
1181                 dat->namespaceURI = qname.nsURI;
1182                 dat->count = 1;
1183                 dat->parent = this;
1184                 dat->prev = lastDat;
1185                 dat->next = 0;
1186                 dat->first = 0;
1187                 dat->last = 0;
1188                 dat->loaded = false;
1189                 dat->textData = (textItem) ? value : QString();
1190 
1191                 // adjust our linked-list
1192                 first = (first) ? first : dat;
1193                 last = dat;
1194                 if (lastDat)
1195                     lastDat->next = dat;
1196                 lastDat = dat;
1197 
1198                 // recursive
1199                 if (depth > 1)
1200                     dat->loadChildren(depth - 1);
1201             }
1202         }
1203     }
1204 
1205     loaded = true;
1206 }
1207 #endif
1208 
unloadChildren()1209 void KoXmlNodeData::unloadChildren()
1210 {
1211     // sanity check
1212     if (!packedDoc) return;
1213 
1214     if (!loaded) return;
1215 
1216     if (first)
1217         for (KoXmlNodeData* node = first; node ;) {
1218             KoXmlNodeData* next = node->next;
1219             node->unloadChildren();
1220             node->unref();
1221             node = next;
1222         }
1223 
1224     clearAttributes();
1225     loaded = false;
1226     first = last = 0;
1227 }
1228 
1229 #ifdef KOXML_COMPACT
1230 
1231 
itemAsQDomNode(QDomDocument & ownerDoc,KoXmlPackedDocument * packedDoc,unsigned nodeDepth,unsigned nodeIndex,QDomNode parentNode=QDomNode ())1232 static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc,
1233                                unsigned nodeDepth, unsigned nodeIndex, QDomNode parentNode = QDomNode())
1234 {
1235     // sanity check
1236     if (!packedDoc)
1237         return;
1238 
1239     const KoXmlPackedItem& self = packedDoc->itemAt(nodeDepth, nodeIndex);
1240 
1241     unsigned childStop = 0;
1242     if (nodeIndex == packedDoc->itemCount(nodeDepth) - 1)
1243         childStop = packedDoc->itemCount(nodeDepth + 1);
1244     else {
1245         const KoXmlPackedItem& next = packedDoc->itemAt(nodeDepth, nodeIndex + 1);
1246         childStop = next.childStart;
1247     }
1248 
1249     // nothing to do here
1250     if (self.type == KoXmlNode::NullNode)
1251         return;
1252 
1253     // create the element properly
1254     if (self.type == KoXmlNode::ElementNode) {
1255         QDomElement element;
1256 
1257         KoQName qname = packedDoc->qnameList[self.qnameIndex];
1258         qname.nsURI = fixNamespace(qname.nsURI);
1259 
1260         if (packedDoc->processNamespace)
1261             element = ownerDoc.createElementNS(qname.nsURI, qname.name);
1262         else
1263             element = ownerDoc.createElement(qname.name);
1264 
1265         if (parentNode.isNull()) {
1266             ownerDoc.appendChild(element);
1267         } else {
1268             parentNode.appendChild(element);
1269         }
1270         // check all subnodes for attributes
1271         for (unsigned i = self.childStart; i < childStop; ++i) {
1272             const KoXmlPackedItem& item = packedDoc->itemAt(nodeDepth + 1, i);
1273             bool textItem = (item.type == KoXmlNode::TextNode);
1274             textItem |= (item.type == KoXmlNode::CDATASectionNode);
1275 
1276             // attribute belongs to this node
1277             if (item.attr) {
1278                 KoQName qname = packedDoc->qnameList[item.qnameIndex];
1279                 qname.nsURI = fixNamespace(qname.nsURI);
1280                 QString value = item.value;
1281 
1282                 QString prefix;
1283 
1284                 QString qName; // with prefix
1285                 QString localName;  // without prefix, i.e. local name
1286 
1287                 localName = qName = qname.name;
1288                 int i = qName.indexOf(':');
1289                 if (i != -1) prefix = qName.left(i);
1290                 if (i != -1) localName = qName.mid(i + 1);
1291 
1292                 if (packedDoc->processNamespace) {
1293                     element.setAttributeNS(qname.nsURI, qName, value);
1294                     element.setAttribute(localName, value);
1295                 } else
1296                     element.setAttribute(qname.name, value);
1297             } else {
1298                 // add it recursively
1299                 itemAsQDomNode(ownerDoc, packedDoc, nodeDepth + 1, i, element);
1300             }
1301         }
1302         return;
1303     }
1304 
1305     // create the text node
1306     if (self.type == KoXmlNode::TextNode) {
1307         QString text = self.value;
1308 
1309         // FIXME: choose CDATA when the value contains special characters
1310         QDomText textNode = ownerDoc.createTextNode(text);
1311         if (parentNode.isNull()) {
1312             ownerDoc.appendChild(textNode);
1313         } else {
1314             parentNode.appendChild(textNode);
1315         }
1316         return;
1317     }
1318     // nothing matches? strange...
1319 }
1320 
asQDomNode(QDomDocument & ownerDoc) const1321 void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const
1322 {
1323     itemAsQDomNode(ownerDoc, packedDoc, nodeDepth, nodeIndex);
1324 }
1325 
1326 #else
1327 
itemAsQDomNode(QDomDocument & ownerDoc,KoXmlPackedDocument * packedDoc,unsigned nodeIndex,QDomNode parentNode=QDomNode ())1328 static void itemAsQDomNode(QDomDocument& ownerDoc, KoXmlPackedDocument* packedDoc,
1329                                unsigned nodeIndex, QDomNode parentNode = QDomNode())
1330 {
1331     // sanity check
1332     if (!packedDoc)
1333         return;
1334 
1335     KoXmlPackedItem& item = packedDoc->items[nodeIndex];
1336 
1337     // nothing to do here
1338     if (item.type == KoXmlNode::NullNode)
1339         return;
1340 
1341     // create the element properly
1342     if (item.type == KoXmlNode::ElementNode) {
1343         QDomElement element;
1344 
1345         KoQName qname = packedDoc->qnameList[item.qnameIndex];
1346         qname.nsURI = fixNamespace(qname.nsURI);
1347 
1348         if (packedDoc->processNamespace)
1349             element = ownerDoc.createElementNS(qname.nsURI, qname.name);
1350         else
1351             element = ownerDoc.createElement(qname.name);
1352 
1353         if (parentNode.isNull()) {
1354             ownerDoc.appendChild(element);
1355         } else {
1356             parentNode.appendChild(element);
1357         }
1358         // check all subnodes for attributes
1359         int nodeDepth = item.depth;
1360         for (int i = nodeIndex + 1; i < packedDoc->items.count(); ++i) {
1361             KoXmlPackedItem& item = packedDoc->items[i];
1362             bool textItem = (item.type == KoXmlNode::TextNode);
1363             textItem |= (item.type == KoXmlNode::CDATASectionNode);
1364 
1365             // element already outside our depth
1366             if (!item.attr && (item.type == KoXmlNode::ElementNode))
1367                 if (item.depth <= (unsigned)nodeDepth)
1368                     break;
1369 
1370             // attribute belongs to this node
1371             if (item.attr && (item.depth == (unsigned)nodeDepth)) {
1372                 KoQName qname = packedDoc->qnameList[item.qnameIndex];
1373                 qname.nsURI = fixNamespace(qname.nsURI);
1374                 QString value = item.value;
1375                 QString prefix;
1376 
1377                 QString qName; // with prefix
1378                 QString localName;  // without prefix, i.e. local name
1379 
1380                 localName = qName = qname.name;
1381                 int i = qName.indexOf(':');
1382                 if (i != -1) prefix = qName.left(i);
1383                 if (i != -1) localName = qName.mid(i + 1);
1384 
1385                 if (packedDoc->processNamespace) {
1386                     element.setAttributeNS(qname.nsURI, qName, value);
1387                     element.setAttribute(localName, value);
1388                 } else
1389                     element.setAttribute(qname.name, value);
1390             }
1391 
1392             // direct child of this node
1393             if (!item.attr && (item.depth == (unsigned)nodeDepth + 1)) {
1394                 // add it recursively
1395                 itemAsQDomNode(ownerDoc, packedDoc, i, element);
1396             }
1397         }
1398         return;
1399     }
1400 
1401     // create the text node
1402     if (item.type == KoXmlNode::TextNode) {
1403         QString text = item.value;
1404         // FIXME: choose CDATA when the value contains special characters
1405         QDomText textNode = ownerDoc.createTextNode(text);
1406         if (parentNode.isNull()) {
1407             ownerDoc.appendChild(textNode);
1408         } else {
1409             parentNode.appendChild(textNode);
1410         }
1411         return;
1412     }
1413 
1414     // nothing matches? strange...
1415 }
1416 
asQDomNode(QDomDocument & ownerDoc) const1417 void KoXmlNodeData::asQDomNode(QDomDocument& ownerDoc) const
1418 {
1419     itemAsQDomNode(ownerDoc, packedDoc, nodeIndex);
1420 }
1421 
1422 #endif
1423 
dump()1424 void KoXmlNodeData::dump()
1425 {
1426     printf("NodeData %p\n", (void*)this);
1427 
1428     printf("  nodeIndex: %d\n", (int)nodeIndex);
1429     printf("  packedDoc: %p\n", (void*)packedDoc);
1430 
1431     printf("  nodeType : %d\n", (int)nodeType);
1432     printf("  tagName: %s\n", qPrintable(tagName));
1433     printf("  namespaceURI: %s\n", qPrintable(namespaceURI));
1434     printf("  prefix: %s\n", qPrintable(prefix));
1435     printf("  localName: %s\n", qPrintable(localName));
1436 
1437     printf("  parent : %p\n", (void*)parent);
1438     printf("  prev : %p\n", (void*)prev);
1439     printf("  next : %p\n", (void*)next);
1440     printf("  first : %p\n", (void*)first);
1441     printf("  last : %p\n", (void*)last);
1442 
1443     printf("  refCount: %ld\n", refCount);
1444 
1445     if (loaded)
1446         printf("  loaded: TRUE\n");
1447     else
1448         printf("  loaded: FALSE\n");
1449 }
1450 
1451 
1452 // ==================================================================
1453 //
1454 //         KoXmlNodeData
1455 //
1456 // ==================================================================
1457 
1458 class KoXmlDocumentData : public KoXmlNodeData
1459 {
1460 public:
1461 
1462     KoXmlDocumentData(unsigned long initialRefCount = 1);
1463     ~KoXmlDocumentData();
1464 
1465     bool setContent(QXmlStreamReader *reader,
1466                     QString* errorMsg = 0, int* errorLine = 0, int* errorColumn = 0);
1467 
1468     KoXmlDocumentType dt;
1469 
1470     bool emptyDocument :1;
1471     // to read the xml with or without spaces
1472     bool stripSpaces :1;
1473 };
1474 
1475 #define KOXMLDOCDATA(d)  static_cast<KoXmlDocumentData*>(d)
1476 
1477 
KoXmlDocumentData(unsigned long initialRefCount)1478 KoXmlDocumentData::KoXmlDocumentData(unsigned long initialRefCount)
1479     : KoXmlNodeData(initialRefCount)
1480     , emptyDocument(true)
1481     , stripSpaces(true)
1482 {
1483 }
1484 
~KoXmlDocumentData()1485 KoXmlDocumentData::~KoXmlDocumentData()
1486 {
1487 }
1488 
setContent(QXmlStreamReader * reader,QString * errorMsg,int * errorLine,int * errorColumn)1489 bool KoXmlDocumentData::setContent(QXmlStreamReader* reader, QString* errorMsg, int* errorLine, int* errorColumn)
1490 {
1491     // sanity checks
1492     if (!reader) return false;
1493 
1494     if (nodeType != KoXmlNode::DocumentNode)
1495         return false;
1496 
1497     clear();
1498     nodeType = KoXmlNode::DocumentNode;
1499 
1500     packedDoc = new KoXmlPackedDocument;
1501     packedDoc->processNamespace = reader->namespaceProcessing();
1502 
1503     ParseError error = parseDocument(*reader, *packedDoc, stripSpaces);
1504     if (error.error) {
1505         // parsing error has occurred
1506         if (errorMsg) *errorMsg = error.errorMsg;
1507         if (errorLine) *errorLine = error.errorLine;
1508         if (errorColumn)  *errorColumn = error.errorColumn;
1509         return false;
1510     }
1511 
1512     // initially load
1513     loadChildren();
1514 
1515     KoXmlNodeData *typeData = new KoXmlNodeData(0);
1516     typeData->nodeType = KoXmlNode::DocumentTypeNode;
1517     typeData->tagName = packedDoc->docType;
1518     typeData->parent = this;
1519     dt = KoXmlDocumentType(typeData);
1520 
1521     return true;
1522 }
1523 
1524 // ==================================================================
1525 //
1526 //         KoXmlNode
1527 //
1528 // ==================================================================
1529 
1530 // Creates a null node
KoXmlNode()1531 KoXmlNode::KoXmlNode()
1532 {
1533     d = &KoXmlNodeData::null;
1534     d->ref();
1535 }
1536 
1537 // Destroys this node
~KoXmlNode()1538 KoXmlNode::~KoXmlNode()
1539 {
1540     d->unref();
1541 }
1542 
1543 // Creates a copy of another node
KoXmlNode(const KoXmlNode & node)1544 KoXmlNode::KoXmlNode(const KoXmlNode& node)
1545 {
1546     d = node.d;
1547     d->ref();
1548 }
1549 
1550 // Creates a node for specific implementation
KoXmlNode(KoXmlNodeData * data)1551 KoXmlNode::KoXmlNode(KoXmlNodeData* data)
1552 {
1553     d = data;
1554     data->ref();
1555 }
1556 
1557 // Creates a shallow copy of another node
operator =(const KoXmlNode & node)1558 KoXmlNode& KoXmlNode::operator=(const KoXmlNode & node)
1559 {
1560     if (this != &node) {
1561         d->unref();
1562         d = node.d;
1563         d->ref();
1564     }
1565     return *this;
1566 }
1567 
1568 // Note: two null nodes are always equal
operator ==(const KoXmlNode & node) const1569 bool KoXmlNode::operator==(const KoXmlNode& node) const
1570 {
1571     if (isNull() && node.isNull()) return true;
1572     return(d == node.d);
1573 }
1574 
1575 // Note: two null nodes are always equal
operator !=(const KoXmlNode & node) const1576 bool KoXmlNode::operator!=(const KoXmlNode& node) const
1577 {
1578     if (isNull() && !node.isNull()) return true;
1579     if (!isNull() && node.isNull()) return true;
1580     if (isNull() && node.isNull()) return false;
1581     return(d != node.d);
1582 }
1583 
nodeType() const1584 KoXmlNode::NodeType KoXmlNode::nodeType() const
1585 {
1586     return d->nodeType;
1587 }
1588 
isNull() const1589 bool KoXmlNode::isNull() const
1590 {
1591     return d->nodeType == NullNode;
1592 }
1593 
isElement() const1594 bool KoXmlNode::isElement() const
1595 {
1596     return d->nodeType == ElementNode;
1597 }
1598 
isText() const1599 bool KoXmlNode::isText() const
1600 {
1601     return (d->nodeType == TextNode) || isCDATASection();
1602 }
1603 
isCDATASection() const1604 bool KoXmlNode::isCDATASection() const
1605 {
1606     return d->nodeType == CDATASectionNode;
1607 }
1608 
isDocument() const1609 bool KoXmlNode::isDocument() const
1610 {
1611     return d->nodeType == DocumentNode;
1612 }
1613 
isDocumentType() const1614 bool KoXmlNode::isDocumentType() const
1615 {
1616     return d->nodeType == DocumentTypeNode;
1617 }
1618 
clear()1619 void KoXmlNode::clear()
1620 {
1621     d->unref();
1622     d = new KoXmlNodeData;
1623 }
1624 
nodeName() const1625 QString KoXmlNode::nodeName() const
1626 {
1627     return d->nodeName();
1628 }
1629 
prefix() const1630 QString KoXmlNode::prefix() const
1631 {
1632     return isElement() ? d->prefix : QString();
1633 }
1634 
namespaceURI() const1635 QString KoXmlNode::namespaceURI() const
1636 {
1637     return isElement() ? d->namespaceURI : QString();
1638 }
1639 
localName() const1640 QString KoXmlNode::localName() const
1641 {
1642     return isElement() ? d->localName : QString();
1643 }
1644 
ownerDocument() const1645 KoXmlDocument KoXmlNode::ownerDocument() const
1646 {
1647     KoXmlNodeData* node = d;
1648     while (node->parent) node = node->parent;
1649 
1650     if (node->nodeType == DocumentNode) {
1651         return KoXmlDocument(static_cast<KoXmlDocumentData*>(node));
1652     }
1653     return KoXmlDocument();
1654 }
1655 
parentNode() const1656 KoXmlNode KoXmlNode::parentNode() const
1657 {
1658     return d->parent ? KoXmlNode(d->parent) : KoXmlNode();
1659 }
1660 
hasChildNodes() const1661 bool KoXmlNode::hasChildNodes() const
1662 {
1663     if (isText())
1664         return false;
1665 
1666     if (!d->loaded)
1667         d->loadChildren();
1668 
1669     return d->first != 0 ;
1670 }
1671 
childNodesCount() const1672 int KoXmlNode::childNodesCount() const
1673 {
1674     if (isText())
1675         return 0;
1676 
1677     if (!d->loaded)
1678         d->loadChildren();
1679 
1680     KoXmlNodeData* node = d->first;
1681     int count = 0;
1682     while (node) {
1683         ++count;
1684         node = node->next;
1685     }
1686 
1687     return count;
1688 }
1689 
attributeNames() const1690 QStringList KoXmlNode::attributeNames() const
1691 {
1692     if (!d->loaded)
1693         d->loadChildren();
1694 
1695     return d->attributeNames();
1696 }
1697 
attributeFullNames() const1698 QList< QPair<QString, QString> > KoXmlNode::attributeFullNames() const
1699 {
1700     if (!d->loaded)
1701         d->loadChildren();
1702 
1703     return d->attributeFullNames();
1704 }
1705 
firstChild() const1706 KoXmlNode KoXmlNode::firstChild() const
1707 {
1708     if (!d->loaded)
1709         d->loadChildren();
1710     return d->first ? KoXmlNode(d->first) : KoXmlNode();
1711 }
1712 
firstChildElement() const1713 KoXmlElement KoXmlNode::firstChildElement() const
1714 {
1715     KoXmlElement element;
1716     forEachElement (element, (*this)) {
1717         return element;
1718     }
1719     return KoXmlElement();
1720 }
1721 
lastChild() const1722 KoXmlNode KoXmlNode::lastChild() const
1723 {
1724     if (!d->loaded)
1725         d->loadChildren();
1726     return d->last ? KoXmlNode(d->last) : KoXmlNode();
1727 }
1728 
nextSibling() const1729 KoXmlNode KoXmlNode::nextSibling() const
1730 {
1731     return d->next ? KoXmlNode(d->next) : KoXmlNode();
1732 }
1733 
previousSibling() const1734 KoXmlNode KoXmlNode::previousSibling() const
1735 {
1736     return d->prev ? KoXmlNode(d->prev) : KoXmlNode();
1737 }
1738 
namedItem(const QString & name) const1739 KoXmlNode KoXmlNode::namedItem(const QString& name) const
1740 {
1741     if (!d->loaded)
1742         d->loadChildren();
1743 
1744     for (KoXmlNodeData* node = d->first; node; node = node->next) {
1745         if (node->nodeName() == name)
1746             return KoXmlNode(node);
1747     }
1748 
1749     // not found
1750     return KoXmlNode();
1751 }
1752 
namedItemNS(const QString & nsURI,const QString & name) const1753 KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name) const
1754 {
1755     if (!d->loaded)
1756         d->loadChildren();
1757 
1758     for (KoXmlNodeData* node = d->first; node; node = node->next) {
1759         if (node->nodeType == KoXmlNode::ElementNode
1760                  && node->localName == name
1761                  && node->namespaceURI == nsURI
1762                  ) {
1763             return KoXmlNode(node);
1764         }
1765     }
1766 
1767     // not found
1768     return KoXmlNode();
1769 }
1770 
namedItemNS(const QString & nsURI,const QString & name,KoXmlNamedItemType type) const1771 KoXmlNode KoXmlNode::namedItemNS(const QString& nsURI, const QString& name, KoXmlNamedItemType type) const
1772 {
1773     if (!d->loaded)
1774         d->loadChildren();
1775 
1776     for (KoXmlNodeData* node = d->first; node; node = node->next) {
1777         if (node->nodeType != KoXmlNode::ElementNode)
1778             continue;
1779         if (node->localName == name && node->namespaceURI == nsURI) {
1780             return KoXmlNode(node);
1781         }
1782         bool isPrelude = false;
1783         switch (type) {
1784             case KoXmlTextContentPrelude:
1785                 isPrelude =
1786                     (node->localName == "tracked-changes" && node->namespaceURI == KoXmlNS::text) ||
1787                     (node->localName == "variable-decls" && node->namespaceURI == KoXmlNS::text) ||
1788                     (node->localName == "user-field-decls" && node->namespaceURI == KoXmlNS::text) ||
1789                     (node->localName == "user-field-decl" && node->namespaceURI == KoXmlNS::text) ||
1790                     (node->localName == "sequence-decls" && node->namespaceURI == KoXmlNS::text) ||
1791                     (node->localName == "sequence-decl" && node->namespaceURI == KoXmlNS::text) ||
1792                     (node->localName == "dde-connection-decls" && node->namespaceURI == KoXmlNS::text) ||
1793                     (node->localName == "alphabetical-index-auto-mark-file" && node->namespaceURI == KoXmlNS::text) ||
1794                     (node->localName == "forms" && node->namespaceURI == KoXmlNS::office);
1795                 break;
1796         }
1797         if (!isPrelude) {
1798             return KoXmlNode(); // no TextContentPrelude means it follows TextContentMain, so stop here.
1799         }
1800     }
1801 
1802     // not found
1803     return KoXmlNode();
1804 }
1805 
toElement() const1806 KoXmlElement KoXmlNode::toElement() const
1807 {
1808     return isElement() ? KoXmlElement(d) : KoXmlElement();
1809 }
1810 
toText() const1811 KoXmlText KoXmlNode::toText() const
1812 {
1813     return isText() ? KoXmlText(d) : KoXmlText();
1814 }
1815 
toCDATASection() const1816 KoXmlCDATASection KoXmlNode::toCDATASection() const
1817 {
1818     return isCDATASection() ? KoXmlCDATASection(d) : KoXmlCDATASection();
1819 }
1820 
toDocument() const1821 KoXmlDocument KoXmlNode::toDocument() const
1822 {
1823     if (isDocument()) {
1824         return KoXmlDocument(static_cast<KoXmlDocumentData*>(d));
1825     }
1826     return KoXmlDocument();
1827 }
1828 
load(int depth)1829 void KoXmlNode::load(int depth)
1830 {
1831     d->loadChildren(depth);
1832 }
1833 
unload()1834 void KoXmlNode::unload()
1835 {
1836     d->unloadChildren();
1837 }
1838 
asQDomNode(QDomDocument & ownerDoc) const1839 void KoXmlNode::asQDomNode(QDomDocument& ownerDoc) const
1840 {
1841     Q_ASSERT(!isDocument());
1842     d->asQDomNode(ownerDoc);
1843 }
1844 
1845 // ==================================================================
1846 //
1847 //         KoXmlElement
1848 //
1849 // ==================================================================
1850 
1851 // Creates an empty element
KoXmlElement()1852 KoXmlElement::KoXmlElement(): KoXmlNode()
1853 {
1854 }
1855 
~KoXmlElement()1856 KoXmlElement::~KoXmlElement()
1857 {
1858 }
1859 
1860 // Creates a shallow copy of another element
KoXmlElement(const KoXmlElement & element)1861 KoXmlElement::KoXmlElement(const KoXmlElement& element): KoXmlNode(element.d)
1862 {
1863 }
1864 
KoXmlElement(KoXmlNodeData * data)1865 KoXmlElement::KoXmlElement(KoXmlNodeData* data): KoXmlNode(data)
1866 {
1867 }
1868 
1869 // Copies another element
operator =(const KoXmlElement & element)1870 KoXmlElement& KoXmlElement::operator=(const KoXmlElement & element)
1871 {
1872     KoXmlNode::operator=(element);
1873     return *this;
1874 }
1875 
operator ==(const KoXmlElement & element) const1876 bool KoXmlElement::operator== (const KoXmlElement& element) const
1877 {
1878     if (isNull() || element.isNull()) return false;
1879     return (d == element.d);
1880 }
1881 
operator !=(const KoXmlElement & element) const1882 bool KoXmlElement::operator!= (const KoXmlElement& element) const
1883 {
1884     if (isNull() && element.isNull()) return false;
1885     if (isNull() || element.isNull()) return true;
1886     return (d != element.d);
1887 }
1888 
tagName() const1889 QString KoXmlElement::tagName() const
1890 {
1891     return isElement() ? d->tagName : QString();
1892 }
1893 
text() const1894 QString KoXmlElement::text() const
1895 {
1896     return d->text();
1897 }
1898 
attribute(const QString & name) const1899 QString KoXmlElement::attribute(const QString& name) const
1900 {
1901     if (!isElement())
1902         return QString();
1903 
1904     if (!d->loaded)
1905         d->loadChildren();
1906 
1907     return d->attribute(name, QString());
1908 }
1909 
attribute(const QString & name,const QString & defaultValue) const1910 QString KoXmlElement::attribute(const QString& name,
1911                                 const QString& defaultValue) const
1912 {
1913     if (!isElement())
1914         return defaultValue;
1915 
1916     if (!d->loaded)
1917         d->loadChildren();
1918 
1919     return d->attribute(name, defaultValue);
1920 }
1921 
attributeNS(const QString & namespaceURI,const QString & localName,const QString & defaultValue) const1922 QString KoXmlElement::attributeNS(const QString& namespaceURI,
1923                                   const QString& localName, const QString& defaultValue) const
1924 {
1925     if (!isElement())
1926         return defaultValue;
1927 
1928     if (!d->loaded)
1929         d->loadChildren();
1930 
1931     KoXmlStringPair key(namespaceURI, localName);
1932     return d->attrNS.value(key, defaultValue);
1933 
1934 //  return d->attributeNS(namespaceURI, localName, defaultValue);
1935 }
1936 
hasAttribute(const QString & name) const1937 bool KoXmlElement::hasAttribute(const QString& name) const
1938 {
1939     if (!d->loaded)
1940         d->loadChildren();
1941 
1942     return isElement() ? d->hasAttribute(name) : false;
1943 }
1944 
hasAttributeNS(const QString & namespaceURI,const QString & localName) const1945 bool KoXmlElement::hasAttributeNS(const QString& namespaceURI,
1946                                   const QString& localName) const
1947 {
1948     if (!d->loaded)
1949         d->loadChildren();
1950 
1951     return isElement() ? d->hasAttributeNS(namespaceURI, localName) : false;
1952 }
1953 
1954 // ==================================================================
1955 //
1956 //         KoXmlText
1957 //
1958 // ==================================================================
1959 
KoXmlText()1960 KoXmlText::KoXmlText(): KoXmlNode()
1961 {
1962 }
1963 
~KoXmlText()1964 KoXmlText::~KoXmlText()
1965 {
1966 }
1967 
KoXmlText(const KoXmlText & text)1968 KoXmlText::KoXmlText(const KoXmlText& text): KoXmlNode(text.d)
1969 {
1970 }
1971 
KoXmlText(KoXmlNodeData * data)1972 KoXmlText::KoXmlText(KoXmlNodeData* data): KoXmlNode(data)
1973 {
1974 }
1975 
isText() const1976 bool KoXmlText::isText() const
1977 {
1978     return true;
1979 }
1980 
data() const1981 QString KoXmlText::data() const
1982 {
1983     return d->data();
1984 }
1985 
operator =(const KoXmlText & element)1986 KoXmlText& KoXmlText::operator=(const KoXmlText & element)
1987 {
1988     KoXmlNode::operator=(element);
1989     return *this;
1990 }
1991 
1992 // ==================================================================
1993 //
1994 //         KoXmlCDATASection
1995 //
1996 // ==================================================================
1997 
KoXmlCDATASection()1998 KoXmlCDATASection::KoXmlCDATASection(): KoXmlText()
1999 {
2000 }
2001 
KoXmlCDATASection(const KoXmlCDATASection & cdata)2002 KoXmlCDATASection::KoXmlCDATASection(const KoXmlCDATASection& cdata)
2003         : KoXmlText(cdata)
2004 {
2005 }
2006 
~KoXmlCDATASection()2007 KoXmlCDATASection::~KoXmlCDATASection()
2008 {
2009 }
2010 
KoXmlCDATASection(KoXmlNodeData * cdata)2011 KoXmlCDATASection::KoXmlCDATASection(KoXmlNodeData* cdata):
2012         KoXmlText(cdata)
2013 {
2014 }
2015 
isCDATASection() const2016 bool KoXmlCDATASection::isCDATASection() const
2017 {
2018     return true;
2019 }
2020 
operator =(const KoXmlCDATASection & cdata)2021 KoXmlCDATASection& KoXmlCDATASection::operator=(const KoXmlCDATASection & cdata)
2022 {
2023     KoXmlNode::operator=(cdata);
2024     return *this;
2025 }
2026 
2027 // ==================================================================
2028 //
2029 //         KoXmlDocumentType
2030 //
2031 // ==================================================================
2032 
KoXmlDocumentType()2033 KoXmlDocumentType::KoXmlDocumentType(): KoXmlNode()
2034 {
2035 }
2036 
~KoXmlDocumentType()2037 KoXmlDocumentType::~KoXmlDocumentType()
2038 {
2039 }
2040 
KoXmlDocumentType(const KoXmlDocumentType & dt)2041 KoXmlDocumentType::KoXmlDocumentType(const KoXmlDocumentType& dt):
2042         KoXmlNode(dt.d)
2043 {
2044 }
2045 
name() const2046 QString KoXmlDocumentType::name() const
2047 {
2048     return nodeName();
2049 }
2050 
KoXmlDocumentType(KoXmlNodeData * dt)2051 KoXmlDocumentType::KoXmlDocumentType(KoXmlNodeData* dt): KoXmlNode(dt)
2052 {
2053 }
2054 
operator =(const KoXmlDocumentType & dt)2055 KoXmlDocumentType& KoXmlDocumentType::operator=(const KoXmlDocumentType & dt)
2056 {
2057     KoXmlNode::operator=(dt);
2058     return *this;
2059 }
2060 
2061 // ==================================================================
2062 //
2063 //         KoXmlDocument
2064 //
2065 // ==================================================================
2066 
KoXmlDocument(bool stripSpaces)2067 KoXmlDocument::KoXmlDocument(bool stripSpaces): KoXmlNode(new KoXmlDocumentData(0))
2068 {
2069     KOXMLDOCDATA(d)->emptyDocument = false;
2070     KOXMLDOCDATA(d)->stripSpaces = stripSpaces;
2071 }
2072 
~KoXmlDocument()2073 KoXmlDocument::~KoXmlDocument()
2074 {
2075 }
2076 
KoXmlDocument(KoXmlDocumentData * data)2077 KoXmlDocument::KoXmlDocument(KoXmlDocumentData* data): KoXmlNode(data)
2078 {
2079     KOXMLDOCDATA(d)->emptyDocument = true;
2080 }
2081 
2082 // Creates a copy of another document
KoXmlDocument(const KoXmlDocument & doc)2083 KoXmlDocument::KoXmlDocument(const KoXmlDocument& doc): KoXmlNode(doc.d)
2084 {
2085 }
2086 
2087 // Creates a shallow copy of another document
operator =(const KoXmlDocument & doc)2088 KoXmlDocument& KoXmlDocument::operator=(const KoXmlDocument & doc)
2089 {
2090     KoXmlNode::operator=(doc);
2091     return *this;
2092 }
2093 
2094 // Checks if this document and doc are equals
operator ==(const KoXmlDocument & doc) const2095 bool KoXmlDocument::operator==(const KoXmlDocument& doc) const
2096 {
2097     return(d == doc.d);
2098 }
2099 
2100 // Checks if this document and doc are not equals
operator !=(const KoXmlDocument & doc) const2101 bool KoXmlDocument::operator!=(const KoXmlDocument& doc) const
2102 {
2103     return(d != doc.d);
2104 }
2105 
documentElement() const2106 KoXmlElement KoXmlDocument::documentElement() const
2107 {
2108     if (!d->loaded)
2109         d->loadChildren();
2110 
2111     for (KoXmlNodeData* node = d->first; node; node = node->next) {
2112         if (node->nodeType == KoXmlNode::ElementNode) {
2113             return KoXmlElement(node);
2114         }
2115     }
2116 
2117     return KoXmlElement();
2118 }
2119 
doctype() const2120 KoXmlDocumentType KoXmlDocument::doctype() const
2121 {
2122     return KOXMLDOCDATA(d)->dt;
2123 }
2124 
nodeName() const2125 QString KoXmlDocument::nodeName() const
2126 {
2127     return (KOXMLDOCDATA(d)->emptyDocument) ? QString::fromLatin1("#document") : QString();
2128 }
2129 
clear()2130 void KoXmlDocument::clear()
2131 {
2132     d->unref();
2133     KoXmlDocumentData *dat = new KoXmlDocumentData;
2134     dat->emptyDocument = false;
2135     d = dat;
2136 }
2137 
2138 namespace {
2139     /* Use an entity resolver that ignores undefined entities and simply
2140        returns an empty string for them.
2141        */
2142     class DumbEntityResolver : public QXmlStreamEntityResolver {
2143     public:
resolveUndeclaredEntity(const QString &)2144         QString resolveUndeclaredEntity (const QString &) override { return ""; }
2145     };
2146 
2147 }
2148 
setContent(QXmlStreamReader * reader,QString * errorMsg,int * errorLine,int * errorColumn)2149 bool KoXmlDocument::setContent(QXmlStreamReader *reader,
2150                                QString* errorMsg, int* errorLine, int* errorColumn)
2151 {
2152     if (d->nodeType != KoXmlNode::DocumentNode) {
2153         const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
2154         d->unref();
2155         KoXmlDocumentData *dat = new KoXmlDocumentData;
2156         dat->nodeType = KoXmlNode::DocumentNode;
2157         dat->stripSpaces = stripSpaces;
2158         d = dat;
2159     }
2160 
2161     const bool result = KOXMLDOCDATA(d)->setContent(reader, errorMsg, errorLine, errorColumn);
2162 
2163     return result;
2164 }
2165 
2166 // no namespace processing
setContent(QIODevice * device,QString * errorMsg,int * errorLine,int * errorColumn)2167 bool KoXmlDocument::setContent(QIODevice* device, QString* errorMsg,
2168                                int* errorLine, int* errorColumn)
2169 {
2170     return setContent(device, false, errorMsg, errorLine, errorColumn);
2171 }
2172 
setContent(QIODevice * device,bool namespaceProcessing,QString * errorMsg,int * errorLine,int * errorColumn)2173 bool KoXmlDocument::setContent(QIODevice* device, bool namespaceProcessing,
2174                                QString* errorMsg, int* errorLine, int* errorColumn)
2175 {
2176     if (d->nodeType != KoXmlNode::DocumentNode) {
2177         const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
2178         d->unref();
2179         KoXmlDocumentData *dat = new KoXmlDocumentData;
2180         dat->nodeType = KoXmlNode::DocumentNode;
2181         dat->stripSpaces = stripSpaces;
2182         d = dat;
2183     }
2184 
2185     if (!device->isOpen()) device->open(QIODevice::ReadOnly);
2186     QXmlStreamReader reader(device);
2187     reader.setNamespaceProcessing(namespaceProcessing);
2188     DumbEntityResolver entityResolver;
2189     reader.setEntityResolver(&entityResolver);
2190 
2191     const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn);
2192 
2193     return result;
2194 }
2195 
setContent(const QByteArray & text,bool namespaceProcessing,QString * errorMsg,int * errorLine,int * errorColumn)2196 bool KoXmlDocument::setContent(const QByteArray& text, bool namespaceProcessing,
2197                                QString *errorMsg, int *errorLine, int *errorColumn)
2198 {
2199     QBuffer buffer;
2200     buffer.setData(text);
2201     return setContent(&buffer, namespaceProcessing, errorMsg, errorLine, errorColumn);
2202 }
2203 
setContent(const QString & text,bool namespaceProcessing,QString * errorMsg,int * errorLine,int * errorColumn)2204 bool KoXmlDocument::setContent(const QString& text, bool namespaceProcessing,
2205                                QString *errorMsg, int *errorLine, int *errorColumn)
2206 {
2207     if (d->nodeType != KoXmlNode::DocumentNode) {
2208         const bool stripSpaces = KOXMLDOCDATA(d)->stripSpaces;
2209         d->unref();
2210         KoXmlDocumentData *dat = new KoXmlDocumentData;
2211         dat->nodeType = KoXmlNode::DocumentNode;
2212         dat->stripSpaces = stripSpaces;
2213         d = dat;
2214     }
2215 
2216     QXmlStreamReader reader(text);
2217     reader.setNamespaceProcessing(namespaceProcessing);
2218     DumbEntityResolver entityResolver;
2219     reader.setEntityResolver(&entityResolver);
2220 
2221     const bool result = KOXMLDOCDATA(d)->setContent(&reader, errorMsg, errorLine, errorColumn);
2222 
2223     return result;
2224 }
2225 
setContent(const QString & text,QString * errorMsg,int * errorLine,int * errorColumn)2226 bool KoXmlDocument::setContent(const QString& text,
2227                                QString *errorMsg, int *errorLine, int *errorColumn)
2228 {
2229     return setContent(text, false, errorMsg, errorLine, errorColumn);
2230 }
2231 
setWhitespaceStripping(bool stripSpaces)2232 void  KoXmlDocument::setWhitespaceStripping(bool stripSpaces)
2233 {
2234     KOXMLDOCDATA(d)->stripSpaces = stripSpaces;
2235 }
2236 
2237 
2238 #endif
2239 
2240 // ==================================================================
2241 //
2242 //         functions in KoXml namespace
2243 //
2244 // ==================================================================
2245 
namedItemNS(const KoXmlNode & node,const QString & nsURI,const QString & localName)2246 KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI,
2247                                 const QString& localName)
2248 {
2249 #ifdef KOXML_USE_QDOM
2250     // David's solution for namedItemNS, only for QDom stuff
2251     KoXmlNode n = node.firstChild();
2252     for (; !n.isNull(); n = n.nextSibling()) {
2253         if (n.isElement() && n.localName() == localName &&
2254                 n.namespaceURI() == nsURI)
2255             return n.toElement();
2256     }
2257     return KoXmlElement();
2258 #else
2259     return node.namedItemNS(nsURI, localName).toElement();
2260 #endif
2261 }
2262 
namedItemNS(const KoXmlNode & node,const QString & nsURI,const QString & localName,KoXmlNamedItemType type)2263 KoXmlElement KoXml::namedItemNS(const KoXmlNode& node, const QString& nsURI,
2264                                 const QString& localName, KoXmlNamedItemType type)
2265 {
2266 #ifdef KOXML_USE_QDOM
2267 Q_ASSERT(false);
2268     return namedItemNS(node, nsURI, localName);
2269 #else
2270     return node.namedItemNS(nsURI, localName, type).toElement();
2271 #endif
2272 }
2273 
load(KoXmlNode & node,int depth)2274 void KoXml::load(KoXmlNode& node, int depth)
2275 {
2276 #ifdef KOXML_USE_QDOM
2277     // do nothing, QDom has no on-demand loading
2278     Q_UNUSED(node);
2279     Q_UNUSED(depth);
2280 #else
2281     node.load(depth);
2282 #endif
2283 }
2284 
2285 
unload(KoXmlNode & node)2286 void KoXml::unload(KoXmlNode& node)
2287 {
2288 #ifdef KOXML_USE_QDOM
2289     // do nothing, QDom has no on-demand unloading
2290     Q_UNUSED(node);
2291 #else
2292     node.unload();
2293 #endif
2294 }
2295 
childNodesCount(const KoXmlNode & node)2296 int KoXml::childNodesCount(const KoXmlNode& node)
2297 {
2298 #ifdef KOXML_USE_QDOM
2299     return node.childNodes().count();
2300 #else
2301     // compatibility function, because no need to implement
2302     // a class like QDomNodeList
2303     return node.childNodesCount();
2304 #endif
2305 }
2306 
attributeNames(const KoXmlNode & node)2307 QStringList KoXml::attributeNames(const KoXmlNode& node)
2308 {
2309 #ifdef KOXML_USE_QDOM
2310     QStringList result;
2311 
2312     QDomNamedNodeMap attrMap = node.attributes();
2313     for (int i = 0; i < attrMap.count(); ++i)
2314         result += attrMap.item(i).toAttr().name();
2315 
2316     return result;
2317 #else
2318     // compatibility function, because no need to implement
2319     // a class like QDomNamedNodeMap
2320     return node.attributeNames();
2321 #endif
2322 }
2323 
asQDomNode(QDomDocument & ownerDoc,const KoXmlNode & node)2324 void KoXml::asQDomNode(QDomDocument& ownerDoc, const KoXmlNode& node)
2325 {
2326     Q_ASSERT(!node.isDocument());
2327 #ifdef KOXML_USE_QDOM
2328     ownerDoc.appendChild(ownerDoc.importNode(node));
2329 #else
2330     node.asQDomNode(ownerDoc);
2331 #endif
2332 }
2333 
asQDomElement(QDomDocument & ownerDoc,const KoXmlElement & element)2334 void KoXml::asQDomElement(QDomDocument &ownerDoc, const KoXmlElement& element)
2335 {
2336     KoXml::asQDomNode(ownerDoc, element);
2337 }
2338 
asQDomDocument(const KoXmlDocument & document)2339 QDomDocument KoXml::asQDomDocument(const KoXmlDocument& document)
2340 {
2341 #ifdef KOXML_USE_QDOM
2342     return document;
2343 #else
2344     QDomDocument qdoc(document.nodeName());
2345     if (document.hasChildNodes()) {
2346         for (KoXmlNode n = document.firstChild(); ! n.isNull(); n = n.nextSibling()) {
2347             KoXml::asQDomNode(qdoc, n);
2348         }
2349     }
2350     return qdoc;
2351 #endif
2352 }
2353 
setDocument(KoXmlDocument & doc,QIODevice * device,bool namespaceProcessing,QString * errorMsg,int * errorLine,int * errorColumn)2354 bool KoXml::setDocument(KoXmlDocument& doc, QIODevice* device,
2355                         bool namespaceProcessing, QString* errorMsg, int* errorLine,
2356                         int* errorColumn)
2357 {
2358     QXmlStreamReader reader(device);
2359     reader.setNamespaceProcessing(namespaceProcessing);
2360     bool result = doc.setContent(&reader, errorMsg, errorLine, errorColumn);
2361     return result;
2362 }
2363