1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QBuffer>
31#include <QByteArray>
32#include <QCoreApplication>
33#include <QDebug>
34#include <QFile>
35#include <QList>
36#include <QRegExp>
37#include <QTextStream>
38#include <QtTest/QtTest>
39#include <QtXml>
40#include <QVariant>
41#include <cmath>
42
43QT_FORWARD_DECLARE_CLASS(QDomDocument)
44QT_FORWARD_DECLARE_CLASS(QDomNode)
45
46class tst_QDom : public QObject
47{
48 Q_OBJECT
49
50private slots:
51 void initTestCase();
52 void namespacedAttributes() const;
53 void setContent_data();
54 void setContent();
55 void toString_01_data();
56 void toString_01();
57 void toString_02_data();
58 void toString_02();
59 void hasAttributes_data();
60 void hasAttributes();
61 void setGetAttributes();
62 void save_data();
63 void save();
64 void saveWithSerialization() const;
65 void saveWithSerialization_data() const;
66 void cloneNode_data();
67 void cloneNode();
68 void ownerDocument_data();
69 void ownerDocument();
70 void ownerDocumentTask27424_data();
71 void ownerDocumentTask27424();
72 void parentNode_data();
73 void parentNode();
74 void documentCreationTask27424_data();
75 void documentCreationTask27424();
76 void browseElements();
77 void ownerElementTask45192_data();
78 void ownerElementTask45192();
79 void domNodeMapAndList();
80
81 void nullDocument();
82 void invalidName_data();
83 void invalidName();
84 void invalidQualifiedName_data();
85 void invalidQualifiedName();
86 void invalidCharData_data();
87 void invalidCharData();
88
89 void roundTripAttributes() const;
90 void normalizeEndOfLine() const;
91 void normalizeAttributes() const;
92 void serializeWeirdEOL() const;
93 void reparentAttribute() const;
94 void serializeNamespaces() const;
95 void flagInvalidNamespaces() const;
96 void flagUndeclaredNamespace() const;
97
98 void indentComments() const;
99 void checkLiveness() const;
100 void reportDuplicateAttributes() const;
101 void appendChildFromToDocument() const;
102 void iterateCDATA() const;
103 void appendDocumentNode() const;
104 void germanUmlautToByteArray() const;
105 void germanUmlautToFile() const;
106 void setInvalidDataPolicy() const;
107 void crashInSetContent() const;
108 void doubleNamespaceDeclarations() const;
109 void setContentQXmlReaderOverload() const;
110 void toStringWithoutNewlines() const;
111 void checkIntOverflow() const;
112 void setContentWhitespace() const;
113 void setContentWhitespace_data() const;
114
115 void taskQTBUG4595_dontAssertWhenDocumentSpecifiesUnknownEncoding() const;
116 void cloneDTD_QTBUG8398() const;
117 void DTDNotationDecl();
118 void DTDEntityDecl();
119 void QTBUG49113_dontCrashWithNegativeIndex() const;
120
121 void cleanupTestCase() const;
122
123private:
124 static QDomDocument generateRequest();
125 static int hasAttributesHelper( const QDomNode& node );
126 static bool compareDocuments( const QDomDocument &doc1, const QDomDocument &doc2 );
127 static bool compareNodes( const QDomNode &node1, const QDomNode &node2, bool deep );
128 static QDomNode findDomNode( const QDomDocument &doc, const QList<QVariant> &pathToNode );
129 static QString onNullWarning(const char *const functionName);
130 static bool isDeepEqual(const QDomNode &n1, const QDomNode &n2);
131 static bool isFakeXMLDeclaration(const QDomNode &node);
132
133 QList<QByteArray> m_testCodecs;
134};
135
136
137void tst_QDom::setContent_data()
138{
139 const QString doc01(
140 "<!DOCTYPE a1 [ <!ENTITY blubber 'and'> ]>\n"
141 "<a1>\n"
142 " <b1>\n"
143 " <c1>foo</c1>\n"
144 " <c2>bar</c2>\n"
145 " <c3>foo &amp; bar</c3>\n"
146 " <c4>foo &blubber; bar</c4>\n"
147 " </b1>\n"
148 " <b2> </b2>\n"
149 " <b3>\n"
150 " <c1/>\n"
151 " </b3>\n"
152 "</a1>\n"
153 );
154
155 QTest::addColumn<QString>(name: "doc");
156 QTest::addColumn<QStringList>(name: "featuresTrue");
157 QTest::addColumn<QStringList>(name: "featuresFalse");
158 QTest::addColumn<QString>(name: "res");
159
160 QTest::newRow( dataTag: "01" ) << doc01
161 << QStringList()
162 << QString("http://trolltech.com/xml/features/report-whitespace-only-CharData").split(sep: ' ')
163 << QString("<!DOCTYPE a1>\n"
164 "<a1>\n"
165 " <b1>\n"
166 " <c1>foo</c1>\n"
167 " <c2>bar</c2>\n"
168 " <c3>foo &amp; bar</c3>\n"
169 " <c4>foo and bar</c4>\n"
170 " </b1>\n"
171 " <b2/>\n"
172 " <b3>\n"
173 " <c1/>\n"
174 " </b3>\n"
175 "</a1>\n");
176
177#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
178 // These configurations cannot be supported by the QXmlStreamReader-based implementation
179 QTest::newRow( dataTag: "02" ) << doc01
180 << QString("http://trolltech.com/xml/features/report-whitespace-only-CharData").split(sep: ' ')
181 << QStringList()
182 << QString("<!DOCTYPE a1>\n"
183 "<a1>\n"
184 " <b1>\n"
185 " <c1>foo</c1>\n"
186 " <c2>bar</c2>\n"
187 " <c3>foo &amp; bar</c3>\n"
188 " <c4>foo and bar</c4>\n"
189 " </b1>\n"
190 " <b2> </b2>\n"
191 " <b3>\n"
192 " <c1/>\n"
193 " </b3>\n"
194 "</a1>\n");
195
196 QTest::newRow( dataTag: "03" ) << doc01
197 << QString("http://trolltech.com/xml/features/report-start-end-entity").split(sep: ' ')
198 << QString("http://trolltech.com/xml/features/report-whitespace-only-CharData").split(sep: ' ')
199 << QString("<!DOCTYPE a1 [\n"
200 "<!ENTITY blubber \"and\">\n"
201 "]>\n"
202 "<a1>\n"
203 " <b1>\n"
204 " <c1>foo</c1>\n"
205 " <c2>bar</c2>\n"
206 " <c3>foo &amp; bar</c3>\n"
207 " <c4>foo &blubber; bar</c4>\n"
208 " </b1>\n"
209 " <b2/>\n"
210 " <b3>\n"
211 " <c1/>\n"
212 " </b3>\n"
213 "</a1>\n");
214
215 QTest::newRow( dataTag: "04" ) << doc01
216 << QString("http://trolltech.com/xml/features/report-whitespace-only-CharData http://trolltech.com/xml/features/report-start-end-entity").split(sep: ' ')
217 << QStringList()
218 << QString("<!DOCTYPE a1 [\n"
219 "<!ENTITY blubber \"and\">\n"
220 "]>\n"
221 "<a1>\n"
222 " <b1>\n"
223 " <c1>foo</c1>\n"
224 " <c2>bar</c2>\n"
225 " <c3>foo &amp; bar</c3>\n"
226 " <c4>foo &blubber; bar</c4>\n"
227 " </b1>\n"
228 " <b2> </b2>\n"
229 " <b3>\n"
230 " <c1/>\n"
231 " </b3>\n"
232 "</a1>\n");
233#endif
234
235 QTest::newRow(dataTag: "05") << QString("<message>\n"
236 " <body>&lt;b&gt;foo&lt;/b&gt;>]]&gt;</body>\n"
237 "</message>\n")
238 << QStringList() << QStringList()
239 << QString("<message>\n"
240 " <body>&lt;b>foo&lt;/b>>]]&gt;</body>\n"
241 "</message>\n");
242
243}
244
245void tst_QDom::setContent()
246{
247 QFETCH( QString, doc );
248
249 QDomDocument domDoc;
250#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
251QT_WARNING_PUSH
252QT_WARNING_DISABLE_DEPRECATED
253 QXmlInputSource source;
254 source.setData( doc );
255
256 QFETCH( QStringList, featuresTrue );
257 QFETCH( QStringList, featuresFalse );
258 QXmlSimpleReader reader;
259 QStringList::Iterator it;
260 for ( it = featuresTrue.begin(); it != featuresTrue.end(); ++it ) {
261 QVERIFY( reader.hasFeature( *it ) );
262 reader.setFeature( name: *it, value: true );
263 }
264 for ( it = featuresFalse.begin(); it != featuresFalse.end(); ++it ) {
265 QVERIFY( reader.hasFeature( *it ) );
266 reader.setFeature( name: *it, value: false );
267 }
268
269 QVERIFY( domDoc.setContent( &source, &reader ) );
270QT_WARNING_POP
271#else
272 QXmlStreamReader reader(doc);
273 QVERIFY(domDoc.setContent(&reader, true));
274#endif
275
276 QString eRes;
277 QTextStream ts( &eRes, QIODevice::WriteOnly );
278 domDoc.save( ts, 4 );
279
280 QTEST( eRes, "res" );
281
282 // make sure that if we parse our output again, we get the same document
283 QDomDocument domDoc1;
284 QDomDocument domDoc2;
285 QVERIFY( domDoc1.setContent( doc ) );
286 QVERIFY( domDoc2.setContent( eRes ) );
287 QVERIFY( compareDocuments( domDoc1, domDoc2 ) );
288}
289
290void tst_QDom::toString_01_data()
291{
292 QTest::addColumn<QString>(name: "fileName");
293 const QString prefix = QFINDTESTDATA("testdata/toString_01");
294 if (prefix.isEmpty())
295 QFAIL("Cannot find testdata directory!");
296 QTest::newRow( dataTag: "01" ) << QString(prefix + "/doc01.xml");
297 QTest::newRow( dataTag: "02" ) << QString(prefix + "/doc02.xml");
298 QTest::newRow( dataTag: "03" ) << QString(prefix + "/doc03.xml");
299 QTest::newRow( dataTag: "04" ) << QString(prefix + "/doc04.xml");
300 QTest::newRow( dataTag: "05" ) << QString(prefix + "/doc05.xml");
301
302 QTest::newRow( dataTag: "euc-jp" ) << QString(prefix + "/doc_euc-jp.xml");
303 QTest::newRow( dataTag: "iso-2022-jp" ) << QString(prefix + "/doc_iso-2022-jp.xml");
304 QTest::newRow( dataTag: "little-endian" ) << QString(prefix + "/doc_little-endian.xml");
305 QTest::newRow( dataTag: "utf-16" ) << QString(prefix + "/doc_utf-16.xml");
306 QTest::newRow( dataTag: "utf-8" ) << QString(prefix + "/doc_utf-8.xml");
307
308}
309
310/*! \internal
311
312 This function tests that the QDomDocument::toString() function results in the
313 same XML document. The meaning of "same" in this context means that the
314 "information" in the resulting XML file is the same as in the original, i.e.
315 we are not intrested in different formatting, etc.
316
317 To achieve this, the XML document of the toString() function is parsed again
318 and the two QDomDocuments are compared.
319*/
320void tst_QDom::toString_01()
321{
322 QFETCH(QString, fileName);
323
324 QFile f(fileName);
325 QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(QString::fromLatin1("Failed to open file %1, file error: %2").arg(fileName).arg(f.error())));
326
327 QDomDocument doc;
328 QString errorMsg;
329 int errorLine;
330 int errorCol;
331
332 QVERIFY(doc.setContent( &f, &errorMsg, &errorLine, &errorCol )); /*,
333 QString("QDomDocument::setContent() failed: %1 in line %2, column %3")
334 .arg( errorMsg ).arg( errorLine ).arg( errorCol )); */
335 // test toString()'s invariant with different indenting depths
336 for ( int i=0; i<5; i++ ) {
337 QString toStr = doc.toString( i );
338
339 QDomDocument res;
340 QVERIFY( res.setContent( toStr ) );
341
342 QVERIFY( compareDocuments( doc, res ) );
343 }
344}
345
346void tst_QDom::toString_02_data()
347{
348 save_data();
349}
350
351/*
352 Tests the new QDomDocument::toString(int) overload (basically the same test
353 as save()).
354*/
355void tst_QDom::toString_02()
356{
357 QFETCH( QString, doc );
358 QFETCH( int, indent );
359
360 QDomDocument domDoc;
361 QVERIFY( domDoc.setContent( doc ) );
362 QTEST( domDoc.toString(indent), "res" );
363}
364
365
366void tst_QDom::hasAttributes_data()
367{
368 QTest::addColumn<int>(name: "visitedNodes");
369 QTest::addColumn<QByteArray>(name: "xmlDoc");
370
371 QByteArray doc1("<top>Make a <blubb>stupid</blubb>, useless test sentence.</top>");
372 QByteArray doc2("<top a=\"a\">Make a <blubb a=\"a\">stupid</blubb>, useless test sentence.</top>");
373 QByteArray doc3("<!-- just a useless comment -->\n"
374 "<?pi foo bar?>\n"
375 "<foo>\n"
376 "<bar fnord=\"snafu\" hmpf=\"grmpf\">\n"
377 "<foobar/>\n"
378 "</bar>\n"
379 "<bar>blubber</bar>\n"
380 "more text, pretty unintresting, though\n"
381 "<hmpfl blubber=\"something\" />\n"
382 "<![CDATA[ foo bar @!<>] ]]>\n"
383 "</foo>\n"
384 "<!-- just a useless comment -->\n"
385 "<?pi foo bar?>\n");
386
387 QTest::newRow( dataTag: "01" ) << 6 << doc1;
388 QTest::newRow( dataTag: "02" ) << 6 << doc2;
389 QTest::newRow( dataTag: "03" ) << 13 << doc3;
390}
391
392/*
393 This function tests that QDomNode::hasAttributes() returns true if and only
394 if the node has attributes (i.e. QDomNode::attributes() returns a list with
395 attributes in it).
396*/
397void tst_QDom::hasAttributes()
398{
399 QFETCH( QByteArray, xmlDoc );
400
401 QDomDocument doc;
402 QVERIFY( doc.setContent( xmlDoc ) );
403
404 int visitedNodes = hasAttributesHelper( node: doc );
405 QTEST( visitedNodes, "visitedNodes" );
406}
407
408void tst_QDom::setGetAttributes()
409{
410 QDomDocument doc;
411 QDomElement rootNode = doc.createElement(tagName: "Root");
412 doc.appendChild(newChild: rootNode);
413
414 const QLocale oldLocale = QLocale();
415 QLocale::setDefault(QLocale::German); // decimal separator != '.'
416
417 const QString qstringVal("QString");
418 const qlonglong qlonglongVal = std::numeric_limits<qlonglong>::min();
419 const qulonglong qulonglongVal = std::numeric_limits<qulonglong>::max();
420 const int intVal = std::numeric_limits<int>::min();
421 const uint uintVal = std::numeric_limits<uint>::max();
422 const float floatVal = 0.1234f;
423 const double doubleVal1 = 1./6.;
424 const double doubleVal2 = std::nextafter(x: doubleVal1, y: 1.);
425 const double doubleVal3 = std::nextafter(x: doubleVal2, y: 1.);
426
427 rootNode.setAttribute(name: "qstringVal", value: qstringVal);
428 rootNode.setAttribute(name: "qlonglongVal", value: qlonglongVal);
429 rootNode.setAttribute(name: "qulonglongVal", value: qulonglongVal);
430 rootNode.setAttribute(name: "intVal", value: intVal);
431 rootNode.setAttribute(name: "uintVal", value: uintVal);
432 rootNode.setAttribute(name: "floatVal", value: floatVal);
433 rootNode.setAttribute(name: "doubleVal1", value: doubleVal1);
434 rootNode.setAttribute(name: "doubleVal2", value: doubleVal2);
435 rootNode.setAttribute(name: "doubleVal3", value: doubleVal3);
436
437 QDomElement nsNode = doc.createElement(tagName: "NS");
438 rootNode.appendChild(newChild: nsNode);
439 nsNode.setAttributeNS(nsURI: "namespace", qName: "qstringVal", value: qstringVal);
440 nsNode.setAttributeNS(nsURI: "namespace", qName: "qlonglongVal", value: qlonglongVal);
441 nsNode.setAttributeNS(nsURI: "namespace", qName: "qulonglongVal", value: qulonglongVal);
442 nsNode.setAttributeNS(nsURI: "namespace", qName: "intVal", value: intVal);
443 nsNode.setAttributeNS(nsURI: "namespace", qName: "uintVal", value: uintVal);
444 nsNode.setAttributeNS(nsURI: "namespace", qName: "floatVal", value: floatVal); // not available atm
445 nsNode.setAttributeNS(nsURI: "namespace", qName: "doubleVal1", value: doubleVal1);
446 nsNode.setAttributeNS(nsURI: "namespace", qName: "doubleVal2", value: doubleVal2);
447 nsNode.setAttributeNS(nsURI: "namespace", qName: "doubleVal3", value: doubleVal3);
448
449 bool bOk;
450 QCOMPARE(rootNode.attribute("qstringVal"), qstringVal);
451 QCOMPARE(rootNode.attribute("qlonglongVal").toLongLong(&bOk), qlonglongVal);
452 QVERIFY(bOk);
453 QCOMPARE(rootNode.attribute("qulonglongVal").toULongLong(&bOk), qulonglongVal);
454 QVERIFY(bOk);
455 QCOMPARE(rootNode.attribute("intVal").toInt(&bOk), intVal);
456 QVERIFY(bOk);
457 QCOMPARE(rootNode.attribute("uintVal").toUInt(&bOk), uintVal);
458 QVERIFY(bOk);
459 QCOMPARE(rootNode.attribute("floatVal").toFloat(&bOk), floatVal);
460 QVERIFY(bOk);
461
462 QVERIFY(rootNode.attribute("doubleVal1").toDouble(&bOk) == doubleVal1 && bOk);
463 QVERIFY(rootNode.attribute("doubleVal2").toDouble(&bOk) == doubleVal2 && bOk);
464 QVERIFY(rootNode.attribute("doubleVal3").toDouble(&bOk) == doubleVal3 && bOk);
465
466 QCOMPARE(nsNode.attributeNS("namespace", "qstringVal"), qstringVal);
467 QCOMPARE(nsNode.attributeNS("namespace", "qlonglongVal").toLongLong(&bOk), qlonglongVal);
468 QVERIFY(bOk);
469 QCOMPARE(nsNode.attributeNS("namespace", "qulonglongVal").toULongLong(&bOk), qulonglongVal);
470 QVERIFY(bOk);
471 QCOMPARE(nsNode.attributeNS("namespace", "intVal").toInt(&bOk), intVal);
472 QVERIFY(bOk);
473 QCOMPARE(nsNode.attributeNS("namespace", "uintVal").toUInt(&bOk), uintVal);
474 QVERIFY(bOk);
475 QCOMPARE(nsNode.attributeNS("namespace", "floatVal").toFloat(&bOk), floatVal);
476 QVERIFY(bOk);
477 QVERIFY(nsNode.attributeNS("namespace", "doubleVal1").toDouble(&bOk) == doubleVal1 && bOk);
478 QVERIFY(nsNode.attributeNS("namespace", "doubleVal2").toDouble(&bOk) == doubleVal2 && bOk);
479 QVERIFY(nsNode.attributeNS("namespace", "doubleVal3").toDouble(&bOk) == doubleVal3 && bOk);
480
481 QLocale::setDefault(oldLocale);
482}
483
484
485int tst_QDom::hasAttributesHelper( const QDomNode& node )
486{
487 int visitedNodes = 1;
488 if ( node.hasAttributes() ) {
489 if (node.attributes().count() == 0)
490 return -1;
491// QVERIFY( node.attributes().count() > 0 );
492 } else {
493 if (node.attributes().count() != 0)
494 return -1;
495// QVERIFY( node.attributes().count() == 0 );
496 }
497
498 QDomNodeList children = node.childNodes();
499 for ( int i=0; i<children.count(); i++ ) {
500 int j = hasAttributesHelper( node: children.item(index: i) );
501 if (j < 0)
502 return -1;
503 visitedNodes += j;
504 }
505 return visitedNodes;
506}
507
508
509void tst_QDom::save_data()
510{
511 const QString doc01(
512 "<a1>\n"
513 " <b1>\n"
514 " <c1>\n"
515 " <d1/>\n"
516 " </c1>\n"
517 " <c2/>\n"
518 " </b1>\n"
519 " <b2/>\n"
520 " <b3>\n"
521 " <c1/>\n"
522 " </b3>\n"
523 "</a1>\n"
524 );
525
526 QTest::addColumn<QString>(name: "doc");
527 QTest::addColumn<int>(name: "indent");
528 QTest::addColumn<QString>(name: "res");
529
530 QTest::newRow( dataTag: "01" ) << doc01 << 0 << QString(doc01).replace( rx: QRegExp(" "), after: "" );
531 QTest::newRow( dataTag: "02" ) << doc01 << 1 << doc01;
532 QTest::newRow( dataTag: "03" ) << doc01 << 2 << QString(doc01).replace( rx: QRegExp(" "), after: " " );
533 QTest::newRow( dataTag: "04" ) << doc01 << 10 << QString(doc01).replace( rx: QRegExp(" "), after: " " );
534}
535
536void tst_QDom::save()
537{
538 QFETCH( QString, doc );
539 QFETCH( int, indent );
540
541 QDomDocument domDoc;
542 QVERIFY( domDoc.setContent( doc ) );
543
544 QString eRes;
545 QTextStream ts( &eRes, QIODevice::WriteOnly );
546 domDoc.save( ts, indent );
547
548 QTEST( eRes, "res" );
549}
550
551void tst_QDom::initTestCase()
552{
553 QString testFile = QFINDTESTDATA("testdata/testCodecs.txt");
554 if (testFile.isEmpty())
555 QFAIL("Cannot find testdata/testCodecs.txt");
556 QFile file(testFile);
557 QVERIFY(file.open(QIODevice::ReadOnly|QIODevice::Text));
558
559 QByteArray codecName;
560
561 m_testCodecs = file.readAll().split(sep: '\n');
562 if (m_testCodecs.last().isEmpty())
563 m_testCodecs.removeLast();
564
565}
566
567void tst_QDom::saveWithSerialization() const
568{
569 QFETCH(QString, fileName);
570
571 QFile f(fileName);
572 QVERIFY(f.open(QIODevice::ReadOnly));
573
574 QDomDocument doc;
575
576 // Read the document
577 QVERIFY(doc.setContent(&f));
578
579 QByteArray codecName;
580
581 foreach (codecName, m_testCodecs) {
582
583 /* Write out doc in the specified codec. */
584 QByteArray storage;
585 QBuffer writeDevice(&storage);
586 QVERIFY(writeDevice.open(QIODevice::WriteOnly));
587
588 QTextStream s(&writeDevice);
589 QTextCodec *codec = QTextCodec::codecForName(name: codecName);
590 QVERIFY2(codec, qPrintable(QString::fromLatin1("Failed to load codec %1").arg(QString::fromLatin1(codecName.constData()))));
591 s.setCodec(codec);
592
593 doc.save(s, 0, QDomNode::EncodingFromTextStream);
594 s.flush();
595 writeDevice.close();
596
597 QBuffer readDevice(&storage);
598 QVERIFY(readDevice.open(QIODevice::ReadOnly));
599
600 QDomDocument result;
601
602 QString msg;
603 int line = 0;
604 int column = 0;
605
606 QVERIFY2(result.setContent(&readDevice, &msg, &line, &column),
607 qPrintable(QString::fromLatin1("Failed for codec %1: line %2, column %3: %4, content: %5")
608 .arg(QString::fromLatin1(codecName.constData()),
609 QString::number(line),
610 QString::number(column),
611 msg,
612 codec->toUnicode(storage))));
613 if(!compareDocuments(doc1: doc, doc2: result))
614 {
615 QCOMPARE(doc.toString(), result.toString());
616
617 /* We put this one here as well, in case the QCOMPARE above for some strange reason
618 * nevertheless succeeds. */
619 QVERIFY2(false, qPrintable(QString::fromLatin1("Failed for codec %1").arg(QString::fromLatin1(codecName.constData()))));
620 }
621 }
622}
623
624void tst_QDom::saveWithSerialization_data() const
625{
626 QTest::addColumn<QString>(name: "fileName");
627 const QString prefix = QFINDTESTDATA("testdata/toString_01");
628 if (prefix.isEmpty())
629 QFAIL("Cannot find testdata!");
630 QTest::newRow(dataTag: "doc01.xml") << QString(prefix + "/doc01.xml");
631 QTest::newRow(dataTag: "doc02.xml") << QString(prefix + "/doc02.xml");
632 QTest::newRow(dataTag: "doc03.xml") << QString(prefix + "/doc03.xml");
633 QTest::newRow(dataTag: "doc04.xml") << QString(prefix + "/doc04.xml");
634 QTest::newRow(dataTag: "doc05.xml") << QString(prefix + "/doc05.xml");
635
636 QTest::newRow(dataTag: "doc_euc-jp.xml") << QString(prefix + "/doc_euc-jp.xml");
637 QTest::newRow(dataTag: "doc_iso-2022-jp.xml") << QString(prefix + "/doc_iso-2022-jp.xml");
638 QTest::newRow(dataTag: "doc_little-endian.xml") << QString(prefix + "/doc_little-endian.xml");
639 QTest::newRow(dataTag: "doc_utf-16.xml") << QString(prefix + "/doc_utf-16.xml");
640 QTest::newRow(dataTag: "doc_utf-8.xml") << QString(prefix + "/doc_utf-8.xml");
641}
642
643void tst_QDom::cloneNode_data()
644{
645 const QString doc01(
646 "<a1>\n"
647 " <b1>\n"
648 " <c1>\n"
649 " <d1/>\n"
650 " </c1>\n"
651 " <c2/>\n"
652 " </b1>\n"
653 " <b2/>\n"
654 " <b3>\n"
655 " <c1/>\n"
656 " </b3>\n"
657 "</a1>\n"
658 );
659 QList<QVariant> nodeB1;
660 nodeB1 << 0;
661
662 QList<QVariant> nodeC1;
663 nodeC1 << 0 << 0;
664
665 QList<QVariant> nodeC2;
666 nodeC2 << 0 << 1;
667
668 QTest::addColumn<QString>(name: "doc");
669 QTest::addColumn<QList<QVariant> >(name: "pathToNode");
670 QTest::addColumn<bool>(name: "deep");
671
672 QTest::newRow( dataTag: "noDeep_01" ) << doc01 << nodeB1 << false;
673 QTest::newRow( dataTag: "noDeep_02" ) << doc01 << nodeC1 << false;
674 QTest::newRow( dataTag: "noDeep_03" ) << doc01 << nodeC2 << false;
675
676 QTest::newRow( dataTag: "deep_01" ) << doc01 << nodeB1 << true;
677 QTest::newRow( dataTag: "deep_02" ) << doc01 << nodeC1 << true;
678 QTest::newRow( dataTag: "deep_03" ) << doc01 << nodeC2 << true;
679}
680
681void tst_QDom::cloneNode()
682{
683 QFETCH( QString, doc );
684 QFETCH( QList<QVariant>, pathToNode );
685 QFETCH( bool, deep );
686 QDomDocument domDoc;
687 QVERIFY( domDoc.setContent( doc ) );
688 QDomNode node = findDomNode( doc: domDoc, pathToNode );
689 QVERIFY(!node.isNull());
690
691 QDomNode clonedNode = node.cloneNode( deep );
692 QVERIFY( compareNodes( node, clonedNode, deep ) );
693
694 QDomNode parent = node.parentNode();
695 if ( !parent.isNull() ) {
696 node = parent.replaceChild( newChild: clonedNode, oldChild: node ); // swap the nodes
697 QVERIFY( !node.isNull() );
698 QVERIFY( compareNodes( node, clonedNode, deep ) );
699 }
700}
701
702
703void tst_QDom::ownerElementTask45192_data()
704{
705 const QString doc(
706 "<root>\n"
707 " <item name=\"test\" >\n"
708 " </item>\n"
709 "</root>"
710 );
711
712 QTest::addColumn<QString>(name: "doc");
713 QTest::newRow(dataTag: "doc") << doc;
714}
715
716void tst_QDom::ownerElementTask45192()
717{
718 QFETCH( QString, doc );
719 QDomDocument domDoc;
720 QVERIFY( domDoc.setContent( doc ) );
721
722 QDomNode item = domDoc.documentElement().firstChild();
723 QDomNode clone = item.cloneNode(deep: false);
724
725 QVERIFY( clone == clone.attributes().namedItem("name").toAttr().ownerElement() );
726}
727
728void tst_QDom::ownerDocument_data()
729{
730 cloneNode_data();
731}
732
733#define OWNERDOCUMENT_CREATE_TEST( t, x ) \
734{ \
735 t n = x; \
736 QVERIFY( n.ownerDocument() == domDoc ); \
737}
738
739#define OWNERDOCUMENT_IMPORTNODE_TEST( t, x ) \
740{ \
741 QDomNode importedNode; \
742 t n = x; \
743 QVERIFY( n.ownerDocument() != domDoc ); \
744 importedNode = domDoc.importNode( n, deep ); \
745 QVERIFY( n.ownerDocument() != domDoc ); \
746 QVERIFY( importedNode.ownerDocument() == domDoc ); \
747}
748
749void tst_QDom::ownerDocument()
750{
751 QFETCH( QString, doc );
752 QFETCH( QList<QVariant>, pathToNode );
753 QFETCH( bool, deep );
754 QDomDocument domDoc;
755 QVERIFY( domDoc.setContent( doc ) );
756 QDomNode node = findDomNode( doc: domDoc, pathToNode );
757 QVERIFY(!node.isNull());
758
759 QVERIFY( node.ownerDocument() == domDoc );
760
761 // Does cloneNode() keep the ownerDocument()?
762 {
763 QDomNode clonedNode = node.cloneNode( deep );
764 QVERIFY( node.ownerDocument() == domDoc );
765 QVERIFY( clonedNode.ownerDocument() == domDoc );
766 }
767
768 // If the original DOM node is replaced with the cloned node, does this
769 // keep the ownerDocument()?
770 {
771 QDomNode clonedNode = node.cloneNode( deep );
772 QDomNode parent = node.parentNode();
773 if ( !parent.isNull() ) {
774 node = parent.replaceChild( newChild: clonedNode, oldChild: node ); // swap the nodes
775 QVERIFY( node.ownerDocument() == domDoc );
776 QVERIFY( clonedNode.ownerDocument() == domDoc );
777 }
778 }
779
780 // test QDomDocument::create...()
781 {
782 OWNERDOCUMENT_CREATE_TEST( QDomAttr, domDoc.createAttribute( "foo" ) );
783 OWNERDOCUMENT_CREATE_TEST( QDomAttr, domDoc.createAttributeNS( "foo", "bar" ) );
784 OWNERDOCUMENT_CREATE_TEST( QDomCDATASection, domDoc.createCDATASection( "foo" ) );
785 OWNERDOCUMENT_CREATE_TEST( QDomComment, domDoc.createComment( "foo" ) );
786 OWNERDOCUMENT_CREATE_TEST( QDomDocumentFragment, domDoc.createDocumentFragment() );
787 OWNERDOCUMENT_CREATE_TEST( QDomElement, domDoc.createElement( "foo" ) );
788 OWNERDOCUMENT_CREATE_TEST( QDomElement, domDoc.createElementNS( "foo", "bar" ) );
789 OWNERDOCUMENT_CREATE_TEST( QDomEntityReference, domDoc.createEntityReference( "foo" ) );
790 OWNERDOCUMENT_CREATE_TEST( QDomProcessingInstruction, domDoc.createProcessingInstruction( "foo", "bar" ) );
791 OWNERDOCUMENT_CREATE_TEST( QDomText, domDoc.createTextNode( "foo" ) );
792 }
793
794 // test importNode()
795 {
796 QDomDocument doc2;
797 OWNERDOCUMENT_IMPORTNODE_TEST( QDomAttr, doc2.createAttribute( "foo" ) );
798 OWNERDOCUMENT_IMPORTNODE_TEST( QDomAttr, doc2.createAttributeNS( "foo", "bar" ) );
799 OWNERDOCUMENT_IMPORTNODE_TEST( QDomCDATASection, doc2.createCDATASection( "foo" ) );
800 OWNERDOCUMENT_IMPORTNODE_TEST( QDomComment, doc2.createComment( "foo" ) );
801 OWNERDOCUMENT_IMPORTNODE_TEST( QDomDocumentFragment, doc2.createDocumentFragment() );
802 OWNERDOCUMENT_IMPORTNODE_TEST( QDomElement, doc2.createElement( "foo" ) );
803 OWNERDOCUMENT_IMPORTNODE_TEST( QDomElement, doc2.createElementNS( "foo", "bar" ) );
804 OWNERDOCUMENT_IMPORTNODE_TEST( QDomEntityReference, doc2.createEntityReference( "foo" ) );
805 OWNERDOCUMENT_IMPORTNODE_TEST( QDomProcessingInstruction, doc2.createProcessingInstruction( "foo", "bar" ) );
806 OWNERDOCUMENT_IMPORTNODE_TEST( QDomText, doc2.createTextNode( "foo" ) );
807
808 // QTBUG-12927
809 QVERIFY(doc2.importNode(QDomNode(), deep).isNull());
810 }
811}
812
813void tst_QDom::ownerDocumentTask27424_data()
814{
815 QTest::addColumn<bool>(name: "insertLevel1AfterCstr");
816 QTest::addColumn<bool>(name: "insertLevel2AfterCstr");
817 QTest::addColumn<bool>(name: "insertLevel3AfterCstr");
818
819 QTest::newRow( dataTag: "000" ) << false << false << false;
820 QTest::newRow( dataTag: "001" ) << false << false << true;
821 QTest::newRow( dataTag: "010" ) << false << true << false;
822 QTest::newRow( dataTag: "011" ) << false << true << true;
823 QTest::newRow( dataTag: "100" ) << true << false << false;
824 QTest::newRow( dataTag: "101" ) << true << false << true;
825 QTest::newRow( dataTag: "110" ) << true << true << false;
826 QTest::newRow( dataTag: "111" ) << true << true << true;
827}
828
829void tst_QDom::ownerDocumentTask27424()
830{
831 QFETCH( bool, insertLevel1AfterCstr );
832 QFETCH( bool, insertLevel2AfterCstr );
833 QFETCH( bool, insertLevel3AfterCstr );
834
835 QDomDocument doc("TestXML");
836
837 QDomElement level1 = doc.createElement(tagName: "Level_1");
838 QVERIFY( level1.ownerDocument() == doc );
839
840 if ( insertLevel1AfterCstr ) {
841 doc.appendChild(newChild: level1);
842 QVERIFY( level1.ownerDocument() == doc );
843 }
844
845 QDomElement level2 = level1.ownerDocument().createElement(tagName: "Level_2");
846 QVERIFY( level1.ownerDocument() == doc );
847 QVERIFY( level2.ownerDocument() == doc );
848
849 if ( insertLevel2AfterCstr ) {
850 level1.appendChild(newChild: level2);
851 QVERIFY( level1.ownerDocument() == doc );
852 QVERIFY( level2.ownerDocument() == doc );
853 }
854
855 QDomElement level3 = level2.ownerDocument().createElement(tagName: "Level_3");
856 QVERIFY( level1.ownerDocument() == doc );
857 QVERIFY( level2.ownerDocument() == doc );
858 QVERIFY( level3.ownerDocument() == doc );
859
860 if ( insertLevel3AfterCstr ) {
861 level2.appendChild(newChild: level3);
862 QVERIFY( level1.ownerDocument() == doc );
863 QVERIFY( level2.ownerDocument() == doc );
864 QVERIFY( level3.ownerDocument() == doc );
865 }
866
867 QDomNode level4 = level3.ownerDocument().createTextNode(data: "This_is_a_value!");
868 QVERIFY( level4.ownerDocument() == doc );
869
870 level3.appendChild(newChild: level4);
871 QVERIFY( level1.ownerDocument() == doc );
872 QVERIFY( level2.ownerDocument() == doc );
873 QVERIFY( level3.ownerDocument() == doc );
874 QVERIFY( level4.ownerDocument() == doc );
875
876 if ( !insertLevel3AfterCstr ) {
877 level2.appendChild(newChild: level3);
878 QVERIFY( level1.ownerDocument() == doc );
879 QVERIFY( level2.ownerDocument() == doc );
880 QVERIFY( level3.ownerDocument() == doc );
881 QVERIFY( level4.ownerDocument() == doc );
882 }
883
884 if ( !insertLevel2AfterCstr ) {
885 level1.appendChild(newChild: level2);
886 QVERIFY( level1.ownerDocument() == doc );
887 QVERIFY( level2.ownerDocument() == doc );
888 QVERIFY( level3.ownerDocument() == doc );
889 QVERIFY( level4.ownerDocument() == doc );
890 }
891
892 if ( !insertLevel1AfterCstr ) {
893 doc.appendChild(newChild: level1);
894 QVERIFY( level1.ownerDocument() == doc );
895 QVERIFY( level2.ownerDocument() == doc );
896 QVERIFY( level3.ownerDocument() == doc );
897 QVERIFY( level4.ownerDocument() == doc );
898 }
899}
900
901void tst_QDom::parentNode_data()
902{
903 cloneNode_data();
904}
905
906#define PARENTNODE_CREATE_TEST( t, x ) \
907{ \
908 t n = x; \
909 QVERIFY( n.parentNode().isNull() ); \
910}
911
912void tst_QDom::parentNode()
913{
914 QFETCH( QString, doc );
915 QFETCH( QList<QVariant>, pathToNode );
916 QFETCH( bool, deep );
917 QDomDocument domDoc;
918 QVERIFY( domDoc.setContent( doc ) );
919 QDomNode node = findDomNode( doc: domDoc, pathToNode );
920 QVERIFY(!node.isNull());
921 Q_UNUSED(deep);
922
923 // test QDomDocument::create...()
924 {
925 PARENTNODE_CREATE_TEST( QDomAttr, domDoc.createAttribute( "foo" ) );
926 PARENTNODE_CREATE_TEST( QDomAttr, domDoc.createAttributeNS( "foo", "bar" ) );
927 PARENTNODE_CREATE_TEST( QDomCDATASection, domDoc.createCDATASection( "foo" ) );
928 PARENTNODE_CREATE_TEST( QDomComment, domDoc.createComment( "foo" ) );
929 PARENTNODE_CREATE_TEST( QDomDocumentFragment, domDoc.createDocumentFragment() );
930 PARENTNODE_CREATE_TEST( QDomElement, domDoc.createElement( "foo" ) );
931 PARENTNODE_CREATE_TEST( QDomElement, domDoc.createElementNS( "foo", "bar" ) );
932 PARENTNODE_CREATE_TEST( QDomEntityReference, domDoc.createEntityReference( "foo" ) );
933 PARENTNODE_CREATE_TEST( QDomProcessingInstruction, domDoc.createProcessingInstruction( "foo", "bar" ) );
934 PARENTNODE_CREATE_TEST( QDomText, domDoc.createTextNode( "foo" ) );
935 }
936}
937
938
939void tst_QDom::documentCreationTask27424_data()
940{
941 QTest::addColumn<bool>(name: "insertLevel1AfterCstr");
942 QTest::addColumn<bool>(name: "insertLevel2AfterCstr");
943 QTest::addColumn<bool>(name: "insertLevel3AfterCstr");
944
945 QTest::newRow( dataTag: "000" ) << false << false << false;
946 QTest::newRow( dataTag: "001" ) << false << false << true;
947 QTest::newRow( dataTag: "010" ) << false << true << false;
948 QTest::newRow( dataTag: "011" ) << false << true << true;
949 QTest::newRow( dataTag: "100" ) << true << false << false;
950 QTest::newRow( dataTag: "101" ) << true << false << true;
951 QTest::newRow( dataTag: "110" ) << true << true << false;
952 QTest::newRow( dataTag: "111" ) << true << true << true;
953}
954
955void tst_QDom::documentCreationTask27424()
956{
957 QFETCH( bool, insertLevel1AfterCstr );
958 QFETCH( bool, insertLevel2AfterCstr );
959 QFETCH( bool, insertLevel3AfterCstr );
960
961 QDomDocument docRes;
962 QVERIFY( docRes.setContent( QString(
963 "<!DOCTYPE TestXML>\n"
964 "<Level_1>\n"
965 " <Level_2>\n"
966 " <Level_3>This_is_a_value!</Level_3>\n"
967 " </Level_2>\n"
968 "</Level_1>"
969 ) ) );
970
971 QDomDocument doc("TestXML");
972
973 QDomElement level1 = doc.createElement(tagName: "Level_1");
974 if ( insertLevel1AfterCstr )
975 doc.appendChild(newChild: level1);
976
977 QDomElement level2 = level1.ownerDocument().createElement(tagName: "Level_2");
978 if ( insertLevel2AfterCstr )
979 level1.appendChild(newChild: level2);
980
981 QDomElement level3 = level2.ownerDocument().createElement(tagName: "Level_3");
982 if ( insertLevel3AfterCstr )
983 level2.appendChild(newChild: level3);
984
985 QDomNode level4 = level3.ownerDocument().createTextNode(data: "This_is_a_value!");
986 level3.appendChild(newChild: level4);
987
988 if ( !insertLevel3AfterCstr )
989 level2.appendChild(newChild: level3);
990 if ( !insertLevel2AfterCstr )
991 level1.appendChild(newChild: level2);
992 if ( !insertLevel1AfterCstr )
993 doc.appendChild(newChild: level1);
994
995 QVERIFY( compareDocuments( doc, docRes ) );
996}
997
998
999bool tst_QDom::isFakeXMLDeclaration(const QDomNode &node)
1000{
1001 return node.isProcessingInstruction() &&
1002 node.nodeName() == QLatin1String("xml");
1003}
1004
1005bool tst_QDom::isDeepEqual(const QDomNode &n1, const QDomNode &n2)
1006{
1007 const QDomNode::NodeType nt = n1.nodeType();
1008
1009 if(nt != n2.nodeType())
1010 return false;
1011
1012 if(n1.nodeName() != n2.nodeName()
1013 || n1.namespaceURI() != n2.namespaceURI()
1014 || n1.nodeValue() != n2.nodeValue())
1015 return false;
1016
1017 /* Check the children. */
1018 const QDomNodeList children1(n1.childNodes());
1019 const QDomNodeList children2(n2.childNodes());
1020 uint len1 = children1.length();
1021 uint len2 = children2.length();
1022 uint i1 = 0;
1023 uint i2 = 0;
1024
1025 if(len1 != 0 && isFakeXMLDeclaration(node: children1.at(index: 0)))
1026 ++i1;
1027
1028 if(len2 != 0 && isFakeXMLDeclaration(node: children2.at(index: 0)))
1029 ++i2;
1030
1031 if(len1 - i1 != len2 - i2)
1032 return false;
1033
1034 // We jump over the first to skip the processing instructions that
1035 // are (incorrectly) used as XML declarations.
1036 for(; i1 < len1; ++i1)
1037 {
1038 if(!isDeepEqual(n1: children1.at(index: i1), n2: children2.at(index: i2)))
1039 return false;
1040
1041 ++i2;
1042 }
1043
1044 return true;
1045}
1046
1047/*
1048 Returns true if \a doc1 and \a doc2 represent the same XML document, i.e.
1049 they have the same informational content. Otherwise, this function returns
1050 false.
1051*/
1052bool tst_QDom::compareDocuments( const QDomDocument &doc1, const QDomDocument &doc2 )
1053{
1054 return isDeepEqual(n1: doc1, n2: doc2);
1055}
1056
1057/*
1058 Returns true if \a node1 and \a node2 represent the same XML node, i.e.
1059 they have the same informational content. Otherwise, this function returns
1060 false.
1061
1062 If \a deep is true, children of the nodes are also tested. If \a deep is
1063 false, only \a node1 and \a node 2 are compared.
1064*/
1065bool tst_QDom::compareNodes( const QDomNode &node1, const QDomNode &node2, bool deep )
1066{
1067 if ( deep ) {
1068 QString str1;
1069 {
1070 QTextStream stream( &str1 );
1071 stream << node1;
1072 }
1073 QString str2;
1074 {
1075 QTextStream stream( &str2 );
1076 stream << node2;
1077 }
1078 return str1 == str2;
1079 }
1080
1081 if ( node1.isNull() && node2.isNull() )
1082 return true;
1083 // ### I am not sure if this test is complete
1084 bool equal = node1.nodeName() == node2.nodeName();
1085 equal = equal && node1.nodeType() == node2.nodeType();
1086 equal = equal && node1.localName() == node2.localName();
1087 equal = equal && node1.nodeValue() == node2.nodeValue();
1088 equal = equal && node1.prefix() == node2.prefix();
1089
1090 return equal;
1091}
1092
1093/*
1094 \a pathToNode is a list of indices to wanted node in \a doc. Returns the
1095 wanted node.
1096*/
1097QDomNode tst_QDom::findDomNode( const QDomDocument &doc, const QList<QVariant> &pathToNode )
1098{
1099 QDomNode node = doc;
1100 QList<QVariant>::const_iterator it;
1101 for ( it = pathToNode.begin(); it != pathToNode.end(); ++it ) {
1102 QDomNodeList children = node.childNodes();
1103 node = children.item( index: (*it).toInt() );
1104// QVERIFY( !node.isNull() );
1105 }
1106 return node;
1107}
1108
1109void tst_QDom::browseElements()
1110{
1111 QDomDocument doc;
1112 QDomElement root = doc.createElement(tagName: "foo");
1113 doc.appendChild(newChild: root);
1114 root.appendChild(newChild: doc.createElement(tagName: "bar"));
1115 root.appendChild(newChild: doc.createElement(tagName: "bop"));
1116 root.appendChild(newChild: doc.createElement(tagName: "bar"));
1117 root.appendChild(newChild: doc.createElement(tagName: "bop"));
1118
1119 QVERIFY(doc.firstChildElement("ding").isNull());
1120 QDomElement foo = doc.firstChildElement(tagName: "foo");
1121 QVERIFY(!foo.isNull());
1122 QVERIFY(foo.firstChildElement("ding").isNull());
1123 QVERIFY(foo.nextSiblingElement("foo").isNull());
1124 QVERIFY(foo.previousSiblingElement("bar").isNull());
1125 QVERIFY(foo.nextSiblingElement().isNull());
1126 QVERIFY(foo.previousSiblingElement().isNull());
1127
1128 QDomElement bar = foo.firstChildElement(tagName: "bar");
1129 QVERIFY(!bar.isNull());
1130 QVERIFY(bar.previousSiblingElement("bar").isNull());
1131 QVERIFY(bar.previousSiblingElement().isNull());
1132 QCOMPARE(bar.nextSiblingElement("bar").tagName(), QLatin1String("bar"));
1133 QVERIFY(bar.nextSiblingElement("bar").nextSiblingElement("bar").isNull());
1134
1135 QDomElement bop = foo.firstChildElement(tagName: "bop");
1136 QVERIFY(!bop.isNull());
1137 QCOMPARE(bar.nextSiblingElement(), bop);
1138 QCOMPARE(bop.nextSiblingElement("bop"), foo.lastChildElement("bop"));
1139 QCOMPARE(bop.previousSiblingElement("bar"), foo.firstChildElement("bar"));
1140 QCOMPARE(bop.previousSiblingElement("bar"), foo.firstChildElement());
1141}
1142
1143void tst_QDom::domNodeMapAndList()
1144{
1145 QString xml_str = QString::fromLatin1(str: "<foo ding='dong'></foo>");
1146
1147 QDomDocument doc;
1148 QVERIFY(doc.setContent(xml_str));
1149
1150 QDomNamedNodeMap map = doc.documentElement().attributes();
1151 QCOMPARE(map.item(0).nodeName(), QString("ding"));
1152 QCOMPARE(map.item(1).nodeName(), QString()); // Make sure we don't assert
1153
1154 QDomNodeList list = doc.elementsByTagName(tagname: "foo");
1155 QCOMPARE(list.item(0).nodeName(), QString("foo"));
1156 QCOMPARE(list.item(1).nodeName(), QString()); // Make sure we don't assert
1157}
1158
1159// Verifies that a default-constructed QDomDocument is null, and that calling
1160// any of the factory functions causes it to be non-null.
1161#define TEST_NULL_DOCUMENT(func) \
1162{ \
1163 QDomDocument doc; \
1164 QVERIFY(doc.isNull()); \
1165 QVERIFY(!doc.func.isNull()); \
1166 QVERIFY(!doc.isNull()); \
1167}
1168
1169void tst_QDom::nullDocument()
1170{
1171 TEST_NULL_DOCUMENT(createAttribute("foo"))
1172 TEST_NULL_DOCUMENT(createAttributeNS("http://foo/", "bar"))
1173 TEST_NULL_DOCUMENT(createCDATASection("foo"))
1174 TEST_NULL_DOCUMENT(createComment("foo"))
1175 TEST_NULL_DOCUMENT(createDocumentFragment())
1176 TEST_NULL_DOCUMENT(createElement("foo"))
1177 TEST_NULL_DOCUMENT(createElementNS("http://foo/", "foo"))
1178 TEST_NULL_DOCUMENT(createEntityReference("foo"))
1179 TEST_NULL_DOCUMENT(createProcessingInstruction("foo", "bar"))
1180 TEST_NULL_DOCUMENT(createTextNode("foo"))
1181 QDomDocument doc2;
1182 QDomElement elt = doc2.createElement(tagName: "foo");
1183 doc2.appendChild(newChild: elt);
1184 TEST_NULL_DOCUMENT(importNode(elt, true))
1185}
1186
1187#undef TEST_NULL_DOCUMENT
1188
1189void tst_QDom::invalidName_data()
1190{
1191 QTest::addColumn<QString>(name: "in_name");
1192 QTest::addColumn<bool>(name: "ok_AcceptInvalidChars");
1193 QTest::addColumn<bool>(name: "ok_DropInvalidChars");
1194 QTest::addColumn<bool>(name: "ok_ReturnNullNode");
1195 QTest::addColumn<QString>(name: "out_name");
1196
1197 QTest::newRow( dataTag: "foo" ) << QString("foo") << true << true << true << QString("foo");
1198 QTest::newRow( dataTag: "_f.o-o:" ) << QString("_f.o-o:") << true << true << true << QString("_f.o-o:");
1199 QTest::newRow( dataTag: "...:." ) << QString("...:.") << true << true << false << QString(":.");
1200 QTest::newRow( dataTag: "empty" ) << QString() << false << false << false << QString();
1201 QTest::newRow( dataTag: "~f~o~o~" ) << QString("~f~o~o~") << true << true << false << QString("foo");
1202 QTest::newRow( dataTag: "~" ) << QString("~") << true << false << false << QString();
1203 QTest::newRow( dataTag: "..." ) << QString("...") << true << false << false << QString();
1204}
1205
1206void tst_QDom::invalidName()
1207{
1208 QFETCH( QString, in_name );
1209 QFETCH( bool, ok_AcceptInvalidChars );
1210 QFETCH( bool, ok_DropInvalidChars );
1211 QFETCH( bool, ok_ReturnNullNode );
1212 QFETCH( QString, out_name );
1213
1214 QDomImplementation impl;
1215 QDomDocument doc;
1216
1217 QDomImplementation::setInvalidDataPolicy(QDomImplementation::AcceptInvalidChars);
1218
1219 {
1220 QDomElement elt = doc.createElement(tagName: in_name);
1221 QDomElement elt_ns = doc.createElementNS(nsURI: "foo", qName: "foo:" + in_name);
1222 QDomAttr attr = doc.createAttribute(name: in_name);
1223 QDomAttr attr_ns = doc.createAttributeNS(nsURI: "foo", qName: "foo:" + in_name);
1224 QDomEntityReference ref = doc.createEntityReference(name: in_name);
1225
1226 QCOMPARE(!elt.isNull(), ok_AcceptInvalidChars);
1227 QCOMPARE(!elt_ns.isNull(), ok_AcceptInvalidChars);
1228 QCOMPARE(!attr.isNull(), ok_AcceptInvalidChars);
1229 QCOMPARE(!attr_ns.isNull(), ok_AcceptInvalidChars);
1230 QCOMPARE(!ref.isNull(), ok_AcceptInvalidChars);
1231
1232 if (ok_AcceptInvalidChars) {
1233 QCOMPARE(elt.tagName(), in_name);
1234 QCOMPARE(elt_ns.tagName(), in_name);
1235 QCOMPARE(attr.name(), in_name);
1236 QCOMPARE(attr_ns.name(), in_name);
1237 QCOMPARE(ref.nodeName(), in_name);
1238 }
1239 }
1240
1241 QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars);
1242
1243 {
1244 QDomElement elt = doc.createElement(tagName: in_name);
1245 QDomElement elt_ns = doc.createElementNS(nsURI: "foo", qName: "foo:" + in_name);
1246 QDomAttr attr = doc.createAttribute(name: in_name);
1247 QDomAttr attr_ns = doc.createAttributeNS(nsURI: "foo", qName: "foo:" + in_name);
1248 QDomEntityReference ref = doc.createEntityReference(name: in_name);
1249
1250 QCOMPARE(!elt.isNull(), ok_DropInvalidChars);
1251 QCOMPARE(!elt_ns.isNull(), ok_DropInvalidChars);
1252 QCOMPARE(!attr.isNull(), ok_DropInvalidChars);
1253 QCOMPARE(!attr_ns.isNull(), ok_DropInvalidChars);
1254 QCOMPARE(!ref.isNull(), ok_DropInvalidChars);
1255
1256 if (ok_DropInvalidChars) {
1257 QCOMPARE(elt.tagName(), out_name);
1258 QCOMPARE(elt_ns.tagName(), out_name);
1259 QCOMPARE(attr.name(), out_name);
1260 QCOMPARE(attr_ns.name(), out_name);
1261 QCOMPARE(ref.nodeName(), out_name);
1262 }
1263 }
1264
1265 QDomImplementation::setInvalidDataPolicy(QDomImplementation::ReturnNullNode);
1266
1267 {
1268 QDomElement elt = doc.createElement(tagName: in_name);
1269 QDomElement elt_ns = doc.createElementNS(nsURI: "foo", qName: "foo:" + in_name);
1270 QDomAttr attr = doc.createAttribute(name: in_name);
1271 QDomAttr attr_ns = doc.createAttributeNS(nsURI: "foo", qName: "foo:" + in_name);
1272 QDomEntityReference ref = doc.createEntityReference(name: in_name);
1273
1274 QCOMPARE(!elt.isNull(), ok_ReturnNullNode);
1275 QCOMPARE(!elt_ns.isNull(), ok_ReturnNullNode);
1276 QCOMPARE(!attr.isNull(), ok_ReturnNullNode);
1277 QCOMPARE(!attr_ns.isNull(), ok_ReturnNullNode);
1278 QCOMPARE(!ref.isNull(), ok_ReturnNullNode);
1279
1280 if (ok_ReturnNullNode) {
1281 QCOMPARE(elt.tagName(), in_name);
1282 QCOMPARE(elt_ns.tagName(), in_name);
1283 QCOMPARE(attr.name(), in_name);
1284 QCOMPARE(attr_ns.name(), in_name);
1285 QCOMPARE(ref.nodeName(), in_name);
1286 }
1287 }
1288}
1289
1290void tst_QDom::invalidQualifiedName_data()
1291{
1292 QTest::addColumn<QString>(name: "in_name");
1293 QTest::addColumn<bool>(name: "ok_AcceptInvalidChars");
1294 QTest::addColumn<bool>(name: "ok_DropInvalidChars");
1295 QTest::addColumn<bool>(name: "ok_ReturnNullNode");
1296 QTest::addColumn<QString>(name: "out_name");
1297
1298 QTest::newRow( dataTag: "foo" ) << QString("foo") << true << true << true << QString("foo");
1299 QTest::newRow( dataTag: "foo:bar" ) << QString("foo:bar") << true << true << true << QString("foo:bar");
1300 QTest::newRow( dataTag: "bar:" ) << QString("bar:") << false << false << false << QString();
1301 QTest::newRow( dataTag: ":" ) << QString(":") << false << false << false << QString();
1302 QTest::newRow( dataTag: "empty" ) << QString() << false << false << false << QString();
1303 QTest::newRow(dataTag: "foo:...:.") << QString("foo:...:.")<< true << true << false << QString("foo::.");
1304 QTest::newRow(dataTag: "foo:~") << QString("foo:~") << true << false << false << QString();
1305 QTest::newRow(dataTag: "foo:.~") << QString("foo:.~") << true << false << false << QString();
1306}
1307
1308void tst_QDom::invalidQualifiedName()
1309{
1310 QFETCH( QString, in_name );
1311 QFETCH( bool, ok_AcceptInvalidChars );
1312 QFETCH( bool, ok_DropInvalidChars );
1313 QFETCH( bool, ok_ReturnNullNode );
1314 QFETCH( QString, out_name );
1315
1316 QDomImplementation impl;
1317 QDomDocument doc;
1318
1319 QDomImplementation::setInvalidDataPolicy(QDomImplementation::AcceptInvalidChars);
1320
1321 {
1322 QDomElement elt_ns = doc.createElementNS(nsURI: "foo", qName: in_name);
1323 QDomAttr attr_ns = doc.createAttributeNS(nsURI: "foo", qName: in_name);
1324 QDomDocumentType doctype = impl.createDocumentType(qName: in_name, publicId: "foo", systemId: "bar");
1325 QDomDocument doc2 = impl.createDocument(nsURI: "foo", qName: in_name, doctype);
1326
1327 QCOMPARE(!elt_ns.isNull(), ok_AcceptInvalidChars);
1328 QCOMPARE(!attr_ns.isNull(), ok_AcceptInvalidChars);
1329 QCOMPARE(!doctype.isNull(), ok_AcceptInvalidChars);
1330 QCOMPARE(!doc2.isNull(), ok_AcceptInvalidChars);
1331
1332 if (ok_AcceptInvalidChars) {
1333 QCOMPARE(elt_ns.nodeName(), in_name);
1334 QCOMPARE(attr_ns.nodeName(), in_name);
1335 QCOMPARE(doctype.name(), in_name);
1336 QCOMPARE(doc2.documentElement().nodeName(), in_name);
1337 }
1338 }
1339
1340 QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars);
1341
1342 {
1343 QDomElement elt_ns = doc.createElementNS(nsURI: "foo", qName: in_name);
1344 QDomAttr attr_ns = doc.createAttributeNS(nsURI: "foo", qName: in_name);
1345 QDomDocumentType doctype = impl.createDocumentType(qName: in_name, publicId: "foo", systemId: "bar");
1346 QDomDocument doc2 = impl.createDocument(nsURI: "foo", qName: in_name, doctype);
1347
1348 QCOMPARE(!elt_ns.isNull(), ok_DropInvalidChars);
1349 QCOMPARE(!attr_ns.isNull(), ok_DropInvalidChars);
1350 QCOMPARE(!doctype.isNull(), ok_DropInvalidChars);
1351 QCOMPARE(!doc2.isNull(), ok_DropInvalidChars);
1352
1353 if (ok_DropInvalidChars) {
1354 QCOMPARE(elt_ns.nodeName(), out_name);
1355 QCOMPARE(attr_ns.nodeName(), out_name);
1356 QCOMPARE(doctype.name(), out_name);
1357 QCOMPARE(doc2.documentElement().nodeName(), out_name);
1358 }
1359 }
1360
1361 QDomImplementation::setInvalidDataPolicy(QDomImplementation::ReturnNullNode);
1362
1363 {
1364 QDomElement elt_ns = doc.createElementNS(nsURI: "foo", qName: in_name);
1365 QDomAttr attr_ns = doc.createAttributeNS(nsURI: "foo", qName: in_name);
1366 QDomDocumentType doctype = impl.createDocumentType(qName: in_name, publicId: "foo", systemId: "bar");
1367 QDomDocument doc2 = impl.createDocument(nsURI: "foo", qName: in_name, doctype);
1368
1369 QCOMPARE(!elt_ns.isNull(), ok_ReturnNullNode);
1370 QCOMPARE(!attr_ns.isNull(), ok_ReturnNullNode);
1371 QCOMPARE(!doctype.isNull(), ok_ReturnNullNode);
1372 QCOMPARE(!doc2.isNull(), ok_ReturnNullNode);
1373
1374 if (ok_ReturnNullNode) {
1375 QCOMPARE(elt_ns.nodeName(), in_name);
1376 QCOMPARE(attr_ns.nodeName(), in_name);
1377 QCOMPARE(doctype.name(), in_name);
1378 QCOMPARE(doc2.documentElement().nodeName(), in_name);
1379 }
1380 }
1381}
1382
1383void tst_QDom::invalidCharData_data()
1384{
1385 QTest::addColumn<QString>(name: "in_text");
1386 QTest::addColumn<bool>(name: "ok_AcceptInvalidChars");
1387 QTest::addColumn<bool>(name: "ok_DropInvalidChars");
1388 QTest::addColumn<bool>(name: "ok_ReturnNullNode");
1389 QTest::addColumn<QString>(name: "out_text");
1390
1391 QTest::newRow( dataTag: "foo" ) << QString("foo") << true << true << true << QString("foo");
1392 QTest::newRow( dataTag: "f<o&o" ) << QString("f<o&o") << true << true << true << QString("f<o&o");
1393 QTest::newRow( dataTag: "empty" ) << QString() << true << true << true << QString();
1394 QTest::newRow(dataTag: "f\\x07o\\x02")<< QString("f\x07o\x02")<< true << true << false << QString("fo");
1395}
1396
1397void tst_QDom::invalidCharData()
1398{
1399 QFETCH( QString, in_text );
1400 QFETCH( bool, ok_AcceptInvalidChars );
1401 QFETCH( bool, ok_DropInvalidChars );
1402 QFETCH( bool, ok_ReturnNullNode );
1403 QFETCH( QString, out_text );
1404
1405 QDomDocument doc;
1406
1407 QDomImplementation::setInvalidDataPolicy(QDomImplementation::AcceptInvalidChars);
1408
1409 {
1410 QDomText text_elt = doc.createTextNode(data: in_text);
1411 QCOMPARE(!text_elt.isNull(), ok_AcceptInvalidChars);
1412 if (ok_AcceptInvalidChars) {
1413 QCOMPARE(text_elt.nodeValue(), in_text);
1414 }
1415 }
1416
1417 QDomImplementation::setInvalidDataPolicy(QDomImplementation::DropInvalidChars);
1418
1419 {
1420 QDomText text_elt = doc.createTextNode(data: in_text);
1421 QCOMPARE(!text_elt.isNull(), ok_DropInvalidChars);
1422 if (ok_DropInvalidChars) {
1423 QCOMPARE(text_elt.nodeValue(), out_text);
1424 }
1425 }
1426
1427 QDomImplementation::setInvalidDataPolicy(QDomImplementation::ReturnNullNode);
1428
1429 {
1430 QDomText text_elt = doc.createTextNode(data: in_text);
1431 QCOMPARE(!text_elt.isNull(), ok_ReturnNullNode);
1432 if (ok_ReturnNullNode) {
1433 QCOMPARE(text_elt.nodeValue(), in_text);
1434 }
1435 }
1436}
1437
1438void tst_QDom::roundTripAttributes() const
1439{
1440 /* Create an attribute via the QDom API with weird whitespace content. */
1441 QDomImplementation impl;
1442
1443 QDomDocument doc(impl.createDocument(nsURI: "", qName: "localName", doctype: QDomDocumentType()));
1444
1445 QDomElement e(doc.documentElement());
1446
1447 QString ws;
1448 ws.reserve(asize: 8);
1449 ws.append(c: QChar(0x20));
1450 ws.append(c: QChar(0x20));
1451 ws.append(c: QChar(0x20));
1452 ws.append(c: QChar(0xD));
1453 ws.append(c: QChar(0xA));
1454 ws.append(c: QChar(0x9));
1455 ws.append(c: QChar(0x20));
1456 ws.append(c: QChar(0x20));
1457
1458 e.setAttribute(name: "attr", value: ws);
1459
1460 QByteArray serialized;
1461 QBuffer buffer(&serialized);
1462 buffer.open(openMode: QIODevice::WriteOnly);
1463 QTextStream stream(&buffer);
1464
1465 doc.save(stream, 0);
1466 stream.flush();
1467
1468 const QByteArray expected("<localName xmlns=\"\" attr=\" &#xd;&#xa;&#x9; \"/>\n");
1469 QCOMPARE(QString::fromLatin1(serialized.constData()), QString::fromLatin1(expected.constData()));
1470}
1471
1472void tst_QDom::normalizeEndOfLine() const
1473{
1474 QByteArray input("<a>\r\nc\rc\ra\na</a>");
1475
1476 QBuffer buffer(&input);
1477 QVERIFY(buffer.open(QIODevice::ReadOnly));
1478
1479 QDomDocument doc;
1480 QVERIFY(doc.setContent(&buffer, true));
1481
1482 const QString expected(QLatin1String("<a>\nc\nc\na\na</a>"));
1483
1484 // ### Qt 6: fix this, if we keep QDom at all
1485 QEXPECT_FAIL("", "The parser doesn't perform newline normalization. Fixing that would change behavior.", Continue);
1486 QCOMPARE(doc.documentElement().text(), expected);
1487}
1488
1489void tst_QDom::normalizeAttributes() const
1490{
1491 QByteArray data("<element attribute=\"a\na\"/>");
1492 QBuffer buffer(&data);
1493
1494 QVERIFY(buffer.open(QIODevice::ReadOnly));
1495
1496 QDomDocument doc;
1497 QVERIFY(doc.setContent(&buffer, true));
1498
1499#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
1500 QEXPECT_FAIL("", "The parser doesn't perform Attribute Value Normalization. Fixing that would change behavior.", Continue);
1501#endif
1502 QCOMPARE(doc.documentElement().attribute(QLatin1String("attribute")), QString::fromLatin1("a a"));
1503}
1504
1505void tst_QDom::serializeWeirdEOL() const
1506{
1507 QDomImplementation impl;
1508
1509 QDomDocument doc(impl.createDocument(nsURI: "", qName: "name", doctype: QDomDocumentType()));
1510 QDomElement ele(doc.documentElement());
1511 ele.appendChild(newChild: doc.createTextNode(data: QLatin1String("\r\nasd\nasd\rasd\n")));
1512
1513 QByteArray output;
1514 QBuffer writeBuffer(&output);
1515 QVERIFY(writeBuffer.open(QIODevice::WriteOnly));
1516 QTextStream stream(&writeBuffer);
1517
1518 const QByteArray expected("<name xmlns=\"\">&#xd;\nasd\nasd&#xd;asd\n</name>\n");
1519 doc.save(stream, 0);
1520 QCOMPARE(QString::fromLatin1(output.constData()), QString::fromLatin1(expected.constData()));
1521}
1522
1523void tst_QDom::reparentAttribute() const
1524{
1525 QDomImplementation impl;
1526 QDomDocument doc(impl.createDocument(nsURI: "", qName: "localName", doctype: QDomDocumentType()));
1527
1528 QDomElement ele(doc.documentElement());
1529 QDomAttr attr(doc.createAttribute(name: "localName"));
1530 ele.setAttributeNode(attr);
1531
1532 QVERIFY(attr.ownerElement() == ele);
1533 QVERIFY(attr.parentNode() == ele);
1534}
1535
1536void tst_QDom::serializeNamespaces() const
1537{
1538 const char *const input = "<doc xmlns:b='http://example.com/'>"
1539 "<b:element b:name=''/>"
1540 "</doc>";
1541
1542 QDomDocument doc;
1543 QByteArray ba(input);
1544#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
1545QT_WARNING_PUSH
1546QT_WARNING_DISABLE_DEPRECATED
1547
1548 QBuffer buffer(&ba);
1549 QVERIFY(buffer.open(QIODevice::ReadOnly));
1550
1551 QXmlInputSource source(&buffer);
1552 QXmlSimpleReader reader;
1553 reader.setFeature(name: "http://xml.org/sax/features/namespaces", value: true);
1554 reader.setFeature(name: "http://xml.org/sax/features/namespace-prefixes", value: false);
1555
1556 QVERIFY(doc.setContent(&source, &reader));
1557QT_WARNING_POP
1558#else
1559 QXmlStreamReader streamReader(input);
1560 QVERIFY(doc.setContent(&streamReader, true));
1561#endif
1562
1563 const QByteArray serialized(doc.toByteArray());
1564
1565 QDomDocument doc2;
1566 QVERIFY(doc2.setContent(doc.toString(), true));
1567
1568 /* Here we test that it roundtrips. */
1569 QVERIFY(isDeepEqual(doc2, doc));
1570
1571 QDomDocument doc3;
1572 QVERIFY(doc3.setContent(QString::fromLatin1(serialized.constData()), true));
1573
1574 QVERIFY(isDeepEqual(doc3, doc));
1575}
1576
1577void tst_QDom::flagInvalidNamespaces() const
1578{
1579 const char *const input = "<doc>"
1580 "<b:element xmlns:b='http://example.com/' b:name='' xmlns:b='http://example.com/'/>"
1581 "</doc>";
1582
1583 QDomDocument doc;
1584 QVERIFY(!doc.setContent(QString::fromLatin1(input, true)));
1585#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
1586 QEXPECT_FAIL("", "The parser doesn't flag identical qualified attribute names. Fixing this would change behavior.", Continue);
1587#endif
1588 QVERIFY(!doc.setContent(QString::fromLatin1(input)));
1589}
1590
1591void tst_QDom::flagUndeclaredNamespace() const
1592{
1593 /* Note, prefix 'a' is not declared. */
1594 const char *const input = "<a:doc xmlns:b='http://example.com/'>"
1595 "<b:element b:name=''/>"
1596 "</a:doc>";
1597
1598 QDomDocument doc;
1599 QByteArray ba(input);
1600#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
1601QT_WARNING_PUSH
1602QT_WARNING_DISABLE_DEPRECATED
1603 QBuffer buffer(&ba);
1604
1605 QVERIFY(buffer.open(QIODevice::ReadOnly));
1606
1607 QXmlInputSource source(&buffer);
1608 QXmlSimpleReader reader;
1609 reader.setFeature(name: "http://xml.org/sax/features/namespaces", value: true);
1610 reader.setFeature(name: "http://xml.org/sax/features/namespace-prefixes", value: false);
1611
1612 QEXPECT_FAIL("", "The parser doesn't flag not declared prefixes. Fixing this would change behavior.", Continue);
1613 QVERIFY(!doc.setContent(&source, &reader));
1614QT_WARNING_POP
1615#else
1616 QXmlStreamReader streamReader(ba);
1617 QVERIFY(!doc.setContent(&streamReader, true));
1618#endif
1619}
1620
1621void tst_QDom::indentComments() const
1622{
1623 /* We test that:
1624 *
1625 * - Whitespace is not added if a text node appears after a comment.
1626 * - Whitespace is not added if a text node appears before a comment.
1627 * - Indentation depth is linear with level depth.
1628 */
1629 const char *const input = "<e>"
1630 "<!-- A Comment -->"
1631 "<b><!-- deep --></b>"
1632 "textNode"
1633 "<!-- Another Comment -->"
1634 "<!-- Another Comment2 -->"
1635 "textNode2"
1636 "</e>";
1637 const char *const expected = "<e>\n"
1638 " <!-- A Comment -->\n"
1639 " <b>\n"
1640 " <!-- deep -->\n"
1641 " </b>"
1642 "textNode"
1643 "<!-- Another Comment -->\n"
1644 " <!-- Another Comment2 -->"
1645 "textNode2"
1646 "</e>\n";
1647 QDomDocument doc;
1648 QVERIFY(doc.setContent(QString::fromLatin1(input)));
1649
1650 const QString serialized(doc.toString(5));
1651
1652 QCOMPARE(serialized, QString::fromLatin1(expected));
1653}
1654
1655void tst_QDom::checkLiveness() const
1656{
1657 QDomImplementation impl;
1658
1659 QDomDocument doc(impl.createDocument(nsURI: QString(), qName: "doc", doctype: QDomDocumentType()));
1660 QDomElement ele(doc.documentElement());
1661
1662 const QDomElement e1(doc.createElement(tagName: "name"));
1663 const QDomElement e2(doc.createElement(tagName: "name"));
1664 const QDomText t1(doc.createTextNode(data: "content"));
1665
1666 ele.appendChild(newChild: e1);
1667 ele.appendChild(newChild: t1);
1668 ele.appendChild(newChild: e2);
1669
1670 const QDomNodeList children(ele.childNodes());
1671 QCOMPARE(children.count(), 3);
1672
1673 ele.removeChild(oldChild: e1);
1674
1675 QCOMPARE(children.count(), 2);
1676 QCOMPARE(children.at(0), static_cast<const QDomNode &>(t1));
1677 QCOMPARE(children.at(1), static_cast<const QDomNode &>(e2));
1678}
1679
1680void tst_QDom::reportDuplicateAttributes() const
1681{
1682 QDomDocument dd;
1683 bool isSuccess = dd.setContent(text: QLatin1String("<test x=\"1\" x=\"2\"/>"));
1684
1685#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
1686 QEXPECT_FAIL("", "The parser doesn't flag duplicate attributes. Fixing this would change behavior.", Continue);
1687#endif
1688 QVERIFY2(!isSuccess, "Duplicate attributes are well-formedness errors, and should be reported as such.");
1689}
1690
1691void tst_QDom::namespacedAttributes() const
1692{
1693 static const char *const xml =
1694 "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n"
1695 "<xan:td xmlns:xan=\"http://www.someurl.com/Something\" "
1696 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
1697 " xsi:schemaLocation=\"http://www.someurl.com/Something/../../xml/td.xsd\" "
1698 " xmlns:xi=\"http://www.w3.org/2001/XInclude\" "
1699 " xmlns=\"http://www.someurl.com/Something\">\n"
1700 " <Title displayLabel='Title' >>>> SIMPLE BASIC OP - SEND - DUT AS SINK</Title>\n"
1701 "</xan:td>\n";
1702
1703 QDomDocument one("document");
1704 QString error;
1705 bool docParsed = one.setContent(text: QByteArray(xml), namespaceProcessing: true, errorMsg: &error);
1706 QVERIFY2(docParsed, qPrintable(error));
1707 QDomDocument two("document2");
1708 docParsed = two.setContent(text: one.toByteArray(2), namespaceProcessing: true, errorMsg: &error);
1709 QVERIFY2(docParsed, qPrintable(error));
1710
1711 QVERIFY(isDeepEqual(one, two));
1712}
1713
1714void tst_QDom::appendChildFromToDocument() const
1715{
1716 QDomDocument doc;
1717 const QByteArray input("<e/>");
1718
1719 doc.setContent(text: input);
1720
1721 QDomDocument doc2(doc.documentElement().toDocument());
1722 QDomElement element = doc2.createElement(tagName: "name");
1723 element.setAttribute(name: "name", value: "value");
1724 doc.documentElement().appendChild(newChild: element);
1725}
1726
1727void tst_QDom::iterateCDATA() const
1728{
1729 const QByteArray input("<e><![CDATA[data]]></e>");
1730
1731 QDomDocument doc;
1732 QVERIFY(doc.setContent(input));
1733 QCOMPARE(doc.toString(), QString("<e><![CDATA[data]]></e>\n"));
1734
1735 const QDomElement element(doc.documentElement());
1736 QVERIFY(!element.isNull());
1737
1738 /* The node at element.childNodes().at(0) is not an element,
1739 * it's a CDATA section. */
1740 const QDomElement child(element.childNodes().at(index: 0).toElement());
1741 QVERIFY(child.isNull());
1742
1743 QVERIFY(element.childNodes().at(0).isCDATASection());
1744}
1745
1746/*!
1747 \internal
1748 \since 4.4
1749 \brief This function cannot be factored into appendDocumentNode(). The
1750 invocation of constructors/destructors is part of triggering the bug.
1751 */
1752QDomDocument tst_QDom::generateRequest()
1753{
1754 QDomDocument doc;
1755 QDomElement elem = doc.createElement(tagName: "test_elem");
1756
1757 elem.setAttribute(name: "name", value: "value");
1758 doc.appendChild(newChild: elem);
1759 return doc;
1760}
1761
1762void tst_QDom::appendDocumentNode() const
1763{
1764 QDomDocument doc;
1765 QDomDocument xml = generateRequest();
1766 QDomElement elem = doc.createElement(tagName: "document");
1767
1768 doc.appendChild(newChild: elem);
1769
1770 QVERIFY(!xml.isNull());
1771 const QString expected(QLatin1String("<document>\n<test_elem name=\"value\"/>\n</document>\n"));
1772
1773 elem.appendChild(newChild: xml);
1774 QCOMPARE(doc.childNodes().count(), 1);
1775 QCOMPARE(doc.toString(0), expected);
1776
1777 elem.appendChild(newChild: xml.firstChild());
1778 QCOMPARE(doc.childNodes().count(), 1);
1779 QCOMPARE(doc.toString(0), expected);
1780}
1781
1782static const QChar umlautName[] =
1783{
1784 'a', 0xfc, 'b'
1785};
1786
1787/*!
1788 \internal
1789
1790 Write a german umlaut to a QByteArray, via a QTextStream.
1791 */
1792void tst_QDom::germanUmlautToByteArray() const
1793{
1794 QCOMPARE(ulong(sizeof(umlautName) / sizeof(QChar)), ulong(3));
1795 const QString name(umlautName, 3);
1796
1797 QDomDocument d;
1798 d.appendChild(newChild: d.createElement(tagName: name));
1799 QByteArray data;
1800 QBuffer buffer(&data);
1801 QVERIFY(buffer.open(QIODevice::WriteOnly));
1802 QTextStream ts(&buffer);
1803 ts.setCodec("UTF-8");
1804 ts << d.toString();
1805 buffer.close();
1806
1807 QByteArray baseline("<a");
1808
1809 /* http://www.fileformat.info/info/unicode/char/00FC/index.htm */
1810 baseline += char(0xC3);
1811 baseline += char(0xBC);
1812 baseline += "b/>\n";
1813
1814 QCOMPARE(data, baseline);
1815}
1816
1817/*!
1818 \internal
1819
1820 Write a german umlaut to a QFile, via a QTextStream.
1821 */
1822void tst_QDom::germanUmlautToFile() const
1823{
1824 /* http://www.fileformat.info/info/unicode/char/00FC/index.htm */
1825 QString name(QLatin1String("german"));
1826 name += QChar(0xFC);
1827 name += QLatin1String("umlaut");
1828 QCOMPARE(name.length(), 13);
1829
1830 QDomDocument d("test");
1831 d.appendChild(newChild: d.createElement(tagName: name));
1832 QTemporaryFile file;
1833 QVERIFY(file.open());
1834 QTextStream ts(&file);
1835 ts.setCodec("UTF-8");
1836 ts << d.toString();
1837 file.close();
1838
1839 QFile inFile(file.fileName());
1840 QVERIFY(inFile.open(QIODevice::ReadOnly));
1841
1842 QString baseline(QLatin1String("<!DOCTYPE test>\n<german"));
1843 baseline += QChar(0xFC);
1844 baseline += QLatin1String("umlaut/>\n");
1845
1846 const QByteArray in(inFile.readAll());
1847 /* Check that it was wwritten out correctly. */
1848 QCOMPARE(in.length(), 34);
1849 QCOMPARE(in, baseline.toUtf8());
1850 inFile.close();
1851
1852 /* Check that we read it in correctly with QDomDocument::setContent(). */
1853 QVERIFY(inFile.open(QIODevice::ReadOnly));
1854 QDomDocument dd;
1855 QVERIFY(dd.setContent(&inFile));
1856
1857 QCOMPARE(dd.toString(), baseline);
1858}
1859
1860void tst_QDom::setInvalidDataPolicy() const
1861{
1862 QDomImplementation::setInvalidDataPolicy(QDomImplementation::ReturnNullNode);
1863 QDomDocument doc;
1864 QDomElement elem = doc.createElement(tagName: "invalid name");
1865 QVERIFY(elem.isNull());
1866}
1867
1868void tst_QDom::crashInSetContent() const
1869{
1870 QDomImplementation::setInvalidDataPolicy(QDomImplementation::ReturnNullNode);
1871 QDomDocument docImport;
1872
1873 QCOMPARE(docImport.setContent(QLatin1String("<a:>text</a:>"), true), false);
1874 QVERIFY(docImport.setContent(QLatin1String("<?xml version=\"1.0\"?><e/>")));
1875}
1876
1877void tst_QDom::doubleNamespaceDeclarations() const
1878{
1879 QDomDocument doc;
1880
1881 QString testFile = QFINDTESTDATA("doubleNamespaces.xml");
1882 if (testFile.isEmpty())
1883 QFAIL("Cannot find test file doubleNamespaces.xml!");
1884 QFile file(testFile);
1885 QVERIFY(file.open(QIODevice::ReadOnly));
1886
1887#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
1888QT_WARNING_PUSH
1889QT_WARNING_DISABLE_DEPRECATED
1890 QXmlSimpleReader reader;
1891
1892 QXmlInputSource source(&file);
1893 QVERIFY(doc.setContent(&source, &reader));
1894QT_WARNING_POP
1895#else
1896 QXmlStreamReader streamReader(&file);
1897 QVERIFY(doc.setContent(&streamReader, true));
1898#endif
1899
1900 // tst_QDom relies on a specific QHash ordering, see QTBUG-25071
1901 QString docAsString = doc.toString(0);
1902 QVERIFY(docAsString == QString::fromLatin1("<a>\n<b p:c=\"\" xmlns:p=\"NS\" p:d=\"\"/>\n</a>\n") ||
1903 docAsString == QString::fromLatin1("<a>\n<b p:c=\"\" p:d=\"\" xmlns:p=\"NS\"/>\n</a>\n") ||
1904 docAsString == QString::fromLatin1("<a>\n<b p:d=\"\" p:c=\"\" xmlns:p=\"NS\"/>\n</a>\n") ||
1905 docAsString == QString::fromLatin1("<a>\n<b p:d=\"\" xmlns:p=\"NS\" p:c=\"\"/>\n</a>\n") ||
1906 docAsString == QString::fromLatin1("<a>\n<b xmlns:p=\"NS\" p:c=\"\" p:d=\"\"/>\n</a>\n") ||
1907 docAsString == QString::fromLatin1("<a>\n<b xmlns:p=\"NS\" p:d=\"\" p:c=\"\"/>\n</a>\n")
1908 );
1909}
1910
1911void tst_QDom::setContentQXmlReaderOverload() const
1912{
1913 QDomDocument doc;
1914
1915#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
1916QT_WARNING_PUSH
1917QT_WARNING_DISABLE_DEPRECATED
1918 QXmlSimpleReader reader;
1919 QXmlInputSource data;
1920 data.setData(QByteArray("<e/>"));
1921 doc.setContent(source: &data, namespaceProcessing: true);
1922QT_WARNING_POP
1923#else
1924 QXmlStreamReader streamReader(QByteArray("<e/>"));
1925 doc.setContent(&streamReader, true);
1926#endif
1927 QCOMPARE(doc.documentElement().nodeName(), QString::fromLatin1("e"));
1928}
1929
1930void tst_QDom::cleanupTestCase() const
1931{
1932 QFile::remove(fileName: "germanUmlautToFile.xml");
1933}
1934
1935void tst_QDom::toStringWithoutNewlines() const
1936{
1937 QDomDocument doc;
1938 doc.setContent(text: QLatin1String("<doc><e/></doc>"));
1939
1940 QCOMPARE(doc.toString(0), QString::fromLatin1("<doc>\n<e/>\n</doc>\n"));
1941 QCOMPARE(doc.toString(-1), QString::fromLatin1("<doc><e/></doc>"));
1942}
1943
1944void tst_QDom::checkIntOverflow() const
1945{
1946 /* This test takes a *very* long time to run, so it is at best a manual
1947 * test. */
1948 return;
1949
1950 /* QDom used an internal global int which overflowed. So iterate until an
1951 * uint wrapsaround. */
1952 const QString xmlMessage(QLatin1String("<test/>"));
1953
1954 bool hasWrapped = false;
1955 for(uint i = 1; i != 0; ++i)
1956 {
1957 /* We want to exit the second time, not loop infinitely. */
1958 if(i == 1 && hasWrapped)
1959 break;
1960 else
1961 hasWrapped = true;
1962
1963 QDomDocument doc;
1964 QVERIFY(doc.setContent(xmlMessage));
1965
1966 const QDomNodeList nl(doc.elementsByTagName(tagname: QLatin1String("test")));
1967 QCOMPARE(nl.length(), 1);
1968 }
1969}
1970
1971void tst_QDom::setContentWhitespace() const
1972{
1973 QFETCH(QString, doc);
1974 QFETCH(bool, expectedValidity);
1975
1976 QDomDocument domDoc;
1977
1978 QCOMPARE(domDoc.setContent(doc), expectedValidity);
1979
1980 if(expectedValidity)
1981 QCOMPARE(domDoc.documentElement().nodeName(), QString::fromLatin1("e"));
1982}
1983
1984void tst_QDom::setContentWhitespace_data() const
1985{
1986 QTest::addColumn<QString>(name: "doc");
1987 QTest::addColumn<bool>(name: "expectedValidity");
1988
1989 QTest::newRow(dataTag: "data1") << QString::fromLatin1(str: " <e/>") << true;
1990 QTest::newRow(dataTag: "data2") << QString::fromLatin1(str: " <e/>") << true;
1991 QTest::newRow(dataTag: "data3") << QString::fromLatin1(str: " <e/>") << true;
1992 QTest::newRow(dataTag: "data4") << QString::fromLatin1(str: " <e/>") << true;
1993 QTest::newRow(dataTag: "data5") << QString::fromLatin1(str: "\n<e/>") << true;
1994 QTest::newRow(dataTag: "data6") << QString::fromLatin1(str: "\n\n<e/>") << true;
1995 QTest::newRow(dataTag: "data7") << QString::fromLatin1(str: "\n\n\n<e/>") << true;
1996 QTest::newRow(dataTag: "data8") << QString::fromLatin1(str: "\n\n\n\n<e/>") << true;
1997 QTest::newRow(dataTag: "data9") << QString::fromLatin1(str: "\t<e/>") << true;
1998 QTest::newRow(dataTag: "data10") << QString::fromLatin1(str: "\t\t<e/>") << true;
1999 QTest::newRow(dataTag: "data11") << QString::fromLatin1(str: "\t\t\t<e/>") << true;
2000 QTest::newRow(dataTag: "data12") << QString::fromLatin1(str: "\t\t\t\t<e/>") << true;
2001
2002 /* With XML prolog. */
2003 QTest::newRow(dataTag: "data13") << QString::fromLatin1(str: "<?xml version='1.0' ?><e/>") << true;
2004
2005 QTest::newRow(dataTag: "data14") << QString::fromLatin1(str: " <?xml version='1.0' ?><e/>") << false;
2006 QTest::newRow(dataTag: "data15") << QString::fromLatin1(str: " <?xml version='1.0' ?><e/>") << false;
2007 QTest::newRow(dataTag: "data16") << QString::fromLatin1(str: " <?xml version='1.0' ?><e/>") << false;
2008 QTest::newRow(dataTag: "data17") << QString::fromLatin1(str: " <?xml version='1.0' ?><e/>") << false;
2009 QTest::newRow(dataTag: "data18") << QString::fromLatin1(str: "\n<?xml version='1.0' ?><e/>") << false;
2010 QTest::newRow(dataTag: "data19") << QString::fromLatin1(str: "\n\n<?xml version='1.0' ?><e/>") << false;
2011 QTest::newRow(dataTag: "data20") << QString::fromLatin1(str: "\n\n\n<?xml version='1.0' ?><e/>") << false;
2012 QTest::newRow(dataTag: "data21") << QString::fromLatin1(str: "\n\n\n\n<?xml version='1.0' ?><e/>") << false;
2013 QTest::newRow(dataTag: "data22") << QString::fromLatin1(str: "\t<?xml version='1.0' ?><e/>") << false;
2014 QTest::newRow(dataTag: "data23") << QString::fromLatin1(str: "\t\t<?xml version='1.0' ?><e/>") << false;
2015 QTest::newRow(dataTag: "data24") << QString::fromLatin1(str: "\t\t\t<?xml version='1.0' ?><e/>") << false;
2016 QTest::newRow(dataTag: "data25") << QString::fromLatin1(str: "\t\t\t\t<?xml version='1.0' ?><e/>") << false;
2017}
2018
2019void tst_QDom::taskQTBUG4595_dontAssertWhenDocumentSpecifiesUnknownEncoding() const
2020{
2021 // QXmlStreamReader fails to read XML documents with unknown encoding. It
2022 // needs to be modified if we want to support this case with the QXmlStreamReader-based
2023 // implementation.
2024#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && QT_DEPRECATED_SINCE(5, 15)
2025 QString xmlWithUnknownEncoding("<?xml version='1.0' encoding='unknown-encoding'?>"
2026 "<foo>"
2027 " <bar>How will this sentence be handled?</bar>"
2028 "</foo>");
2029 QDomDocument d;
2030 QVERIFY(d.setContent(xmlWithUnknownEncoding));
2031
2032 QString dontAssert = d.toString(); // this should not assert
2033 QVERIFY(true);
2034#endif
2035}
2036
2037void tst_QDom::cloneDTD_QTBUG8398() const
2038{
2039 QString dtd("<?xml version='1.0' encoding='UTF-8'?>\n"
2040 "<!DOCTYPE first [\n"
2041 "<!ENTITY secondFile SYSTEM 'second.xml'>\n"
2042 "<!ENTITY thirdFile SYSTEM 'third.xml'>\n"
2043 "]>\n"
2044 "<first/>\n");
2045 QDomDocument domDocument;
2046 QVERIFY(domDocument.setContent(dtd));
2047 QDomDocument domDocument2 = domDocument.cloneNode(deep: true).toDocument();
2048
2049 // this string is relying on a specific QHash ordering, QTBUG-25071
2050 QString expected("<?xml version='1.0' encoding='UTF-8'?>\n"
2051 "<!DOCTYPE first [\n"
2052 "<!ENTITY thirdFile SYSTEM 'third.xml'>\n"
2053 "<!ENTITY secondFile SYSTEM 'second.xml'>\n"
2054 "]>\n"
2055 "<first/>\n");
2056 QString output;
2057 QTextStream stream(&output);
2058 domDocument2.save(stream, 0);
2059 // check against the original string and the expected one, QTBUG-25071
2060 QVERIFY(output == dtd || output == expected);
2061}
2062
2063void tst_QDom::DTDNotationDecl()
2064{
2065 QString dtd("<?xml version='1.0' encoding='UTF-8'?>\n"
2066 "<!DOCTYPE first [\n"
2067 "<!NOTATION gif SYSTEM 'image/gif'>\n"
2068 "<!NOTATION jpeg SYSTEM 'image/jpeg'>\n"
2069 "]>\n"
2070 "<first/>\n");
2071
2072 QDomDocument domDocument;
2073 QVERIFY(domDocument.setContent(dtd));
2074
2075 const QDomDocumentType doctype = domDocument.doctype();
2076 QCOMPARE(doctype.notations().size(), 2);
2077
2078 QVERIFY(doctype.namedItem(QString("gif")).isNotation());
2079 QCOMPARE(doctype.namedItem(QString("gif")).toNotation().systemId(), QString("image/gif"));
2080
2081 QVERIFY(doctype.namedItem(QString("jpeg")).isNotation());
2082 QCOMPARE(doctype.namedItem(QString("jpeg")).toNotation().systemId(), QString("image/jpeg"));
2083}
2084
2085void tst_QDom::DTDEntityDecl()
2086{
2087 QString dtd("<?xml version='1.0' encoding='UTF-8'?>\n"
2088 "<!DOCTYPE first [\n"
2089 "<!ENTITY secondFile SYSTEM 'second.xml'>\n"
2090 "<!ENTITY logo SYSTEM \"http://www.w3c.org/logo.gif\" NDATA gif>"
2091 "]>\n"
2092 "<first/>\n");
2093
2094 QDomDocument domDocument;
2095 QVERIFY(domDocument.setContent(dtd));
2096
2097 const QDomDocumentType doctype = domDocument.doctype();
2098 QCOMPARE(doctype.entities().count(), 2);
2099
2100 QVERIFY(doctype.namedItem(QString("secondFile")).isEntity());
2101 QCOMPARE(doctype.namedItem(QString("secondFile")).toEntity().systemId(), QString("second.xml"));
2102 QCOMPARE(doctype.namedItem(QString("secondFile")).toEntity().notationName(), QString());
2103
2104 QVERIFY(doctype.namedItem(QString("logo")).isEntity());
2105 QCOMPARE(doctype.namedItem(QString("logo")).toEntity().systemId(), QString("http://www.w3c.org/logo.gif"));
2106 QCOMPARE(doctype.namedItem(QString("logo")).toEntity().notationName(), QString("gif"));
2107}
2108
2109void tst_QDom::QTBUG49113_dontCrashWithNegativeIndex() const
2110{
2111 QDomDocument doc;
2112 QDomElement elem = doc.appendChild(newChild: doc.createElement(tagName: "root")).toElement();
2113 QDomNode node = elem.attributes().item(index: -1);
2114 QVERIFY(node.isNull());
2115}
2116
2117QTEST_MAIN(tst_QDom)
2118#include "tst_qdom.moc"
2119

source code of qtbase/tests/auto/xml/dom/qdom/tst_qdom.cpp