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