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 <QDirIterator>
31#include <QEventLoop>
32#include <QNetworkAccessManager>
33#include <QNetworkReply>
34#include <QNetworkRequest>
35#include <QtTest/QtTest>
36#include <QUrl>
37#include <QXmlDefaultHandler>
38#include <QXmlStreamReader>
39#include <QBuffer>
40#include <QStack>
41#include <QtGui/private/qzipreader_p.h>
42
43#include "qc14n.h"
44
45Q_DECLARE_METATYPE(QXmlStreamReader::ReadElementTextBehaviour)
46
47static const char *const catalogFile = "XML-Test-Suite/xmlconf/finalCatalog.xml";
48static const int expectedRunCount = 1646;
49static const int expectedSkipCount = 532;
50static const char *const xmlconfDir = "XML-Test-Suite/xmlconf/";
51static const char *const xmlDatasetName = "xmltest";
52static const char *const updateFilesDir = "xmltest_updates";
53static const char *const destinationFolder = "/valid/sa/out/";
54
55static inline int best(int a, int b)
56{
57 if (a < 0)
58 return b;
59 if (b < 0)
60 return a;
61 return qMin(a, b);
62}
63
64static inline int best(int a, int b, int c)
65{
66 if (a < 0)
67 return best(a: b, b: c);
68 if (b < 0)
69 return best(a, b: c);
70 if (c < 0)
71 return best(a, b);
72 return qMin(a: qMin(a, b), b: c);
73}
74
75template <typename C>
76const C sorted_by_name(C c) { // return by const value so we can feed directly into range-for loops below
77 using T = typename C::value_type;
78 auto byName = [](const T &lhs, const T &rhs) {
79 return lhs.name() < rhs.name();
80 };
81 std::sort(c.begin(), c.end(), byName);
82 return c;
83}
84
85/**
86 * Opens \a filename and returns content produced as per
87 * xmlconf/xmltest/canonxml.html.
88 *
89 * \a docType is the DOCTYPE name that the returned output should
90 * have, if it doesn't already have one.
91 */
92static QByteArray makeCanonical(const QString &filename,
93 const QString &docType,
94 bool &hasError,
95 bool testIncremental = false)
96{
97 QFile file(filename);
98 file.open(flags: QIODevice::ReadOnly);
99
100 QXmlStreamReader reader;
101
102 QByteArray buffer;
103 int bufferPos = 0;
104
105 if (testIncremental)
106 buffer = file.readAll();
107 else
108 reader.setDevice(&file);
109
110 QByteArray outarray;
111 QXmlStreamWriter writer(&outarray);
112
113 forever {
114 while (!reader.atEnd()) {
115 reader.readNext();
116 if (reader.isDTD()) {
117 const auto notationDeclarations = reader.notationDeclarations();
118 if (!notationDeclarations.isEmpty()) {
119 QString dtd;
120 QTextStream writeDtd(&dtd);
121
122 writeDtd << "<!DOCTYPE ";
123 writeDtd << docType;
124 writeDtd << " [";
125 writeDtd << Qt::endl;
126 for (const QXmlStreamNotationDeclaration &notation : sorted_by_name(c: notationDeclarations)) {
127 writeDtd << "<!NOTATION ";
128 writeDtd << notation.name().toString();
129 if (notation.publicId().isEmpty()) {
130 writeDtd << " SYSTEM \'";
131 writeDtd << notation.systemId().toString();
132 writeDtd << '\'';
133 } else {
134 writeDtd << " PUBLIC \'";
135 writeDtd << notation.publicId().toString();
136 writeDtd << "\'";
137 if (!notation.systemId().isEmpty() ) {
138 writeDtd << " \'";
139 writeDtd << notation.systemId().toString();
140 writeDtd << '\'';
141 }
142 }
143 writeDtd << '>';
144 writeDtd << Qt::endl;
145 }
146
147 writeDtd << "]>";
148 writeDtd << Qt::endl;
149 writer.writeDTD(dtd);
150 }
151 } else if (reader.isStartElement()) {
152 writer.writeStartElement(namespaceUri: reader.namespaceUri().toString(), name: reader.name().toString());
153 for (const QXmlStreamAttribute &attribute : sorted_by_name(c: reader.attributes()))
154 writer.writeAttribute(attribute);
155 writer.writeCharacters(text: QString()); // write empty string to avoid having empty xml tags
156 } else if (reader.isCharacters()) {
157 // make canonical
158
159 QString text = reader.text().toString();
160 int i = 0;
161 int p = 0;
162 while ((i = best(a: text.indexOf(c: QLatin1Char(10), from: p),
163 b: text.indexOf(c: QLatin1Char(13), from: p),
164 c: text.indexOf(c: QLatin1Char(9), from: p))) >= 0) {
165 writer.writeCharacters(text: text.mid(position: p, n: i - p));
166 writer.writeEntityReference(name: QLatin1Char('#') + QString::number(text.at(i).unicode()));
167 p = i + 1;
168 }
169 writer.writeCharacters(text: text.mid(position: p));
170 } else if (reader.isStartDocument() || reader.isEndDocument() || reader.isComment()){
171 // canonical does not want any of those
172 } else if (reader.isProcessingInstruction() && reader.processingInstructionData().isEmpty()) {
173 // for some reason canonical wants a space
174 writer.writeProcessingInstruction(target: reader.processingInstructionTarget().toString(), data: QLatin1String(""));
175 } else if (!reader.hasError()){
176 writer.writeCurrentToken(reader);
177 }
178 }
179 if (testIncremental && bufferPos < buffer.size()) {
180 reader.addData(data: QByteArray(buffer.data() + (bufferPos++), 1));
181 } else {
182 break;
183 }
184 }
185
186 if (reader.hasError()) {
187 hasError = true;
188 outarray += "ERROR:";
189 outarray += reader.errorString().toLatin1();
190 }
191 else
192 hasError = false;
193
194 return outarray;
195}
196
197/**
198 * \brief Returns the lexical QName of the document element in
199 * \a document.
200 *
201 * It is assumed that \a document is a well-formed XML document.
202 */
203static QString documentElement(const QByteArray &document)
204{
205 QXmlStreamReader reader(document);
206
207 while(!reader.atEnd())
208 {
209 if(reader.isStartElement())
210 return reader.qualifiedName().toString();
211
212 reader.readNext();
213 }
214
215 qFatal(msg: "The input %s didn't contain an element", document.constData());
216 return QString();
217}
218
219/**
220 * \brief Loads W3C's XML conformance test suite and runs it on QXmlStreamReader.
221 *
222 * Since this suite is fairly large, it runs the tests sequentially in order to not
223 * have them all loaded into memory at once. In this way, the maximum memory usage stays
224 * low, which means one can run valgrind on this test. However, the drawback is that
225 * Qt Test's usual error reporting and testing mechanisms are slightly bypassed.
226 *
227 * Part of this code is a manual, ad-hoc implementation of xml:base.
228 *
229 * See \l {http://www.w3.org/XML/Test/} {Extensible Markup Language (XML) Conformance Test Suites}
230 */
231class TestSuiteHandler
232{
233public:
234 /**
235 * The first string is the test ID, the second is
236 * a description of what went wrong.
237 */
238 typedef QPair<QString, QString> GeneralFailure;
239
240 /**
241 * The string is the test ID.
242 */
243 QStringList successes;
244
245 /**
246 * The first value is the baseline, while the se
247 */
248 class MissedBaseline
249 {
250 friend class QVector<MissedBaseline>;
251 MissedBaseline() {} // for QVector, don't use
252 public:
253 MissedBaseline(const QString &aId,
254 const QByteArray &aExpected,
255 const QByteArray &aOutput) : id(aId),
256 expected(aExpected),
257 output(aOutput)
258 {
259 if (aId.isEmpty())
260 qFatal(msg: "%s: aId must not be an empty string", Q_FUNC_INFO);
261 }
262
263 void swap(MissedBaseline &other) noexcept
264 {
265 qSwap(value1&: id, value2&: other.id);
266 qSwap(value1&: expected, value2&: other.expected);
267 qSwap(value1&: output, value2&: other.output);
268 }
269
270 QString id;
271 QByteArray expected;
272 QByteArray output;
273 };
274
275 QVector<GeneralFailure> failures;
276 QVector<MissedBaseline> missedBaselines;
277
278 /**
279 * The count of how many tests that were run.
280 */
281 int runCount;
282
283 int skipCount;
284
285 /**
286 * \a baseURI is the URI of where the catalog file resides.
287 */
288 TestSuiteHandler(const QUrl &baseURI) : runCount(0),
289 skipCount(0)
290 {
291 if (!baseURI.isValid())
292 qFatal(msg: "%s: baseURI must be valid", Q_FUNC_INFO);
293 m_baseURI.push(t: baseURI);
294 }
295
296 bool runTests(QFile *file)
297 {
298 QXmlStreamReader reader(file);
299 while (!reader.atEnd() && !reader.hasError()) {
300 reader.readNext();
301
302 if (reader.isStartElement() && !startElement(atts: reader.attributes()))
303 return false;
304
305 if (reader.isEndElement() && !endElement(localName: reader.name().toString()))
306 return false;
307 }
308 return !reader.hasError();
309 }
310
311 bool startElement(const QXmlStreamAttributes &atts)
312 {
313 m_atts.push(t: atts);
314
315 const auto attr = atts.value(qualifiedName: QLatin1String("xml:base"));
316 if (!attr.isEmpty())
317 m_baseURI.push(t: m_baseURI.top().resolved(relative: attr.toString()));
318
319 return true;
320 }
321
322 bool endElement(const QString &localName)
323 {
324 if(localName == QLatin1String("TEST"))
325 {
326 /* We don't want tests for XML 1.1.0, in fact). */
327 if(m_atts.top().value(namespaceUri: QString(), name: QLatin1String("VERSION")) == QLatin1String("1.1"))
328 {
329 ++skipCount;
330 m_atts.pop();
331 return true;
332 }
333
334 /* We don't want tests that conflict with the namespaces spec. Our parser is a
335 * namespace-aware parser. */
336 else if(m_atts.top().value(namespaceUri: QString(), name: QLatin1String("NAMESPACE")) == QLatin1String("no"))
337 {
338 ++skipCount;
339 m_atts.pop();
340 return true;
341 }
342
343 const QString inputFilePath(
344 m_baseURI.top()
345 .resolved(
346 relative: m_atts.top().value(namespaceUri: QString(), name: QLatin1String("URI")).toString())
347 .toLocalFile());
348 const QString id(m_atts.top().value(namespaceUri: QString(), name: QLatin1String("ID")).toString());
349 const QString type(m_atts.top().value(namespaceUri: QString(), name: QLatin1String("TYPE")).toString());
350
351 QString expectedFilePath;
352
353 const auto attr = m_atts.top().value(namespaceUri: QString(), name: QLatin1String("OUTPUT"));
354 if (!attr.isEmpty())
355 expectedFilePath = m_baseURI.top().resolved(relative: attr.toString()).toLocalFile();
356
357 /* testcases.dtd: 'No parser should accept a "not-wf" testcase
358 * unless it's a nonvalidating parser and the test contains
359 * external entities that the parser doesn't read.'
360 *
361 * We also let this apply to "valid", "invalid" and "error" tests, although
362 * I'm not fully sure this is correct. */
363 const QString ents(m_atts.top().value(namespaceUri: QString(), name: QLatin1String("ENTITIES")).toString());
364 m_atts.pop();
365
366 if(ents == QLatin1String("both") ||
367 ents == QLatin1String("general") ||
368 ents == QLatin1String("parameter"))
369 {
370 ++skipCount;
371 return true;
372 }
373
374 ++runCount;
375
376 QFile inputFile(inputFilePath);
377 if(!inputFile.open(flags: QIODevice::ReadOnly))
378 {
379 failures.append(t: qMakePair(x: id, y: QLatin1String("Failed to open input file ") + inputFilePath));
380 return true;
381 }
382
383 if(type == QLatin1String("not-wf"))
384 {
385 if(isWellformed(inputFile: &inputFile, mode: ParseSinglePass))
386 {
387 failures.append(t: qMakePair(x: id, y: QLatin1String("Failed to flag ") + inputFilePath
388 + QLatin1String(" as not well-formed.")));
389
390 /* Exit, the incremental test will fail as well, no need to flood the output. */
391 return true;
392 }
393 else
394 successes.append(t: id);
395
396 if(isWellformed(inputFile: &inputFile, mode: ParseIncrementally))
397 {
398 failures.append(t: qMakePair(x: id, y: QLatin1String("Failed to flag ") + inputFilePath
399 + QLatin1String(" as not well-formed with incremental parsing.")));
400 }
401 else
402 successes.append(t: id);
403
404 return true;
405 }
406
407 /* See testcases.dtd which reads: 'Nonvalidating parsers
408 * must also accept "invalid" testcases, but validating ones must reject them.' */
409 if(type == QLatin1String("invalid") || type == QLatin1String("valid"))
410 {
411 QByteArray expected;
412 QString docType;
413
414 /* We only want to compare against a baseline when we have
415 * one. Some "invalid"-tests, for instance, doesn't have baselines. */
416 if(!expectedFilePath.isEmpty())
417 {
418 QFile expectedFile(expectedFilePath);
419
420 if(!expectedFile.open(flags: QIODevice::ReadOnly))
421 {
422 failures.append(t: qMakePair(x: id, y: QLatin1String("Failed to open baseline ") + expectedFilePath));
423 return true;
424 }
425
426 expected = expectedFile.readAll();
427 docType = documentElement(document: expected);
428 }
429 else
430 docType = QLatin1String("dummy");
431
432 bool hasError = true;
433 bool incremental = false;
434
435 QByteArray input(makeCanonical(filename: inputFilePath, docType, hasError, testIncremental: incremental));
436
437 if (!hasError && !expectedFilePath.isEmpty() && input == expected)
438 input = makeCanonical(filename: inputFilePath, docType, hasError, testIncremental: (incremental = true));
439
440 if(hasError)
441 failures.append(t: qMakePair(x: id, y: QString::fromLatin1(str: "Failed to parse %1%2")
442 .arg(a: incremental?"(incremental run only) ":"")
443 .arg(a: inputFilePath)));
444
445 if(!expectedFilePath.isEmpty() && input != expected)
446 {
447 missedBaselines.append(t: MissedBaseline(id, expected, input));
448 return true;
449 }
450 else
451 {
452 successes.append(t: id);
453 return true;
454 }
455 }
456 else if(type == QLatin1String("error"))
457 {
458 /* Not yet sure about this one. */
459 // TODO
460 return true;
461 }
462 else
463 {
464 qFatal(msg: "The input catalog is invalid.");
465 return false;
466 }
467 } else if (localName == QLatin1String("TESTCASES")
468 && m_atts.top().hasAttribute(qualifiedName: QLatin1String("xml:base")))
469 m_baseURI.pop();
470
471 m_atts.pop();
472
473 return true;
474 }
475
476 enum ParseMode
477 {
478 ParseIncrementally,
479 ParseSinglePass
480 };
481
482 static bool isWellformed(QIODevice *const inputFile, const ParseMode mode)
483 {
484 if (!inputFile)
485 qFatal(msg: "%s: inputFile must be a valid QIODevice pointer", Q_FUNC_INFO);
486 if (!inputFile->isOpen())
487 qFatal(msg: "%s: inputFile must be opened by the caller", Q_FUNC_INFO);
488 if (mode != ParseIncrementally && mode != ParseSinglePass)
489 qFatal(msg: "%s: mode must be either ParseIncrementally or ParseSinglePass", Q_FUNC_INFO);
490
491 if(mode == ParseIncrementally)
492 {
493 QXmlStreamReader reader;
494 QByteArray buffer;
495 int bufferPos = 0;
496
497 buffer = inputFile->readAll();
498
499 while(true)
500 {
501 while(!reader.atEnd())
502 reader.readNext();
503
504 if(bufferPos < buffer.size())
505 {
506 ++bufferPos;
507 reader.addData(data: QByteArray(buffer.data() + bufferPos, 1));
508 }
509 else
510 break;
511 }
512
513 return !reader.hasError();
514 }
515 else
516 {
517 QXmlStreamReader reader;
518 reader.setDevice(inputFile);
519
520 while(!reader.atEnd())
521 reader.readNext();
522
523 return !reader.hasError();
524 }
525 }
526
527private:
528 QStack<QXmlStreamAttributes> m_atts;
529 QStack<QUrl> m_baseURI;
530};
531QT_BEGIN_NAMESPACE
532Q_DECLARE_SHARED(TestSuiteHandler::MissedBaseline)
533QT_END_NAMESPACE
534
535class tst_QXmlStream: public QObject
536{
537 Q_OBJECT
538public:
539 tst_QXmlStream() : m_handler(QUrl::fromLocalFile(QFINDTESTDATA(catalogFile)))
540 {
541 }
542
543private slots:
544 void initTestCase();
545 void cleanupTestCase();
546 void reportFailures() const;
547 void reportFailures_data();
548 void checkBaseline() const;
549 void checkBaseline_data() const;
550 void testReader() const;
551 void testReader_data() const;
552 void reportSuccess() const;
553 void reportSuccess_data() const;
554 void writerHangs() const;
555 void writerAutoFormattingWithComments() const;
556 void writerAutoFormattingWithTabs() const;
557 void writerAutoFormattingWithProcessingInstructions() const;
558 void writerAutoEmptyTags() const;
559 void writeAttributesWithSpace() const;
560 void addExtraNamespaceDeclarations();
561 void setEntityResolver();
562 void readFromQBuffer() const;
563 void readFromQBufferInvalid() const;
564 void readNextStartElement() const;
565 void readElementText() const;
566 void readElementText_data() const;
567 void crashInUTF16Codec() const;
568 void hasAttributeSignature() const;
569 void hasAttribute() const;
570 void writeWithCodec() const;
571 void writeWithUtf8Codec() const;
572 void writeWithUtf16Codec() const;
573 void writeWithStandalone() const;
574 void entitiesAndWhitespace_1() const;
575 void entitiesAndWhitespace_2() const;
576 void testFalsePrematureError() const;
577 void garbageInXMLPrologDefaultCodec() const;
578 void garbageInXMLPrologUTF8Explicitly() const;
579 void clear() const;
580 void checkCommentIndentation() const;
581 void checkCommentIndentation_data() const;
582 void crashInXmlStreamReader() const;
583 void write8bitCodec() const;
584 void invalidStringCharacters_data() const;
585 void invalidStringCharacters() const;
586 void hasError() const;
587 void readBack() const;
588 void roundTrip() const;
589 void roundTrip_data() const;
590
591 void entityExpansionLimit() const;
592
593private:
594 static QByteArray readFile(const QString &filename);
595
596 TestSuiteHandler m_handler;
597};
598
599void tst_QXmlStream::initTestCase()
600{
601#ifdef Q_OS_WINRT
602 QSKIP("Skipping the tests because we cannot unzip the data archive from WinRT app");
603#endif
604 // Due to license restrictions, we need to distribute part of the test
605 // suit as a zip archive. So we need to unzip it before running the tests,
606 // and also update some files there.
607 // We also need to remove the unzipped data during cleanup.
608 const QString filesDir(QFINDTESTDATA(xmlconfDir));
609 QZipReader reader(filesDir + xmlDatasetName + ".zip");
610 QVERIFY(reader.isReadable());
611 QVERIFY(reader.extractAll(filesDir));
612 // update files
613 const auto files =
614 QDir(filesDir + updateFilesDir).entryInfoList(filters: QDir::Files | QDir::NoDotAndDotDot);
615 for (const auto &fileInfo : files) {
616 const QString destinationPath =
617 filesDir + xmlDatasetName + destinationFolder + fileInfo.fileName();
618 QFile::remove(fileName: destinationPath); // copy will fail if file exists
619 QVERIFY(QFile::copy(fileInfo.filePath(), destinationPath));
620 }
621
622 QFile file(QFINDTESTDATA(catalogFile));
623 QVERIFY2(file.open(QIODevice::ReadOnly),
624 qPrintable(QString::fromLatin1("Failed to open the test suite catalog; %1").arg(file.fileName())));
625
626 QVERIFY(m_handler.runTests(&file));
627}
628
629void tst_QXmlStream::cleanupTestCase()
630{
631 QDir d(QFINDTESTDATA(xmlconfDir) + xmlDatasetName);
632 d.removeRecursively();
633 QFile::remove(fileName: QLatin1String("test.xml"));
634}
635
636void tst_QXmlStream::reportFailures() const
637{
638 QFETCH(bool, isError);
639 QFETCH(QString, description);
640
641 QVERIFY2(!isError, qPrintable(description));
642}
643
644void tst_QXmlStream::reportFailures_data()
645{
646 const int len = m_handler.failures.count();
647
648 QTest::addColumn<bool>(name: "isError");
649 QTest::addColumn<QString>(name: "description");
650
651 /* We loop over all our failures(if any!), and output them such
652 * that they appear in the Qt Test log. */
653 for(int i = 0; i < len; ++i)
654 QTest::newRow(dataTag: m_handler.failures.at(i).first.toLatin1().constData()) << true << m_handler.failures.at(i).second;
655
656 /* We need to add at least one column of test data, otherwise Qt Test complains. */
657 if(len == 0)
658 QTest::newRow(dataTag: "Whole test suite passed") << false << QString();
659
660 /* We compare the test case counts to ensure that we've actually run test cases, that
661 * the driver hasn't been broken or changed without updating the expected count, and
662 * similar reasons. */
663 QCOMPARE(m_handler.runCount, expectedRunCount);
664 QCOMPARE(m_handler.skipCount, expectedSkipCount);
665}
666
667void tst_QXmlStream::checkBaseline() const
668{
669 QFETCH(bool, isError);
670 QFETCH(QString, expected);
671 QFETCH(QString, output);
672
673 if(isError)
674 QCOMPARE(output, expected);
675}
676
677void tst_QXmlStream::checkBaseline_data() const
678{
679 QTest::addColumn<bool>(name: "isError");
680 QTest::addColumn<QString>(name: "expected");
681 QTest::addColumn<QString>(name: "output");
682
683 const int len = m_handler.missedBaselines.count();
684
685 for(int i = 0; i < len; ++i)
686 {
687 const TestSuiteHandler::MissedBaseline &b = m_handler.missedBaselines.at(i);
688
689 /* We indeed don't know what encoding the content is in so in some cases fromUtf8
690 * is all wrong, but it's an acceptable guess for error reporting. */
691 QTest::newRow(dataTag: b.id.toLatin1().constData())
692 << true
693 << QString::fromUtf8(str: b.expected.constData())
694 << QString::fromUtf8(str: b.output.constData());
695 }
696
697 if(len == 0)
698 QTest::newRow(dataTag: "dummy") << false << QString() << QString();
699}
700
701void tst_QXmlStream::reportSuccess() const
702{
703 QFETCH(bool, isError);
704
705 QVERIFY(!isError);
706}
707
708void tst_QXmlStream::reportSuccess_data() const
709{
710 QTest::addColumn<bool>(name: "isError");
711
712 const int len = m_handler.successes.count();
713
714 for (int i = 0; i < len; ++i) {
715 const QByteArray testName = QByteArray::number(i) + ". " + m_handler.successes.at(i).toLatin1();
716 QTest::newRow(dataTag: testName.constData()) << false;
717 }
718
719 if(len == 0)
720 QTest::newRow(dataTag: "No test cases succeeded.") << true;
721}
722
723QByteArray tst_QXmlStream::readFile(const QString &filename)
724{
725 QFile file(filename);
726 file.open(flags: QIODevice::ReadOnly);
727
728 QXmlStreamReader reader;
729
730 reader.setDevice(&file);
731 QByteArray outarray;
732 QTextStream writer(&outarray);
733 // We always want UTF-8, and not what the system picks up.
734 writer.setCodec("UTF-8");
735
736 while (!reader.atEnd()) {
737 reader.readNext();
738 writer << reader.tokenString() << '(';
739 if (reader.isWhitespace())
740 writer << " whitespace";
741 if (reader.isCDATA())
742 writer << " CDATA";
743 if (reader.isStartDocument() && reader.isStandaloneDocument())
744 writer << " standalone";
745 if (!reader.text().isEmpty())
746 writer << " text=\"" << reader.text().toString() << '"';
747 if (!reader.processingInstructionTarget().isEmpty())
748 writer << " processingInstructionTarget=\"" << reader.processingInstructionTarget().toString() << '"';
749 if (!reader.processingInstructionData().isEmpty())
750 writer << " processingInstructionData=\"" << reader.processingInstructionData().toString() << '"';
751 if (!reader.dtdName().isEmpty())
752 writer << " dtdName=\"" << reader.dtdName().toString() << '"';
753 if (!reader.dtdPublicId().isEmpty())
754 writer << " dtdPublicId=\"" << reader.dtdPublicId().toString() << '"';
755 if (!reader.dtdSystemId().isEmpty())
756 writer << " dtdSystemId=\"" << reader.dtdSystemId().toString() << '"';
757 if (!reader.documentVersion().isEmpty())
758 writer << " documentVersion=\"" << reader.documentVersion().toString() << '"';
759 if (!reader.documentEncoding().isEmpty())
760 writer << " documentEncoding=\"" << reader.documentEncoding().toString() << '"';
761 if (!reader.name().isEmpty())
762 writer << " name=\"" << reader.name().toString() << '"';
763 if (!reader.namespaceUri().isEmpty())
764 writer << " namespaceUri=\"" << reader.namespaceUri().toString() << '"';
765 if (!reader.qualifiedName().isEmpty())
766 writer << " qualifiedName=\"" << reader.qualifiedName().toString() << '"';
767 if (!reader.prefix().isEmpty())
768 writer << " prefix=\"" << reader.prefix().toString() << '"';
769 const auto attributes = reader.attributes();
770 if (attributes.size()) {
771 for (const QXmlStreamAttribute &attribute : attributes) {
772 writer << Qt::endl << " Attribute(";
773 if (!attribute.name().isEmpty())
774 writer << " name=\"" << attribute.name().toString() << '"';
775 if (!attribute.namespaceUri().isEmpty())
776 writer << " namespaceUri=\"" << attribute.namespaceUri().toString() << '"';
777 if (!attribute.qualifiedName().isEmpty())
778 writer << " qualifiedName=\"" << attribute.qualifiedName().toString() << '"';
779 if (!attribute.prefix().isEmpty())
780 writer << " prefix=\"" << attribute.prefix().toString() << '"';
781 if (!attribute.value().isEmpty())
782 writer << " value=\"" << attribute.value().toString() << '"';
783 writer << " )" << Qt::endl;
784 }
785 }
786 const auto namespaceDeclarations = reader.namespaceDeclarations();
787 if (namespaceDeclarations.size()) {
788 for (const QXmlStreamNamespaceDeclaration &namespaceDeclaration : namespaceDeclarations) {
789 writer << Qt::endl << " NamespaceDeclaration(";
790 if (!namespaceDeclaration.prefix().isEmpty())
791 writer << " prefix=\"" << namespaceDeclaration.prefix().toString() << '"';
792 if (!namespaceDeclaration.namespaceUri().isEmpty())
793 writer << " namespaceUri=\"" << namespaceDeclaration.namespaceUri().toString() << '"';
794 writer << " )" << Qt::endl;
795 }
796 }
797 const auto notationDeclarations = reader.notationDeclarations();
798 if (notationDeclarations.size()) {
799 for (const QXmlStreamNotationDeclaration &notationDeclaration : notationDeclarations) {
800 writer << Qt::endl << " NotationDeclaration(";
801 if (!notationDeclaration.name().isEmpty())
802 writer << " name=\"" << notationDeclaration.name().toString() << '"';
803 if (!notationDeclaration.systemId().isEmpty())
804 writer << " systemId=\"" << notationDeclaration.systemId().toString() << '"';
805 if (!notationDeclaration.publicId().isEmpty())
806 writer << " publicId=\"" << notationDeclaration.publicId().toString() << '"';
807 writer << " )" << Qt::endl;
808 }
809 }
810 const auto entityDeclarations = reader.entityDeclarations();
811 if (entityDeclarations.size()) {
812 for (const QXmlStreamEntityDeclaration &entityDeclaration : entityDeclarations) {
813 writer << Qt::endl << " EntityDeclaration(";
814 if (!entityDeclaration.name().isEmpty())
815 writer << " name=\"" << entityDeclaration.name().toString() << '"';
816 if (!entityDeclaration.notationName().isEmpty())
817 writer << " notationName=\"" << entityDeclaration.notationName().toString() << '"';
818 if (!entityDeclaration.systemId().isEmpty())
819 writer << " systemId=\"" << entityDeclaration.systemId().toString() << '"';
820 if (!entityDeclaration.publicId().isEmpty())
821 writer << " publicId=\"" << entityDeclaration.publicId().toString() << '"';
822 if (!entityDeclaration.value().isEmpty())
823 writer << " value=\"" << entityDeclaration.value().toString() << '"';
824 writer << " )" << Qt::endl;
825 }
826 }
827 writer << " )" << Qt::endl;
828 }
829 if (reader.hasError())
830 writer << "ERROR: " << reader.errorString() << Qt::endl;
831 return outarray;
832}
833
834void tst_QXmlStream::testReader() const
835{
836 QFETCH(QString, xml);
837 QFETCH(QString, ref);
838 QFile file(ref);
839 if (!file.exists()) {
840 QByteArray reference = readFile(filename: xml);
841 QVERIFY(file.open(QIODevice::WriteOnly));
842 file.write(data: reference);
843 file.close();
844 } else {
845 QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text));
846 QString reference = QString::fromUtf8(str: file.readAll());
847 QString qxmlstream = QString::fromUtf8(str: readFile(filename: xml));
848 QCOMPARE(qxmlstream, reference);
849 }
850}
851
852void tst_QXmlStream::testReader_data() const
853{
854 QTest::addColumn<QString>(name: "xml");
855 QTest::addColumn<QString>(name: "ref");
856 QDir dir;
857 dir.cd(QFINDTESTDATA("data/"));
858 const auto fileNames = dir.entryList(nameFilters: QStringList() << "*.xml");
859 for (const QString &filename : fileNames) {
860 QString reference = QFileInfo(filename).baseName() + ".ref";
861 QTest::newRow(dataTag: dir.filePath(fileName: filename).toLatin1().data()) << dir.filePath(fileName: filename) << dir.filePath(fileName: reference);
862 }
863}
864
865void tst_QXmlStream::addExtraNamespaceDeclarations()
866{
867 const char *data = "<bla><undeclared:foo/><undeclared_too:foo/></bla>";
868 {
869 QXmlStreamReader xml(data);
870 while (!xml.atEnd()) {
871 xml.readNext();
872 }
873 QVERIFY2(xml.hasError(), "namespaces undeclared");
874 }
875 {
876 QXmlStreamReader xml(data);
877 xml.addExtraNamespaceDeclaration(extraNamespaceDeclaraction: QXmlStreamNamespaceDeclaration("undeclared", "blabla"));
878 xml.addExtraNamespaceDeclaration(extraNamespaceDeclaraction: QXmlStreamNamespaceDeclaration("undeclared_too", "foofoo"));
879 while (!xml.atEnd()) {
880 xml.readNext();
881 }
882 QVERIFY2(!xml.hasError(), xml.errorString().toLatin1().constData());
883 }
884}
885
886
887class EntityResolver : public QXmlStreamEntityResolver {
888public:
889 QString resolveUndeclaredEntity(const QString &name) {
890 static int count = 0;
891 return name.toUpper() + QString::number(++count);
892 }
893};
894void tst_QXmlStream::setEntityResolver()
895{
896 const char *data = "<bla foo=\"&undeclared;\">&undeclared_too;</bla>";
897 {
898 QXmlStreamReader xml(data);
899 while (!xml.atEnd()) {
900 xml.readNext();
901 }
902 QVERIFY2(xml.hasError(), "undeclared entities");
903 }
904 {
905 QString foo;
906 QString bla_text;
907 QXmlStreamReader xml(data);
908 EntityResolver resolver;
909 xml.setEntityResolver(&resolver);
910 while (!xml.atEnd()) {
911 xml.readNext();
912 if (xml.isStartElement())
913 foo = xml.attributes().value(qualifiedName: "foo").toString();
914 if (xml.isCharacters())
915 bla_text += xml.text().toString();
916 }
917 QVERIFY2(!xml.hasError(), xml.errorString().toLatin1().constData());
918 QCOMPARE(foo, QLatin1String("UNDECLARED1"));
919 QCOMPARE(bla_text, QLatin1String("UNDECLARED_TOO2"));
920 }
921}
922
923void tst_QXmlStream::testFalsePrematureError() const
924{
925 const char *illegal_start = "illegal<sta";
926 const char *legal_start = "<sta";
927 const char* end = "rt/>";
928 {
929 QXmlStreamReader xml("");
930 while (!xml.atEnd()) {
931 xml.readNext();
932 }
933 QCOMPARE(xml.error(), QXmlStreamReader::PrematureEndOfDocumentError);
934 QCOMPARE(xml.errorString(), QLatin1String("Premature end of document."));
935 xml.addData(data: legal_start);
936 while (!xml.atEnd()) {
937 xml.readNext();
938 }
939 QCOMPARE(xml.error(), QXmlStreamReader::PrematureEndOfDocumentError);
940 QCOMPARE(xml.errorString(), QLatin1String("Premature end of document."));
941 xml.addData(data: end);
942 while (!xml.atEnd()) {
943 xml.readNext();
944 }
945 QVERIFY(!xml.hasError());
946 }
947 {
948 QXmlStreamReader xml(illegal_start);
949 while (!xml.atEnd()) {
950 xml.readNext();
951 }
952 QVERIFY(xml.hasError());
953 QCOMPARE(xml.errorString(), QLatin1String("Start tag expected."));
954 QCOMPARE(xml.error(), QXmlStreamReader::NotWellFormedError);
955 }
956}
957
958// Regression test for crash due to using empty QStack.
959void tst_QXmlStream::writerHangs() const
960{
961 QTemporaryDir dir(QDir::tempPath() + QLatin1String("/tst_qxmlstream.XXXXXX"));
962 QFile file(dir.path() + "/test.xml");
963
964 QVERIFY(file.open(QIODevice::WriteOnly));
965
966 QXmlStreamWriter writer(&file);
967 double radius = 4.0;
968 writer.setAutoFormatting(true);
969 writer.writeStartDocument();
970 writer.writeEmptyElement(qualifiedName: "circle");
971 writer.writeAttribute(qualifiedName: "radius", value: QString::number(radius));
972 writer.writeEndElement();
973 writer.writeEndDocument();
974}
975
976void tst_QXmlStream::writerAutoFormattingWithComments() const
977{
978 QBuffer buffer;
979 buffer.open(openMode: QIODevice::WriteOnly);
980
981 QXmlStreamWriter writer(&buffer);
982 writer.setAutoFormatting(true);
983 writer.writeStartDocument();
984 writer.writeComment(text: "This is a comment");
985 writer.writeEndDocument();
986 const char *str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--This is a comment-->\n";
987 QCOMPARE(buffer.buffer().data(), str);
988}
989
990void tst_QXmlStream::writerAutoFormattingWithTabs() const
991{
992 QBuffer buffer;
993 buffer.open(openMode: QIODevice::WriteOnly);
994
995
996 QXmlStreamWriter writer(&buffer);
997 writer.setAutoFormatting(true);
998 writer.setAutoFormattingIndent(-1);
999 QCOMPARE(writer.autoFormattingIndent(), -1);
1000 writer.writeStartDocument();
1001 writer.writeStartElement(qualifiedName: "A");
1002 writer.writeStartElement(qualifiedName: "B");
1003 writer.writeEndDocument();
1004 const char *str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<A>\n\t<B/>\n</A>\n";
1005 QCOMPARE(buffer.buffer().data(), str);
1006}
1007
1008void tst_QXmlStream::writerAutoFormattingWithProcessingInstructions() const
1009{
1010 QBuffer buffer;
1011 buffer.open(openMode: QIODevice::WriteOnly);
1012
1013 QXmlStreamWriter writer(&buffer);
1014 writer.setAutoFormatting(true);
1015 writer.writeStartDocument();
1016 writer.writeProcessingInstruction(target: "B", data: "C");
1017 writer.writeStartElement(qualifiedName: "A");
1018 writer.writeEndElement();
1019 writer.writeEndDocument();
1020 const char *str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?B C?>\n<A/>\n";
1021 QCOMPARE(buffer.buffer().data(), str);
1022}
1023
1024void tst_QXmlStream::writeAttributesWithSpace() const
1025{
1026 QBuffer buffer;
1027 buffer.open(openMode: QIODevice::WriteOnly);
1028
1029
1030 QXmlStreamWriter writer(&buffer);
1031 writer.writeStartDocument();
1032 writer.writeEmptyElement(qualifiedName: "A");
1033 writer.writeAttribute(qualifiedName: "attribute", QStringLiteral("value") + QChar(QChar::Nbsp));
1034 writer.writeEndDocument();
1035 QString s = QLatin1String("<?xml version=\"1.0\" encoding=\"UTF-8\"?><A attribute=\"value")
1036 + QChar(QChar::Nbsp) + QLatin1String("\"/>\n");
1037 QCOMPARE(buffer.buffer().data(), s.toUtf8().data());
1038}
1039
1040void tst_QXmlStream::writerAutoEmptyTags() const
1041{
1042 QBuffer buffer;
1043 buffer.open(openMode: QIODevice::WriteOnly);
1044
1045
1046 QXmlStreamWriter writer(&buffer);
1047
1048 writer.writeStartDocument();
1049
1050 writer.writeStartElement(qualifiedName: "Hans");
1051 writer.writeAttribute(qualifiedName: "key", value: "value");
1052 writer.writeEndElement();
1053
1054 writer.writeStartElement(qualifiedName: "Hans");
1055 writer.writeAttribute(qualifiedName: "key", value: "value");
1056 writer.writeEmptyElement(qualifiedName: "Leer");
1057 writer.writeAttribute(qualifiedName: "key", value: "value");
1058 writer.writeEndElement();
1059
1060 writer.writeStartElement(qualifiedName: "Hans");
1061 writer.writeAttribute(qualifiedName: "key", value: "value");
1062 writer.writeCharacters(text: "stuff");
1063 writer.writeEndElement();
1064
1065 writer.writeEndDocument();
1066
1067 QString s = QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Hans key=\"value\"/><Hans key=\"value\"><Leer key=\"value\"/></Hans><Hans key=\"value\">stuff</Hans>\n");
1068 QCOMPARE(buffer.buffer().data(), s.toUtf8().data());
1069}
1070
1071void tst_QXmlStream::readFromQBuffer() const
1072{
1073 QByteArray in("<e/>");
1074 QBuffer buffer(&in);
1075 QVERIFY(buffer.open(QIODevice::ReadOnly));
1076
1077 QXmlStreamReader reader(&buffer);
1078
1079 while(!reader.atEnd())
1080 {
1081 reader.readNext();
1082 }
1083
1084 QVERIFY(!reader.hasError());
1085}
1086
1087void tst_QXmlStream::readFromQBufferInvalid() const
1088{
1089 QByteArray in("<e/><e/>");
1090 QBuffer buffer(&in);
1091 QVERIFY(buffer.open(QIODevice::ReadOnly));
1092
1093 QXmlStreamReader reader(&buffer);
1094
1095 while(!reader.atEnd())
1096 {
1097 reader.readNext();
1098 }
1099
1100 QVERIFY(reader.hasError());
1101}
1102
1103void tst_QXmlStream::readNextStartElement() const
1104{
1105 QLatin1String in("<?xml version=\"1.0\"?><A><!-- blah --><B><C/></B><B attr=\"value\"/>text</A>");
1106 QXmlStreamReader reader(in);
1107
1108 QVERIFY(reader.readNextStartElement());
1109 QVERIFY(reader.isStartElement() && reader.name() == QLatin1String("A"));
1110
1111 int amountOfB = 0;
1112 while (reader.readNextStartElement()) {
1113 QVERIFY(reader.isStartElement() && reader.name() == QLatin1String("B"));
1114 ++amountOfB;
1115 reader.skipCurrentElement();
1116 }
1117
1118 QCOMPARE(amountOfB, 2);
1119}
1120
1121void tst_QXmlStream::readElementText() const
1122{
1123 QFETCH(QXmlStreamReader::ReadElementTextBehaviour, behaviour);
1124 QFETCH(QString, input);
1125 QFETCH(QString, expected);
1126
1127 QXmlStreamReader reader(input);
1128
1129 QVERIFY(reader.readNextStartElement());
1130 QCOMPARE(reader.readElementText(behaviour), expected);
1131}
1132
1133void tst_QXmlStream::readElementText_data() const
1134{
1135 QTest::addColumn<QXmlStreamReader::ReadElementTextBehaviour>(name: "behaviour");
1136 QTest::addColumn<QString>(name: "input");
1137 QTest::addColumn<QString>(name: "expected");
1138
1139 QString validInput("<p>He was <em>never</em> going to admit<!-- TODO: rephrase --> his mistake.</p>");
1140 QString invalidInput("<p>invalid...<p>");
1141 QString invalidOutput("invalid...");
1142
1143 QTest::newRow(dataTag: "ErrorOnUnexpectedElement")
1144 << QXmlStreamReader::ErrorOnUnexpectedElement
1145 << validInput << QString("He was ");
1146
1147 QTest::newRow(dataTag: "IncludeChildElements")
1148 << QXmlStreamReader::IncludeChildElements
1149 << validInput << QString("He was never going to admit his mistake.");
1150
1151 QTest::newRow(dataTag: "SkipChildElements")
1152 << QXmlStreamReader::SkipChildElements
1153 << validInput << QString("He was going to admit his mistake.");
1154
1155 QTest::newRow(dataTag: "ErrorOnUnexpectedElement Invalid")
1156 << QXmlStreamReader::ErrorOnUnexpectedElement
1157 << invalidInput << invalidOutput;
1158
1159 QTest::newRow(dataTag: "IncludeChildElements Invalid")
1160 << QXmlStreamReader::IncludeChildElements
1161 << invalidInput << invalidOutput;
1162
1163 QTest::newRow(dataTag: "SkipChildElements Invalid")
1164 << QXmlStreamReader::SkipChildElements
1165 << invalidInput << invalidOutput;
1166}
1167
1168void tst_QXmlStream::crashInUTF16Codec() const
1169{
1170 QEventLoop eventLoop;
1171
1172 QNetworkAccessManager networkManager;
1173 QNetworkRequest request(QUrl::fromLocalFile(QFINDTESTDATA("data/051reduced.xml")));
1174 QNetworkReply *const reply = networkManager.get(request);
1175 eventLoop.connect(asender: reply, SIGNAL(finished()), SLOT(quit()));
1176
1177 QCOMPARE(eventLoop.exec(), 0);
1178
1179 QXmlStreamReader reader(reply);
1180 while(!reader.atEnd())
1181 {
1182 reader.readNext();
1183 continue;
1184 }
1185
1186 QVERIFY(!reader.hasError());
1187}
1188
1189/*
1190 In addition to Qt Test's flags, one can specify "-c <filename>" and have that file output in its canonical form.
1191*/
1192int main(int argc, char *argv[])
1193{
1194 QCoreApplication app(argc, argv);
1195
1196 if (argc == 3 && QByteArray(argv[1]).startsWith(c: "-c")) {
1197 // output canonical only
1198 bool error = false;
1199 QByteArray canonical = makeCanonical(filename: argv[2], docType: "doc", hasError&: error);
1200 QTextStream myStdOut(stdout);
1201 myStdOut << canonical << Qt::endl;
1202 exit(status: 0);
1203 }
1204
1205 tst_QXmlStream tc;
1206 return QTest::qExec(testObject: &tc, argc, argv);
1207}
1208
1209void tst_QXmlStream::hasAttributeSignature() const
1210{
1211 /* These functions should be const so invoke all
1212 * of them on a const object. */
1213 const QXmlStreamAttributes atts;
1214 atts.hasAttribute(qualifiedName: QLatin1String("localName"));
1215 atts.hasAttribute(qualifiedName: QString::fromLatin1(str: "localName"));
1216 atts.hasAttribute(namespaceUri: QString::fromLatin1(str: "http://example.com/"), name: QLatin1String("localName"));
1217
1218 /* The input arguments should be const references, not mutable references
1219 * so pass const references. */
1220 const QLatin1String latin1StringLocalName(QLatin1String("localName"));
1221 const QString qStringLocalname(QLatin1String("localName"));
1222 const QString namespaceURI(QLatin1String("http://example.com/"));
1223
1224 /* QLatin1String overload. */
1225 atts.hasAttribute(qualifiedName: latin1StringLocalName);
1226
1227 /* QString overload. */
1228 atts.hasAttribute(qualifiedName: latin1StringLocalName);
1229
1230 /* namespace/local name overload. */
1231 atts.hasAttribute(namespaceUri: namespaceURI, name: qStringLocalname);
1232}
1233
1234void tst_QXmlStream::hasAttribute() const
1235{
1236 QXmlStreamReader reader(QLatin1String("<e xmlns:p='http://example.com/2' xmlns='http://example.com/' "
1237 "attr1='value' attr2='value2' p:attr3='value3' emptyAttr=''><noAttributes/></e>"));
1238
1239 QCOMPARE(reader.readNext(), QXmlStreamReader::StartDocument);
1240 QCOMPARE(reader.readNext(), QXmlStreamReader::StartElement);
1241 const QXmlStreamAttributes &atts = reader.attributes();
1242
1243 /* QLatin1String overload. */
1244 QVERIFY(atts.hasAttribute(QLatin1String("attr1")));
1245 QVERIFY(atts.hasAttribute(QLatin1String("attr2")));
1246 QVERIFY(atts.hasAttribute(QLatin1String("p:attr3")));
1247 QVERIFY(atts.hasAttribute(QLatin1String("emptyAttr")));
1248 QVERIFY(!atts.hasAttribute(QLatin1String("DOESNOTEXIST")));
1249
1250 /* Test with an empty & null namespaces. */
1251 QVERIFY(atts.hasAttribute(QString(), QLatin1String("attr2"))); /* A null string. */
1252 QVERIFY(atts.hasAttribute(QLatin1String(""), QLatin1String("attr2"))); /* An empty string. */
1253
1254 /* QString overload. */
1255 QVERIFY(atts.hasAttribute(QString::fromLatin1("attr1")));
1256 QVERIFY(atts.hasAttribute(QString::fromLatin1("attr2")));
1257 QVERIFY(atts.hasAttribute(QString::fromLatin1("p:attr3")));
1258 QVERIFY(atts.hasAttribute(QString::fromLatin1("emptyAttr")));
1259 QVERIFY(!atts.hasAttribute(QString::fromLatin1("DOESNOTEXIST")));
1260
1261 /* namespace/local name overload. */
1262 QVERIFY(atts.hasAttribute(QString(), QString::fromLatin1("attr1")));
1263 /* Attributes do not pick up the default namespace. */
1264 QVERIFY(!atts.hasAttribute(QLatin1String("http://example.com/"), QString::fromLatin1("attr1")));
1265 QVERIFY(atts.hasAttribute(QLatin1String("http://example.com/2"), QString::fromLatin1("attr3")));
1266 QVERIFY(atts.hasAttribute(QString(), QString::fromLatin1("emptyAttr")));
1267 QVERIFY(!atts.hasAttribute(QLatin1String("http://example.com/2"), QString::fromLatin1("DOESNOTEXIST")));
1268 QVERIFY(!atts.hasAttribute(QLatin1String("WRONG_NAMESPACE"), QString::fromLatin1("attr3")));
1269
1270 /* Invoke on an QXmlStreamAttributes that has no attributes at all. */
1271 QCOMPARE(reader.readNext(), QXmlStreamReader::StartElement);
1272
1273 const QXmlStreamAttributes &atts2 = reader.attributes();
1274 QVERIFY(atts2.isEmpty());
1275
1276 /* QLatin1String overload. */
1277 QVERIFY(!atts.hasAttribute(QLatin1String("arbitraryName")));
1278
1279 /* QString overload. */
1280 QVERIFY(!atts.hasAttribute(QString::fromLatin1("arbitraryName")));
1281
1282 /* namespace/local name overload. */
1283 QVERIFY(!atts.hasAttribute(QLatin1String("http://example.com/"), QString::fromLatin1("arbitraryName")));
1284
1285 while(!reader.atEnd())
1286 reader.readNext();
1287
1288 QVERIFY(!reader.hasError());
1289}
1290
1291
1292void tst_QXmlStream::writeWithCodec() const
1293{
1294 QByteArray outarray;
1295 QXmlStreamWriter writer(&outarray);
1296 writer.setAutoFormatting(true);
1297
1298 QTextCodec *codec = QTextCodec::codecForName(name: "ISO 8859-15");
1299 QVERIFY(codec);
1300 writer.setCodec(codec);
1301
1302 const char *latin2 = "h\xe9 h\xe9";
1303 const QString string = codec->toUnicode(chars: latin2);
1304
1305
1306 writer.writeStartDocument(version: "1.0");
1307
1308 writer.writeTextElement(qualifiedName: "foo", text: string);
1309 writer.writeEndElement();
1310 writer.writeEndDocument();
1311
1312 QVERIFY(outarray.contains(latin2));
1313 QVERIFY(outarray.contains(codec->name()));
1314}
1315
1316void tst_QXmlStream::writeWithUtf8Codec() const
1317{
1318 QByteArray outarray;
1319 QXmlStreamWriter writer(&outarray);
1320
1321 QTextCodec *codec = QTextCodec::codecForMib(mib: 106); // utf-8
1322 QVERIFY(codec);
1323 writer.setCodec(codec);
1324
1325 writer.writeStartDocument(version: "1.0");
1326 static const char begin[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
1327 QVERIFY(outarray.startsWith(begin));
1328}
1329
1330void tst_QXmlStream::writeWithUtf16Codec() const
1331{
1332 QByteArray outarray;
1333 QXmlStreamWriter writer(&outarray);
1334
1335 QTextCodec *codec = QTextCodec::codecForMib(mib: 1014); // utf-16LE
1336 QVERIFY(codec);
1337 writer.setCodec(codec);
1338
1339 writer.writeStartDocument(version: "1.0");
1340 static const char begin[] = "<?xml version=\"1.0\" encoding=\"UTF-16"; // skip potential "LE" suffix
1341 const int count = sizeof(begin) - 1; // don't include 0 terminator
1342 QByteArray begin_UTF16;
1343 begin_UTF16.reserve(asize: 2*(count));
1344 for (int i = 0; i < count; ++i) {
1345 begin_UTF16.append(c: begin[i]);
1346 begin_UTF16.append(c: (char)'\0');
1347 }
1348 QVERIFY(outarray.startsWith(begin_UTF16));
1349}
1350
1351void tst_QXmlStream::writeWithStandalone() const
1352{
1353 {
1354 QByteArray outarray;
1355 QXmlStreamWriter writer(&outarray);
1356 writer.setAutoFormatting(true);
1357 writer.writeStartDocument(version: "1.0", standalone: true);
1358 writer.writeEndDocument();
1359 const char *ref = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
1360 QCOMPARE(outarray.constData(), ref);
1361 }
1362 {
1363 QByteArray outarray;
1364 QXmlStreamWriter writer(&outarray);
1365 writer.setAutoFormatting(true);
1366 writer.writeStartDocument(version: "1.0", standalone: false);
1367 writer.writeEndDocument();
1368 const char *ref = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
1369 QCOMPARE(outarray.constData(), ref);
1370 }
1371}
1372
1373void tst_QXmlStream::entitiesAndWhitespace_1() const
1374{
1375 QXmlStreamReader reader(QLatin1String("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\"><test>&extEnt;</test>"));
1376
1377 int entityCount = 0;
1378 int characterCount = 0;
1379 while(!reader.atEnd())
1380 {
1381 QXmlStreamReader::TokenType token = reader.readNext();
1382 switch(token)
1383 {
1384 case QXmlStreamReader::Characters:
1385 characterCount++;
1386 break;
1387 case QXmlStreamReader::EntityReference:
1388 entityCount++;
1389 break;
1390 default:
1391 ;
1392 }
1393 }
1394
1395 QCOMPARE(entityCount, 1);
1396 QCOMPARE(characterCount, 0);
1397 QVERIFY(!reader.hasError());
1398}
1399
1400void tst_QXmlStream::entitiesAndWhitespace_2() const
1401{
1402 QXmlStreamReader reader(QLatin1String("<test>&extEnt;</test>"));
1403
1404 int entityCount = 0;
1405 int characterCount = 0;
1406 while(!reader.atEnd())
1407 {
1408 QXmlStreamReader::TokenType token = reader.readNext();
1409 switch(token)
1410 {
1411 case QXmlStreamReader::Characters:
1412 characterCount++;
1413 break;
1414 case QXmlStreamReader::EntityReference:
1415 entityCount++;
1416 break;
1417 default:
1418 ;
1419 }
1420 }
1421
1422 QCOMPARE(entityCount, 0);
1423 QCOMPARE(characterCount, 0);
1424 QVERIFY(reader.hasError());
1425}
1426
1427void tst_QXmlStream::garbageInXMLPrologDefaultCodec() const
1428{
1429 QBuffer out;
1430 QVERIFY(out.open(QIODevice::ReadWrite));
1431
1432 QXmlStreamWriter writer (&out);
1433 writer.writeStartDocument();
1434 writer.writeEmptyElement(qualifiedName: "Foo");
1435 writer.writeEndDocument();
1436
1437 QCOMPARE(out.data(), QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Foo/>\n"));
1438}
1439
1440void tst_QXmlStream::garbageInXMLPrologUTF8Explicitly() const
1441{
1442 QBuffer out;
1443 QVERIFY(out.open(QIODevice::ReadWrite));
1444
1445 QXmlStreamWriter writer (&out);
1446 writer.setCodec("UTF-8");
1447 writer.writeStartDocument();
1448 writer.writeEmptyElement(qualifiedName: "Foo");
1449 writer.writeEndDocument();
1450
1451 QCOMPARE(out.data(), QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Foo/>\n"));
1452}
1453
1454void tst_QXmlStream::clear() const
1455{
1456 QString xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><body></body>";
1457 QXmlStreamReader reader;
1458
1459 reader.addData(data: xml);
1460 while (!reader.atEnd()) {
1461 reader.readNext();
1462 }
1463 QCOMPARE(reader.tokenType(), QXmlStreamReader::EndDocument);
1464
1465 reader.clear();
1466 reader.addData(data: xml);
1467 while (!reader.atEnd()) {
1468 reader.readNext();
1469 }
1470 QCOMPARE(reader.tokenType(), QXmlStreamReader::EndDocument);
1471
1472
1473 // now we stop in the middle to check whether clear really works
1474 reader.clear();
1475 reader.addData(data: xml);
1476 reader.readNext();
1477 reader.readNext();
1478 QCOMPARE(reader.tokenType(), QXmlStreamReader::StartElement);
1479
1480 // and here the final read
1481 reader.clear();
1482 reader.addData(data: xml);
1483 while (!reader.atEnd()) {
1484 reader.readNext();
1485 }
1486 QCOMPARE(reader.tokenType(), QXmlStreamReader::EndDocument);
1487}
1488
1489void tst_QXmlStream::checkCommentIndentation_data() const
1490{
1491
1492 QTest::addColumn<QString>(name: "input");
1493 QTest::addColumn<QString>(name: "expectedOutput");
1494
1495 QString simpleInput = "<a><!-- bla --></a>";
1496 QString simpleOutput = "<?xml version=\"1.0\"?>\n"
1497 "<a>\n"
1498 " <!-- bla -->\n"
1499 "</a>\n";
1500 QTest::newRow(dataTag: "simple-comment") << simpleInput << simpleOutput;
1501
1502 QString advancedInput = "<a><!-- bla --><!-- bla --><b><!-- bla --><c><!-- bla --></c><!-- bla --></b></a>";
1503 QString advancedOutput = "<?xml version=\"1.0\"?>\n"
1504 "<a>\n"
1505 " <!-- bla -->\n"
1506 " <!-- bla -->\n"
1507 " <b>\n"
1508 " <!-- bla -->\n"
1509 " <c>\n"
1510 " <!-- bla -->\n"
1511 " </c>\n"
1512 " <!-- bla -->\n"
1513 " </b>\n"
1514 "</a>\n";
1515 QTest::newRow(dataTag: "advanced-comment") << advancedInput << advancedOutput;
1516}
1517
1518void tst_QXmlStream::checkCommentIndentation() const
1519{
1520 QFETCH(QString, input);
1521 QFETCH(QString, expectedOutput);
1522 QString output;
1523 QXmlStreamReader reader(input);
1524 QXmlStreamWriter writer(&output);
1525 writer.setAutoFormatting(true);
1526 writer.setAutoFormattingIndent(3);
1527
1528 while (!reader.atEnd()) {
1529 reader.readNext();
1530 if (reader.error()) {
1531 QFAIL("error reading XML input");
1532 } else {
1533 writer.writeCurrentToken(reader);
1534 }
1535 }
1536 QCOMPARE(output, expectedOutput);
1537}
1538
1539// This is a regression test for QTBUG-9196, where the series of tags used
1540// in the test caused a crash in the XML stream reader.
1541void tst_QXmlStream::crashInXmlStreamReader() const
1542{
1543 QByteArray ba("<a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a>"
1544 "<a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a><a></a>");
1545 QXmlStreamReader xml(ba);
1546 while (!xml.atEnd()) {
1547 xml.readNext();
1548 }
1549}
1550
1551class FakeBuffer : public QBuffer
1552{
1553protected:
1554 qint64 writeData(const char *c, qint64 i)
1555 {
1556 qint64 ai = qMin(a: m_capacity, b: i);
1557 m_capacity -= ai;
1558 return ai ? QBuffer::writeData(data: c, len: ai) : 0;
1559 }
1560public:
1561 void setCapacity(int capacity) { m_capacity = capacity; }
1562private:
1563 qint64 m_capacity;
1564};
1565
1566void tst_QXmlStream::hasError() const
1567{
1568 {
1569 FakeBuffer fb;
1570 QVERIFY(fb.open(QBuffer::ReadWrite));
1571 fb.setCapacity(1000);
1572 QXmlStreamWriter writer(&fb);
1573 writer.writeStartDocument();
1574 writer.writeEndDocument();
1575 QVERIFY(!writer.hasError());
1576 QCOMPARE(fb.data(), QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"));
1577 }
1578
1579 {
1580 // Failure caused by write(QString)
1581 FakeBuffer fb;
1582 QVERIFY(fb.open(QBuffer::ReadWrite));
1583 const QByteArray expected = QByteArrayLiteral("<?xml version=\"");
1584 fb.setCapacity(expected.size());
1585 QXmlStreamWriter writer(&fb);
1586 writer.writeStartDocument();
1587 QVERIFY(writer.hasError());
1588 QCOMPARE(fb.data(), expected);
1589 }
1590
1591 {
1592 // Failure caused by write(char *)
1593 FakeBuffer fb;
1594 QVERIFY(fb.open(QBuffer::ReadWrite));
1595 const QByteArray expected = QByteArrayLiteral("<?xml version=\"1.0");
1596 fb.setCapacity(expected.size());
1597 QXmlStreamWriter writer(&fb);
1598 writer.writeStartDocument();
1599 QVERIFY(writer.hasError());
1600 QCOMPARE(fb.data(), expected);
1601 }
1602
1603 {
1604 // Failure caused by write(QStringRef)
1605 FakeBuffer fb;
1606 QVERIFY(fb.open(QBuffer::ReadWrite));
1607 const QByteArray expected = QByteArrayLiteral("<?xml version=\"1.0\" encoding=\"UTF-8\"?><test xmlns:");
1608 fb.setCapacity(expected.size());
1609 QXmlStreamWriter writer(&fb);
1610 writer.writeStartDocument();
1611 writer.writeStartElement(qualifiedName: "test");
1612 writer.writeNamespace(namespaceUri: "http://foo.bar", prefix: "foo");
1613 QVERIFY(writer.hasError());
1614 QCOMPARE(fb.data(), expected);
1615 }
1616
1617 {
1618 // Refusal to write after 1st failure
1619 FakeBuffer fb;
1620 QVERIFY(fb.open(QBuffer::ReadWrite));
1621 fb.setCapacity(10);
1622 QXmlStreamWriter writer(&fb);
1623 writer.writeStartDocument();
1624 QVERIFY(writer.hasError());
1625 QCOMPARE(fb.data(), QByteArray("<?xml vers"));
1626 fb.setCapacity(1000);
1627 writer.writeStartElement(qualifiedName: "test"); // literal & qstring
1628 writer.writeNamespace(namespaceUri: "http://foo.bar", prefix: "foo"); // literal & qstringref
1629 QVERIFY(writer.hasError());
1630 QCOMPARE(fb.data(), QByteArray("<?xml vers"));
1631 }
1632
1633}
1634
1635void tst_QXmlStream::write8bitCodec() const
1636{
1637 QBuffer outBuffer;
1638 QVERIFY(outBuffer.open(QIODevice::WriteOnly));
1639 QXmlStreamWriter writer(&outBuffer);
1640 writer.setAutoFormatting(false);
1641
1642 QTextCodec *codec = QTextCodec::codecForName(name: "IBM500");
1643 if (!codec) {
1644 QSKIP("Encoding IBM500 not available.");
1645 }
1646 writer.setCodec(codec);
1647
1648 writer.writeStartDocument();
1649 writer.writeStartElement(qualifiedName: "root");
1650 writer.writeAttribute(qualifiedName: "attrib", value: "1");
1651 writer.writeEndElement();
1652 writer.writeEndDocument();
1653 outBuffer.close();
1654
1655 // test 8 bit encoding
1656 QByteArray values = outBuffer.data();
1657 QVERIFY(values.size() > 1);
1658 // check '<'
1659 QCOMPARE(values[0] & 0x00FF, 0x4c);
1660 // check '?'
1661 QCOMPARE(values[1] & 0x00FF, 0x6F);
1662
1663 // convert the start of the XML
1664 const QString expected = ("<?xml version=\"1.0\" encoding=\"IBM500\"?>");
1665 QTextDecoder *decoder = codec->makeDecoder();
1666 QVERIFY(decoder);
1667 QString decodedText = decoder->toUnicode(ba: values);
1668 delete decoder;
1669 QVERIFY(decodedText.startsWith(expected));
1670}
1671
1672void tst_QXmlStream::invalidStringCharacters() const
1673{
1674 // test scan in attributes
1675 QFETCH(QString, testString);
1676 QFETCH(bool, expectedResultNoError);
1677
1678 QByteArray values = testString.toUtf8();
1679 QBuffer inBuffer;
1680 inBuffer.setData(values);
1681 QVERIFY(inBuffer.open(QIODevice::ReadOnly));
1682 QXmlStreamReader reader(&inBuffer);
1683 do {
1684 reader.readNext();
1685 } while (!reader.atEnd());
1686 QCOMPARE((reader.error() == QXmlStreamReader::NoError), expectedResultNoError);
1687}
1688
1689void tst_QXmlStream::invalidStringCharacters_data() const
1690{
1691 // test scan in attributes
1692 QTest::addColumn<bool>(name: "expectedResultNoError");
1693 QTest::addColumn<QString>(name: "testString");
1694 QChar ctrl(0x1A);
1695 QTest::newRow(dataTag: "utf8, attributes, legal") << true << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'/>");
1696 QTest::newRow(dataTag: "utf8, attributes, only char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='")+ctrl+QString("'/>");
1697 QTest::newRow(dataTag: "utf8, attributes, 1st char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='")+ctrl+QString("abc'/>");
1698 QTest::newRow(dataTag: "utf8, attributes, middle char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='abc")+ctrl+QString("efgx'/>");
1699 QTest::newRow(dataTag: "utf8, attributes, last char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='abcde")+ctrl+QString("'/>");
1700 //
1701 QTest::newRow(dataTag: "utf8, text, legal") << true << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'>abcx1A</root>");
1702 QTest::newRow(dataTag: "utf8, text, only, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'>")+ctrl+QString("</root>");
1703 QTest::newRow(dataTag: "utf8, text, 1st char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'>abc")+ctrl+QString("def</root>");
1704 QTest::newRow(dataTag: "utf8, text, middle char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'>abc")+ctrl+QString("efg</root>");
1705 QTest::newRow(dataTag: "utf8, text, last char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'>abc")+ctrl+QString("</root>");
1706 //
1707 QTest::newRow(dataTag: "utf8, cdata text, legal") << true << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'><![CDATA[abcdefghi]]></root>");
1708 QTest::newRow(dataTag: "utf8, cdata text, only, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'><![CDATA[")+ctrl+QString("]]></root>");
1709 QTest::newRow(dataTag: "utf8, cdata text, 1st char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'><![CDATA[")+ctrl+QString("abcdefghi]]></root>");
1710 QTest::newRow(dataTag: "utf8, cdata text, middle char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'><![CDATA[abcd")+ctrl+QString("efghi]]></root>");
1711 QTest::newRow(dataTag: "utf8, cdata text, last char, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aa'><![CDATA[abcdefghi")+ctrl+QString("]]></root>");
1712 //
1713 QTest::newRow(dataTag: "utf8, mixed, control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='a")+ctrl+QString("a'><![CDATA[abcdefghi")+ctrl+QString("]]></root>");
1714 QTest::newRow(dataTag: "utf8, tag") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><roo")+ctrl+QString("t attr='aa'><![CDATA[abcdefghi]]></roo")+ctrl+QString("t>");
1715 //
1716 QTest::newRow(dataTag: "utf8, attributes, 1st char, legal escaping hex") << true << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='a&#xA0;'/>");
1717 QTest::newRow(dataTag: "utf8, attributes, 1st char, control escaping hex") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='&#x1A;aaa'/>");
1718 QTest::newRow(dataTag: "utf8, attributes, middle char, legal escaping hex") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aaa&#x1A;aaa'/>");
1719 QTest::newRow(dataTag: "utf8, attributes, last char, control escaping hex") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aaa&#x1A;'/>");
1720 QTest::newRow(dataTag: "utf8, attributes, 1st char, legal escaping dec") << true << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='a&#160;'/>");
1721 QTest::newRow(dataTag: "utf8, attributes, 1st char, control escaping dec") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='&#26;aaaa'/>");
1722 QTest::newRow(dataTag: "utf8, attributes, middle char, legal escaping dec") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aaa&#26;aaaaa'/>");
1723 QTest::newRow(dataTag: "utf8, attributes, last char, control escaping dec") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='aaaaaa&#26;'/>");
1724 QTest::newRow(dataTag: "utf8, tag escaping") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><roo&#x1A;t attr='aa'><![CDATA[abcdefghi]]></roo&#x1A;t>");
1725 //
1726 QTest::newRow(dataTag: "utf8, mix of illegal control") << false << QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><root attr='a&#0;&#x4;&#x1c;a'><![CDATA[abcdefghi]]></root>");
1727 //
1728}
1729
1730static bool isValidSingleTextChar(const ushort c)
1731{
1732 // Conforms to https://www.w3.org/TR/REC-xml/#NT-Char - except for the high range, which is done
1733 // with surrogates.
1734 // Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF]
1735 static const QPair<ushort, ushort> validRanges[] = {
1736 QPair<ushort, ushort>(0x9, 0xb),
1737 QPair<ushort, ushort>(0xd, 0xe),
1738 QPair<ushort, ushort>(0x20, 0xd800),
1739 QPair<ushort, ushort>(0xe000, 0xfffe)
1740 };
1741
1742 for (const QPair<ushort, ushort> &range : validRanges) {
1743 if (c >= range.first && c < range.second)
1744 return true;
1745 }
1746 return false;
1747}
1748
1749void tst_QXmlStream::readBack() const
1750{
1751 for (ushort c = 0; c < std::numeric_limits<ushort>::max(); ++c) {
1752 QBuffer buffer;
1753
1754 QVERIFY(buffer.open(QIODevice::WriteOnly));
1755 QXmlStreamWriter writer(&buffer);
1756 writer.writeStartDocument();
1757 writer.writeTextElement(qualifiedName: "a", text: QString(QChar(c)));
1758 writer.writeEndDocument();
1759 buffer.close();
1760
1761 if (writer.hasError()) {
1762 QVERIFY2(!isValidSingleTextChar(c), QByteArray::number(c));
1763 } else {
1764 QVERIFY2(isValidSingleTextChar(c), QByteArray::number(c));
1765 QVERIFY(buffer.open(QIODevice::ReadOnly));
1766 QXmlStreamReader reader(&buffer);
1767 do {
1768 reader.readNext();
1769 } while (!reader.atEnd());
1770 QVERIFY2(!reader.hasError(), QByteArray::number(c));
1771 }
1772 }
1773}
1774
1775void tst_QXmlStream::roundTrip_data() const
1776{
1777 QTest::addColumn<QString>(name: "in");
1778
1779 QTest::newRow(dataTag: "QTBUG-63434") <<
1780 "<?xml version=\"1.0\"?>"
1781 "<root>"
1782 "<father>"
1783 "<child xmlns:unknown=\"http://mydomain\">Text</child>"
1784 "</father>"
1785 "</root>\n";
1786}
1787
1788void tst_QXmlStream::entityExpansionLimit() const
1789{
1790 QString xml = QStringLiteral("<?xml version=\"1.0\"?>"
1791 "<!DOCTYPE foo ["
1792 "<!ENTITY a \"0123456789\" >"
1793 "<!ENTITY b \"&a;&a;&a;&a;&a;&a;&a;&a;&a;&a;\" >"
1794 "<!ENTITY c \"&b;&b;&b;&b;&b;&b;&b;&b;&b;&b;\" >"
1795 "<!ENTITY d \"&c;&c;&c;&c;&c;&c;&c;&c;&c;&c;\" >"
1796 "]>"
1797 "<foo>&d;&d;&d;</foo>");
1798 {
1799 QXmlStreamReader reader(xml);
1800 QCOMPARE(reader.entityExpansionLimit(), 4096);
1801 do {
1802 reader.readNext();
1803 } while (!reader.atEnd());
1804 QCOMPARE(reader.error(), QXmlStreamReader::NotWellFormedError);
1805 }
1806
1807 // &d; expands to 10k characters, minus the 3 removed (&d;) means it should fail
1808 // with a limit of 9996 chars and pass with 9997
1809 {
1810 QXmlStreamReader reader(xml);
1811 reader.setEntityExpansionLimit(9996);
1812 do {
1813 reader.readNext();
1814 } while (!reader.atEnd());
1815
1816 QCOMPARE(reader.error(), QXmlStreamReader::NotWellFormedError);
1817 }
1818 {
1819 QXmlStreamReader reader(xml);
1820 reader.setEntityExpansionLimit(9997);
1821 do {
1822 reader.readNext();
1823 } while (!reader.atEnd());
1824 QCOMPARE(reader.error(), QXmlStreamReader::NoError);
1825 }
1826}
1827
1828void tst_QXmlStream::roundTrip() const
1829{
1830 QFETCH(QString, in);
1831 QString out;
1832
1833 QXmlStreamReader reader(in);
1834 QXmlStreamWriter writer(&out);
1835
1836 while (!reader.atEnd()) {
1837 reader.readNext();
1838 QVERIFY(!reader.hasError());
1839 writer.writeCurrentToken(reader);
1840 QVERIFY(!writer.hasError());
1841 }
1842 QCOMPARE(out, in);
1843}
1844
1845#include "tst_qxmlstream.moc"
1846// vim: et:ts=4:sw=4:sts=4
1847

source code of qtbase/tests/auto/corelib/serialization/qxmlstream/tst_qxmlstream.cpp