summaryrefslogtreecommitdiffstats
path: root/tests/auto/corelib/xml/qxmlstream/tst_qxmlstream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/auto/corelib/xml/qxmlstream/tst_qxmlstream.cpp')
-rw-r--r--tests/auto/corelib/xml/qxmlstream/tst_qxmlstream.cpp1650
1 files changed, 1650 insertions, 0 deletions
diff --git a/tests/auto/corelib/xml/qxmlstream/tst_qxmlstream.cpp b/tests/auto/corelib/xml/qxmlstream/tst_qxmlstream.cpp
new file mode 100644
index 0000000000..0ddbe990a0
--- /dev/null
+++ b/tests/auto/corelib/xml/qxmlstream/tst_qxmlstream.cpp
@@ -0,0 +1,1650 @@
+/****************************************************************************
+**
+** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
+** All rights reserved.
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <QDirIterator>
+#include <QEventLoop>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QNetworkRequest>
+#include <QtTest/QtTest>
+#include <QUrl>
+#include <QXmlDefaultHandler>
+#include <QXmlStreamReader>
+
+#include "qc14n.h"
+
+//TESTED_CLASS=QXmlStreamReader QXmlStreamWriter
+//TESTED_FILES=corelib/xml/stream/qxmlutils.cpp corelib/xml/stream/qxmlstream.cpp corelib/xml/stream/qxmlstream_p.h
+
+#ifdef Q_OS_SYMBIAN
+#define SRCDIR ""
+#endif
+
+Q_DECLARE_METATYPE(QXmlStreamReader::ReadElementTextBehaviour)
+
+static const char *const catalogFile = SRCDIR "XML-Test-Suite/xmlconf/finalCatalog.xml";
+static const int expectedRunCount = 1646;
+static const int expectedSkipCount = 532;
+
+static inline int best(int a, int b)
+{
+ if (a < 0)
+ return b;
+ if (b < 0)
+ return a;
+ return qMin(a, b);
+}
+
+static inline int best(int a, int b, int c)
+{
+ if (a < 0)
+ return best(b, c);
+ if (b < 0)
+ return best(a, c);
+ if (c < 0)
+ return best(a, b);
+ return qMin(qMin(a, b), c);
+}
+
+/**
+ * Opens @p filename and returns content produced as per
+ * xmlconf/xmltest/canonxml.html.
+ *
+ * @p docType is the DOCTYPE name that the returned output should
+ * have, if it doesn't already have one.
+ */
+static QByteArray makeCanonical(const QString &filename,
+ const QString &docType,
+ bool &hasError,
+ bool testIncremental = false)
+{
+ QFile file(filename);
+ file.open(QIODevice::ReadOnly);
+
+ QXmlStreamReader reader;
+
+ QByteArray buffer;
+ int bufferPos = 0;
+
+ if (testIncremental)
+ buffer = file.readAll();
+ else
+ reader.setDevice(&file);
+
+ QByteArray outarray;
+ QXmlStreamWriter writer(&outarray);
+
+ forever {
+ while (!reader.atEnd()) {
+ reader.readNext();
+ if (reader.isDTD()) {
+ if (!reader.notationDeclarations().isEmpty()) {
+ QString dtd;
+ QTextStream writeDtd(&dtd);
+
+ writeDtd << "<!DOCTYPE ";
+ writeDtd << docType;
+ writeDtd << " [";
+ writeDtd << endl;
+ QMap<QString, QXmlStreamNotationDeclaration> sortedNotationDeclarations;
+ foreach (QXmlStreamNotationDeclaration notation, reader.notationDeclarations())
+ sortedNotationDeclarations.insert(notation.name().toString(), notation);
+ foreach (QXmlStreamNotationDeclaration notation, sortedNotationDeclarations.values()) {
+ writeDtd << "<!NOTATION ";
+ writeDtd << notation.name().toString();
+ if (notation.publicId().isEmpty()) {
+ writeDtd << " SYSTEM \'";
+ writeDtd << notation.systemId().toString();
+ writeDtd << "\'";
+ } else {
+ writeDtd << " PUBLIC \'";
+ writeDtd << notation.publicId().toString();
+ writeDtd << "\'";
+ if (!notation.systemId().isEmpty() ) {
+ writeDtd << " \'";
+ writeDtd << notation.systemId().toString();
+ writeDtd << "\'";
+ }
+ }
+ writeDtd << ">";
+ writeDtd << endl;
+ }
+
+ writeDtd << "]>";
+ writeDtd << endl;
+ writer.writeDTD(dtd);
+ }
+ } else if (reader.isStartElement()) {
+ writer.writeStartElement(reader.namespaceUri().toString(), reader.name().toString());
+
+ QMap<QString, QXmlStreamAttribute> sortedAttributes;
+ foreach(QXmlStreamAttribute attribute, reader.attributes())
+ sortedAttributes.insert(attribute.name().toString(), attribute);
+ foreach(QXmlStreamAttribute attribute, sortedAttributes.values())
+ writer.writeAttribute(attribute);
+ writer.writeCharacters(QString()); // write empty string to avoid having empty xml tags
+ } else if (reader.isCharacters()) {
+ // make canonical
+
+ QString text = reader.text().toString();
+ int i = 0;
+ int p = 0;
+ while ((i = best(text.indexOf(QLatin1Char(10), p),
+ text.indexOf(QLatin1Char(13), p),
+ text.indexOf(QLatin1Char(9), p))) >= 0) {
+ writer.writeCharacters(text.mid(p, i - p));
+ writer.writeEntityReference(QString("#%1").arg(text.at(i).unicode()));
+ p = i + 1;
+ }
+ writer.writeCharacters(text.mid(p));
+ } else if (reader.isStartDocument() || reader.isEndDocument() || reader.isComment()){
+ // canonical does not want any of those
+ } else if (reader.isProcessingInstruction() && reader.processingInstructionData().isEmpty()) {
+ // for some reason canonical wants a space
+ writer.writeProcessingInstruction(reader.processingInstructionTarget().toString(), QLatin1String(""));
+ } else if (!reader.hasError()){
+ writer.writeCurrentToken(reader);
+ }
+ }
+ if (testIncremental && bufferPos < buffer.size()) {
+ reader.addData(QByteArray(buffer.data() + (bufferPos++), 1));
+ } else {
+ break;
+ }
+ }
+
+ if (reader.hasError()) {
+ hasError = true;
+ outarray += "ERROR:";
+ outarray += reader.errorString().toLatin1();
+ }
+ else
+ hasError = false;
+
+ return outarray;
+}
+
+/**
+ * @short Returns the lexical QName of the document element in
+ * @p document.
+ *
+ * It is assumed that @p document is a well-formed XML document.
+ */
+static QString documentElement(const QByteArray &document)
+{
+ QXmlStreamReader reader(document);
+
+ while(!reader.atEnd())
+ {
+ if(reader.isStartElement())
+ return reader.qualifiedName().toString();
+
+ reader.readNext();
+ }
+
+ qFatal("The input %s didn't contain an element", document.constData());
+ return QString();
+}
+
+/**
+ * @short Loads W3C's XML conformance test suite and runs it on QXmlStreamReader.
+ *
+ * Since this suite is fairly large, it runs the tests sequentially in order to not
+ * have them all loaded into memory at once. In this way, the maximum memory usage stays
+ * low, which means one can run valgrind on this test. However, the drawback is that
+ * QTestLib's usual error reporting and testing mechanisms are slightly bypassed.
+ *
+ * Part of this code is a manual, ad-hoc implementation of xml:base.
+ *
+ * @see <a href="http://www.w3.org/XML/Test/">Extensible
+ * Markup Language (XML) Conformance Test Suites</a>
+ */
+class TestSuiteHandler : public QXmlDefaultHandler
+{
+public:
+ /**
+ * The first string is the test ID, the second is
+ * a description of what went wrong.
+ */
+ typedef QPair<QString, QString> GeneralFailure;
+
+ /**
+ * The string is the test ID.
+ */
+ QStringList successes;
+
+ /**
+ * The first value is the baseline, while the se
+ */
+ class MissedBaseline
+ {
+ public:
+ MissedBaseline(const QString &aId,
+ const QByteArray &aExpected,
+ const QByteArray &aOutput) : id(aId),
+ expected(aExpected),
+ output(aOutput)
+ {
+ if (aId.isEmpty())
+ qFatal("%s: aId must not be an empty string", Q_FUNC_INFO);
+ }
+
+ QString id;
+ QByteArray expected;
+ QByteArray output;
+ };
+
+ QList<GeneralFailure> failures;
+ QList<MissedBaseline> missedBaselines;
+
+ /**
+ * The count of how many tests that were run.
+ */
+ int runCount;
+
+ int skipCount;
+
+ /**
+ * @p baseURI is the the URI of where the catalog file resides.
+ */
+ TestSuiteHandler(const QUrl &baseURI) : runCount(0),
+ skipCount(0)
+ {
+ if (!baseURI.isValid())
+ qFatal("%s: baseURI must be valid", Q_FUNC_INFO);
+ m_baseURI.push(baseURI);
+ }
+
+ virtual bool characters(const QString &chars)
+ {
+ m_ch = chars;
+ return true;
+ }
+
+ virtual bool startElement(const QString &,
+ const QString &,
+ const QString &,
+ const QXmlAttributes &atts)
+ {
+ m_atts.push(atts);
+ const int i = atts.index(QLatin1String("xml:base"));
+
+ if(i != -1)
+ m_baseURI.push(m_baseURI.top().resolved(atts.value(i)));
+
+ return true;
+ }
+
+ virtual bool endElement(const QString &,
+ const QString &localName,
+ const QString &)
+ {
+ if(localName == QLatin1String("TEST"))
+ {
+ /* We don't want tests for XML 1.1.0, in fact). */
+ if(m_atts.top().value(QString(), QLatin1String("VERSION")) == QLatin1String("1.1"))
+ {
+ ++skipCount;
+ m_atts.pop();
+ return true;
+ }
+
+ /* We don't want tests that conflict with the namespaces spec. Our parser is a
+ * namespace-aware parser. */
+ else if(m_atts.top().value(QString(), QLatin1String("NAMESPACE")) == QLatin1String("no"))
+ {
+ ++skipCount;
+ m_atts.pop();
+ return true;
+ }
+
+ const QString inputFilePath(m_baseURI.top().resolved(m_atts.top().value(QString(), QLatin1String("URI")))
+ .toLocalFile());
+ const QString id(m_atts.top().value(QString(), QLatin1String("ID")));
+ const QString type(m_atts.top().value(QString(), QLatin1String("TYPE")));
+
+ QString expectedFilePath;
+ const int index = m_atts.top().index(QString(), QLatin1String("OUTPUT"));
+
+ //qDebug() << "Running test case:" << id;
+
+ if(index != -1)
+ {
+ expectedFilePath = m_baseURI.top().resolved(m_atts.top().value(QString(),
+ QLatin1String("OUTPUT"))).toLocalFile();
+ }
+
+ /* testcases.dtd: 'No parser should accept a "not-wf" testcase
+ * unless it's a nonvalidating parser and the test contains
+ * external entities that the parser doesn't read.'
+ *
+ * We also let this apply to "valid", "invalid" and "error" tests, although
+ * I'm not fully sure this is correct. */
+ const QString ents(m_atts.top().value(QString(), QLatin1String("ENTITIES")));
+ m_atts.pop();
+
+ if(ents == QLatin1String("both") ||
+ ents == QLatin1String("general") ||
+ ents == QLatin1String("parameter"))
+ {
+ ++skipCount;
+ return true;
+ }
+
+ ++runCount;
+
+ QFile inputFile(inputFilePath);
+ if(!inputFile.open(QIODevice::ReadOnly))
+ {
+ failures.append(qMakePair(id, QString::fromLatin1("Failed to open input file %1").arg(inputFilePath)));
+ return true;
+ }
+
+ if(type == QLatin1String("not-wf"))
+ {
+ if(isWellformed(&inputFile, ParseSinglePass))
+ {
+ failures.append(qMakePair(id, QString::fromLatin1("Failed to flag %1 as not well-formed.")
+ .arg(inputFilePath)));
+
+ /* Exit, the incremental test will fail as well, no need to flood the output. */
+ return true;
+ }
+ else
+ successes.append(id);
+
+ if(isWellformed(&inputFile, ParseIncrementally))
+ {
+ failures.append(qMakePair(id, QString::fromLatin1("Failed to flag %1 as not well-formed with incremental parsing.")
+ .arg(inputFilePath)));
+ }
+ else
+ successes.append(id);
+
+ return true;
+ }
+
+ QXmlStreamReader reader(&inputFile);
+
+ /* See testcases.dtd which reads: 'Nonvalidating parsers
+ * must also accept "invalid" testcases, but validating ones must reject them.' */
+ if(type == QLatin1String("invalid") || type == QLatin1String("valid"))
+ {
+ QByteArray expected;
+ QString docType;
+
+ /* We only want to compare against a baseline when we have
+ * one. Some "invalid"-tests, for instance, doesn't have baselines. */
+ if(!expectedFilePath.isEmpty())
+ {
+ QFile expectedFile(expectedFilePath);
+
+ if(!expectedFile.open(QIODevice::ReadOnly))
+ {
+ failures.append(qMakePair(id, QString::fromLatin1("Failed to open baseline %1").arg(expectedFilePath)));
+ return true;
+ }
+
+ expected = expectedFile.readAll();
+ docType = documentElement(expected);
+ }
+ else
+ docType = QLatin1String("dummy");
+
+ bool hasError = true;
+ bool incremental = false;
+
+ QByteArray input(makeCanonical(inputFilePath, docType, hasError, incremental));
+
+ if (!hasError && !expectedFilePath.isEmpty() && input == expected)
+ input = makeCanonical(inputFilePath, docType, hasError, (incremental = true));
+
+ if(hasError)
+ failures.append(qMakePair(id, QString::fromLatin1("Failed to parse %1%2")
+ .arg(incremental?"(incremental run only) ":"")
+ .arg(inputFilePath)));
+
+ if(!expectedFilePath.isEmpty() && input != expected)
+ {
+ missedBaselines.append(MissedBaseline(id, expected, input));
+ return true;
+ }
+ else
+ {
+ successes.append(id);
+ return true;
+ }
+ }
+ else if(type == QLatin1String("error"))
+ {
+ /* Not yet sure about this one. */
+ // TODO
+ return true;
+ }
+ else
+ {
+ qFatal("The input catalog is invalid.");
+ return false;
+ }
+ }
+ else if(localName == QLatin1String("TESTCASES") && m_atts.top().index(QLatin1String("xml:base")) != -1)
+ m_baseURI.pop();
+
+ m_atts.pop();
+
+ return true;
+ }
+
+ enum ParseMode
+ {
+ ParseIncrementally,
+ ParseSinglePass
+ };
+
+ static bool isWellformed(QIODevice *const inputFile, const ParseMode mode)
+ {
+ if (!inputFile)
+ qFatal("%s: inputFile must be a valid QIODevice pointer", Q_FUNC_INFO);
+ if (!inputFile->isOpen())
+ qFatal("%s: inputFile must be opened by the caller", Q_FUNC_INFO);
+ if (mode != ParseIncrementally && mode != ParseSinglePass)
+ qFatal("%s: mode must be either ParseIncrementally or ParseSinglePass", Q_FUNC_INFO);
+
+ if(mode == ParseIncrementally)
+ {
+ QXmlStreamReader reader;
+ QByteArray buffer;
+ int bufferPos = 0;
+
+ buffer = inputFile->readAll();
+
+ while(true)
+ {
+ while(!reader.atEnd())
+ reader.readNext();
+
+ if(bufferPos < buffer.size())
+ {
+ ++bufferPos;
+ reader.addData(QByteArray(buffer.data() + bufferPos, 1));
+ }
+ else
+ break;
+ }
+
+ return !reader.hasError();
+ }
+ else
+ {
+ QXmlStreamReader reader;
+ reader.setDevice(inputFile);
+
+ while(!reader.atEnd())
+ reader.readNext();
+
+ return !reader.hasError();
+ }
+ }
+
+private:
+ QStack<QXmlAttributes> m_atts;
+ QString m_ch;
+ QStack<QUrl> m_baseURI;
+};
+
+class tst_QXmlStream: public QObject
+{
+ Q_OBJECT
+public:
+ tst_QXmlStream() : m_handler(QUrl::fromLocalFile(QLatin1String(catalogFile)))
+ {
+ }
+
+private slots:
+ void initTestCase();
+ void reportFailures() const;
+ void reportFailures_data();
+ void checkBaseline() const;
+ void checkBaseline_data() const;
+ void testReader() const;
+ void testReader_data() const;
+ void reportSuccess() const;
+ void reportSuccess_data() const;
+ void parseXSLTTestSuite() const;
+ void writerHangs() const;
+ void writerAutoFormattingWithComments() const;
+ void writerAutoFormattingWithTabs() const;
+ void writerAutoFormattingWithProcessingInstructions() const;
+ void writerAutoEmptyTags() const;
+ void writeAttributesWithSpace() const;
+ void addExtraNamespaceDeclarations();
+ void setEntityResolver();
+ void readFromQBuffer() const;
+ void readFromQBufferInvalid() const;
+ void readNextStartElement() const;
+ void readElementText() const;
+ void readElementText_data() const;
+ void crashInUTF16Codec() const;
+ void hasAttributeSignature() const;
+ void hasAttribute() const;
+ void writeWithCodec() const;
+ void writeWithUtf8Codec() const;
+ void writeWithStandalone() const;
+ void entitiesAndWhitespace_1() const;
+ void entitiesAndWhitespace_2() const;
+ void testFalsePrematureError() const;
+ void garbageInXMLPrologDefaultCodec() const;
+ void garbageInXMLPrologUTF8Explicitly() const;
+ void clear() const;
+ void checkCommentIndentation() const;
+ void checkCommentIndentation_data() const;
+ void qtbug9196_crash() const;
+ void hasError() const;
+
+private:
+ static QByteArray readFile(const QString &filename);
+
+ TestSuiteHandler m_handler;
+};
+
+void tst_QXmlStream::initTestCase()
+{
+ QFile file(QString::fromLatin1(catalogFile));
+ QVERIFY2(file.open(QIODevice::ReadOnly),
+ qPrintable(QString::fromLatin1("Failed to open the test suite catalog; %1").arg(file.fileName())));
+
+ QXmlInputSource source(&file);
+ QXmlSimpleReader reader;
+ reader.setContentHandler(&m_handler);
+
+ QVERIFY(reader.parse(&source, false));
+}
+
+void tst_QXmlStream::reportFailures() const
+{
+ QFETCH(bool, isError);
+ QFETCH(QString, description);
+
+ QVERIFY2(!isError, qPrintable(description));
+}
+
+void tst_QXmlStream::reportFailures_data()
+{
+ const int len = m_handler.failures.count();
+
+ QTest::addColumn<bool>("isError");
+ QTest::addColumn<QString>("description");
+
+ /* We loop over all our failures(if any!), and output them such
+ * that they appear in the QTestLib log. */
+ for(int i = 0; i < len; ++i)
+ QTest::newRow(m_handler.failures.at(i).first.toLatin1().constData()) << true << m_handler.failures.at(i).second;
+
+ /* We need to add at least one column of test data, otherwise QTestLib complains. */
+ if(len == 0)
+ QTest::newRow("Whole test suite passed") << false << QString();
+
+ /* We compare the test case counts to ensure that we've actually run test cases, that
+ * the driver hasn't been broken or changed without updating the expected count, and
+ * similar reasons. */
+ QCOMPARE(m_handler.runCount, expectedRunCount);
+ QCOMPARE(m_handler.skipCount, expectedSkipCount);
+}
+
+void tst_QXmlStream::checkBaseline() const
+{
+ QFETCH(bool, isError);
+ QFETCH(QString, expected);
+ QFETCH(QString, output);
+
+ if(isError)
+ QCOMPARE(output, expected);
+}
+
+void tst_QXmlStream::checkBaseline_data() const
+{
+ QTest::addColumn<bool>("isError");
+ QTest::addColumn<QString>("expected");
+ QTest::addColumn<QString>("output");
+
+ const int len = m_handler.missedBaselines.count();
+
+ for(int i = 0; i < len; ++i)
+ {
+ const TestSuiteHandler::MissedBaseline &b = m_handler.missedBaselines.at(i);
+
+ /* We indeed don't know what encoding the content is in so in some cases fromUtf8
+ * is all wrong, but it's an acceptable guess for error reporting. */
+ QTest::newRow(b.id.toLatin1().constData())
+ << true
+ << QString::fromUtf8(b.expected.constData())
+ << QString::fromUtf8(b.output.constData());
+ }
+
+ if(len == 0)
+ QTest::newRow("dummy") << false << QString() << QString();
+}
+
+void tst_QXmlStream::reportSuccess() const
+{
+ QFETCH(bool, isError);
+
+ QVERIFY(!isError);
+}
+
+void tst_QXmlStream::reportSuccess_data() const
+{
+ QTest::addColumn<bool>("isError");
+
+ const int len = m_handler.successes.count();
+
+ for(int i = 0; i < len; ++i)
+ QTest::newRow(m_handler.successes.at(i).toLatin1().constData()) << false;
+
+ if(len == 0)
+ QTest::newRow("No test cases succeeded.") << true;
+}
+
+QByteArray tst_QXmlStream::readFile(const QString &filename)
+{
+ QFile file(filename);
+ file.open(QIODevice::ReadOnly);
+
+ QXmlStreamReader reader;
+
+ reader.setDevice(&file);
+ QByteArray outarray;
+ QTextStream writer(&outarray);
+ // We always want UTF-8, and not what the system picks up.
+ writer.setCodec("UTF-8");
+
+ while (!reader.atEnd()) {
+ reader.readNext();
+ writer << reader.tokenString() << "(";
+ if (reader.isWhitespace())
+ writer << " whitespace";
+ if (reader.isCDATA())
+ writer << " CDATA";
+ if (reader.isStartDocument() && reader.isStandaloneDocument())
+ writer << " standalone";
+ if (!reader.text().isEmpty())
+ writer << " text=\"" << reader.text().toString() << "\"";
+ if (!reader.processingInstructionTarget().isEmpty())
+ writer << " processingInstructionTarget=\"" << reader.processingInstructionTarget().toString() << "\"";
+ if (!reader.processingInstructionData().isEmpty())
+ writer << " processingInstructionData=\"" << reader.processingInstructionData().toString() << "\"";
+ if (!reader.dtdName().isEmpty())
+ writer << " dtdName=\"" << reader.dtdName().toString() << "\"";
+ if (!reader.dtdPublicId().isEmpty())
+ writer << " dtdPublicId=\"" << reader.dtdPublicId().toString() << "\"";
+ if (!reader.dtdSystemId().isEmpty())
+ writer << " dtdSystemId=\"" << reader.dtdSystemId().toString() << "\"";
+ if (!reader.documentVersion().isEmpty())
+ writer << " documentVersion=\"" << reader.documentVersion().toString() << "\"";
+ if (!reader.documentEncoding().isEmpty())
+ writer << " documentEncoding=\"" << reader.documentEncoding().toString() << "\"";
+ if (!reader.name().isEmpty())
+ writer << " name=\"" << reader.name().toString() << "\"";
+ if (!reader.namespaceUri().isEmpty())
+ writer << " namespaceUri=\"" << reader.namespaceUri().toString() << "\"";
+ if (!reader.qualifiedName().isEmpty())
+ writer << " qualifiedName=\"" << reader.qualifiedName().toString() << "\"";
+ if (!reader.prefix().isEmpty())
+ writer << " prefix=\"" << reader.prefix().toString() << "\"";
+ if (reader.attributes().size()) {
+ foreach(QXmlStreamAttribute attribute, reader.attributes()) {
+ writer << endl << " Attribute(";
+ if (!attribute.name().isEmpty())
+ writer << " name=\"" << attribute.name().toString() << "\"";
+ if (!attribute.namespaceUri().isEmpty())
+ writer << " namespaceUri=\"" << attribute.namespaceUri().toString() << "\"";
+ if (!attribute.qualifiedName().isEmpty())
+ writer << " qualifiedName=\"" << attribute.qualifiedName().toString() << "\"";
+ if (!attribute.prefix().isEmpty())
+ writer << " prefix=\"" << attribute.prefix().toString() << "\"";
+ if (!attribute.value().isEmpty())
+ writer << " value=\"" << attribute.value().toString() << "\"";
+ writer << " )" << endl;
+ }
+ }
+ if (reader.namespaceDeclarations().size()) {
+ foreach(QXmlStreamNamespaceDeclaration namespaceDeclaration, reader.namespaceDeclarations()) {
+ writer << endl << " NamespaceDeclaration(";
+ if (!namespaceDeclaration.prefix().isEmpty())
+ writer << " prefix=\"" << namespaceDeclaration.prefix().toString() << "\"";
+ if (!namespaceDeclaration.namespaceUri().isEmpty())
+ writer << " namespaceUri=\"" << namespaceDeclaration.namespaceUri().toString() << "\"";
+ writer << " )" << endl;
+ }
+ }
+ if (reader.notationDeclarations().size()) {
+ foreach(QXmlStreamNotationDeclaration notationDeclaration, reader.notationDeclarations()) {
+ writer << endl << " NotationDeclaration(";
+ if (!notationDeclaration.name().isEmpty())
+ writer << " name=\"" << notationDeclaration.name().toString() << "\"";
+ if (!notationDeclaration.systemId().isEmpty())
+ writer << " systemId=\"" << notationDeclaration.systemId().toString() << "\"";
+ if (!notationDeclaration.publicId().isEmpty())
+ writer << " publicId=\"" << notationDeclaration.publicId().toString() << "\"";
+ writer << " )" << endl;
+ }
+ }
+ if (reader.entityDeclarations().size()) {
+ foreach(QXmlStreamEntityDeclaration entityDeclaration, reader.entityDeclarations()) {
+ writer << endl << " EntityDeclaration(";
+ if (!entityDeclaration.name().isEmpty())
+ writer << " name=\"" << entityDeclaration.name().toString() << "\"";
+ if (!entityDeclaration.notationName().isEmpty())
+ writer << " notationName=\"" << entityDeclaration.notationName().toString() << "\"";
+ if (!entityDeclaration.systemId().isEmpty())
+ writer << " systemId=\"" << entityDeclaration.systemId().toString() << "\"";
+ if (!entityDeclaration.publicId().isEmpty())
+ writer << " publicId=\"" << entityDeclaration.publicId().toString() << "\"";
+ if (!entityDeclaration.value().isEmpty())
+ writer << " value=\"" << entityDeclaration.value().toString() << "\"";
+ writer << " )" << endl;
+ }
+ }
+ writer << " )" << endl;
+ }
+ if (reader.hasError())
+ writer << "ERROR: " << reader.errorString() << endl;
+ return outarray;
+}
+
+void tst_QXmlStream::testReader() const
+{
+ QFETCH(QString, xml);
+ QFETCH(QString, ref);
+ QFile file(ref);
+ if (!file.exists()) {
+ QByteArray reference = readFile(xml);
+ QVERIFY(file.open(QIODevice::WriteOnly));
+ file.write(reference);
+ file.close();
+ } else {
+ QVERIFY(file.open(QIODevice::ReadOnly | QIODevice::Text));
+ QString reference = QString::fromUtf8(file.readAll());
+ QString qxmlstream = QString::fromUtf8(readFile(xml));
+ QCOMPARE(qxmlstream, reference);
+ }
+}
+
+void tst_QXmlStream::testReader_data() const
+{
+ QTest::addColumn<QString>("xml");
+ QTest::addColumn<QString>("ref");
+ QDir dir;
+ dir.cd(SRCDIR "data/");
+ foreach(QString filename , dir.entryList(QStringList() << "*.xml")) {
+ QString reference = QFileInfo(filename).baseName() + ".ref";
+ QTest::newRow(dir.filePath(filename).toLatin1().data()) << dir.filePath(filename) << dir.filePath(reference);
+ }
+}
+
+void tst_QXmlStream::parseXSLTTestSuite() const
+{
+ /* We disable this test for now, so it doesn't show up as an XFAIL. */
+#if 0
+ QEXPECT_FAIL("", "Two problems needs to be solved in order to enable this test: \n"
+ "* The XSLT suite is 69 MB large, which is quite a lot compared to the existing XML suite on 2 mb.\n"
+ "* We need a c14n-like implementation in order to compare the outputs.", Abort);
+ QVERIFY(false);
+
+ /* We don't yet know this. TODO */
+ int xsltExpectedRunCount = -1;
+
+ QStringList nameFilters;
+ nameFilters.append("*.xsl");
+ nameFilters.append("*.xml");
+
+ QDirIterator dirIterator("XSLT-Test-Suite/", nameFilters,
+ QDir::AllEntries, QDirIterator::Subdirectories);
+
+ int filesParsed = 0;
+
+ while(dirIterator.hasNext())
+ {
+ dirIterator.next();
+
+ const QString fp(dirIterator.filePath());
+ qDebug() << "Found" << fp;
+
+ QFile inputFile(fp);
+ QVERIFY(inputFile.open(QIODevice::ReadOnly));
+
+ /* Read in and write out to the QByteArray. */
+ QByteArray outputArray;
+ {
+ QXmlStreamReader reader(&inputFile);
+
+ QXmlStreamWriter writer(&outputArray);
+
+ while(!reader.atEnd())
+ {
+ writer.writeCurrentToken(reader);
+ reader.readNext();
+
+ QVERIFY2(!reader.hasError(), qPrintable(reader.errorString()));
+ }
+ /* Might be we got an error here, but we don't care. */
+ }
+
+ /* Read in the two files, and compare them. */
+ {
+ QBuffer outputBuffer(&outputArray);
+ outputBuffer.open(QIODevice::ReadOnly);
+ inputFile.close();
+ inputFile.open(QIODevice::ReadOnly);
+
+ QString message;
+ const bool isEqual = QC14N::isEqual(&inputFile, &outputBuffer, &message);
+
+ QVERIFY2(isEqual, message.toLatin1().constData());
+
+ ++filesParsed;
+ }
+ }
+
+ QCOMPARE(xsltExpectedRunCount, filesParsed);
+#endif
+}
+
+void tst_QXmlStream::addExtraNamespaceDeclarations()
+{
+ const char *data = "<bla><undeclared:foo/><undeclared_too:foo/></bla>";
+ {
+ QXmlStreamReader xml(data);
+ while (!xml.atEnd()) {
+ xml.readNext();
+ }
+ QVERIFY2(xml.hasError(), "namespaces undeclared");
+ }
+ {
+ QXmlStreamReader xml(data);
+ xml.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("undeclared", "blabla"));
+ xml.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration("undeclared_too", "foofoo"));
+ while (!xml.atEnd()) {
+ xml.readNext();
+ }
+ QVERIFY2(!xml.hasError(), xml.errorString().toLatin1().constData());
+ }
+}
+
+
+class EntityResolver : public QXmlStreamEntityResolver {
+public:
+ QString resolveUndeclaredEntity(const QString &name) {
+ static int count = 0;
+ return name.toUpper() + QString::number(++count);
+ }
+};
+void tst_QXmlStream::setEntityResolver()
+{
+ const char *data = "<bla foo=\"&undeclared;\">&undeclared_too;</bla>";
+ {
+ QXmlStreamReader xml(data);
+ while (!xml.atEnd()) {
+ xml.readNext();
+ }
+ QVERIFY2(xml.hasError(), "undeclared entities");
+ }
+ {
+ QString foo;
+ QString bla_text;
+ QXmlStreamReader xml(data);
+ EntityResolver resolver;
+ xml.setEntityResolver(&resolver);
+ while (!xml.atEnd()) {
+ xml.readNext();
+ if (xml.isStartElement())
+ foo = xml.attributes().value("foo").toString();
+ if (xml.isCharacters())
+ bla_text += xml.text().toString();
+ }
+ QVERIFY2(!xml.hasError(), xml.errorString().toLatin1().constData());
+ QCOMPARE(foo, QLatin1String("UNDECLARED1"));
+ QCOMPARE(bla_text, QLatin1String("UNDECLARED_TOO2"));
+ }
+}
+
+void tst_QXmlStream::testFalsePrematureError() const // task 179320
+{
+ const char *illegal_start = "illegal<sta";
+ const char *legal_start = "<sta";
+ const char* end = "rt/>";
+ {
+ QXmlStreamReader xml("");
+ while (!xml.atEnd()) {
+ xml.readNext();
+ }
+ QVERIFY(xml.error() == QXmlStreamReader::PrematureEndOfDocumentError);
+ QCOMPARE(xml.errorString(), QLatin1String("Premature end of document."));
+ xml.addData(legal_start);
+ while (!xml.atEnd()) {
+ xml.readNext();
+ }
+ QVERIFY(xml.error() == QXmlStreamReader::PrematureEndOfDocumentError);
+ QCOMPARE(xml.errorString(), QLatin1String("Premature end of document."));
+ xml.addData(end);
+ while (!xml.atEnd()) {
+ xml.readNext();
+ }
+ QVERIFY(!xml.hasError());
+ }
+ {
+ QXmlStreamReader xml(illegal_start);
+ while (!xml.atEnd()) {
+ xml.readNext();
+ }
+ QVERIFY(xml.hasError());
+ QCOMPARE(xml.errorString(), QLatin1String("Start tag expected."));
+ QVERIFY(xml.error() == QXmlStreamReader::NotWellFormedError);
+ }
+}
+
+/*!
+ See task 188737. Crash due to using empty QStack.
+ */
+void tst_QXmlStream::writerHangs() const
+{
+ QFile file("test.xml");
+
+ QVERIFY(file.open(QIODevice::WriteOnly));
+
+ QXmlStreamWriter writer(&file);
+ double radius = 4.0;
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeEmptyElement("circle");
+ writer.writeAttribute("radius", QString::number(radius));
+ writer.writeEndElement();
+ writer.writeEndDocument();
+}
+/*!
+ Task 189611
+*/
+void tst_QXmlStream::writerAutoFormattingWithComments() const
+{
+ QBuffer buffer;
+ buffer.open(QIODevice::WriteOnly);
+
+ QXmlStreamWriter writer(&buffer);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeComment("This is a comment");
+ writer.writeEndDocument();
+ const char *str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--This is a comment-->\n";
+ QCOMPARE(buffer.buffer().data(), str);
+}
+
+
+/*!
+ Task 206782
+*/
+void tst_QXmlStream::writerAutoFormattingWithTabs() const
+{
+ QBuffer buffer;
+ buffer.open(QIODevice::WriteOnly);
+
+
+ QXmlStreamWriter writer(&buffer);
+ writer.setAutoFormatting(true);
+ writer.setAutoFormattingIndent(-1);
+ QCOMPARE(writer.autoFormattingIndent(), -1);
+ writer.writeStartDocument();
+ writer.writeStartElement("A");
+ writer.writeStartElement("B");
+ writer.writeEndDocument();
+ const char *str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<A>\n\t<B/>\n</A>\n";
+ QCOMPARE(buffer.buffer().data(), str);
+}
+
+void tst_QXmlStream::writerAutoFormattingWithProcessingInstructions() const
+{
+ QBuffer buffer;
+ buffer.open(QIODevice::WriteOnly);
+
+ QXmlStreamWriter writer(&buffer);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument();
+ writer.writeProcessingInstruction("B", "C");
+ writer.writeStartElement("A");
+ writer.writeEndElement();
+ writer.writeEndDocument();
+ const char *str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<?B C?>\n<A/>\n";
+ QCOMPARE(buffer.buffer().data(), str);
+}
+
+/*!
+ Task 204822
+*/
+void tst_QXmlStream::writeAttributesWithSpace() const
+{
+ QBuffer buffer;
+ buffer.open(QIODevice::WriteOnly);
+
+
+ QXmlStreamWriter writer(&buffer);
+ writer.writeStartDocument();
+ writer.writeEmptyElement("A");
+ writer.writeAttribute("attribute", QString("value")+QChar::Nbsp);
+ writer.writeEndDocument();
+ QString s = QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?><A attribute=\"value%1\"/>\n").arg(QChar(QChar::Nbsp));
+ QCOMPARE(buffer.buffer().data(), s.toUtf8().data());
+}
+
+/*!
+ Task 209340
+*/
+void tst_QXmlStream::writerAutoEmptyTags() const
+{
+ QBuffer buffer;
+ buffer.open(QIODevice::WriteOnly);
+
+
+ QXmlStreamWriter writer(&buffer);
+
+ writer.writeStartDocument();
+
+ writer.writeStartElement("Hans");
+ writer.writeAttribute("key", "value");
+ writer.writeEndElement();
+
+ writer.writeStartElement("Hans");
+ writer.writeAttribute("key", "value");
+ writer.writeEmptyElement("Leer");
+ writer.writeAttribute("key", "value");
+ writer.writeEndElement();
+
+ writer.writeStartElement("Hans");
+ writer.writeAttribute("key", "value");
+ writer.writeCharacters("stuff");
+ writer.writeEndElement();
+
+ writer.writeEndDocument();
+
+ 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");
+ QCOMPARE(buffer.buffer().data(), s.toUtf8().data());
+}
+
+void tst_QXmlStream::readFromQBuffer() const
+{
+ QByteArray in("<e/>");
+ QBuffer buffer(&in);
+ QVERIFY(buffer.open(QIODevice::ReadOnly));
+
+ QXmlStreamReader reader(&buffer);
+
+ while(!reader.atEnd())
+ {
+ reader.readNext();
+ }
+
+ QVERIFY(!reader.hasError());
+}
+
+void tst_QXmlStream::readFromQBufferInvalid() const
+{
+ QByteArray in("<e/><e/>");
+ QBuffer buffer(&in);
+ QVERIFY(buffer.open(QIODevice::ReadOnly));
+
+ QXmlStreamReader reader(&buffer);
+
+ while(!reader.atEnd())
+ {
+ reader.readNext();
+ }
+
+ QVERIFY(reader.hasError());
+}
+
+void tst_QXmlStream::readNextStartElement() const
+{
+ QLatin1String in("<?xml version=\"1.0\"?><A><!-- blah --><B><C/></B><B attr=\"value\"/>text</A>");
+ QXmlStreamReader reader(in);
+
+ QVERIFY(reader.readNextStartElement());
+ QVERIFY(reader.isStartElement() && reader.name() == "A");
+
+ int amountOfB = 0;
+ while (reader.readNextStartElement()) {
+ QVERIFY(reader.isStartElement() && reader.name() == "B");
+ ++amountOfB;
+ reader.skipCurrentElement();
+ }
+
+ QCOMPARE(amountOfB, 2);
+}
+
+void tst_QXmlStream::readElementText() const
+{
+ QFETCH(QXmlStreamReader::ReadElementTextBehaviour, behaviour);
+ QFETCH(QString, input);
+ QFETCH(QString, expected);
+
+ QXmlStreamReader reader(input);
+
+ QVERIFY(reader.readNextStartElement());
+ QCOMPARE(reader.readElementText(behaviour), expected);
+}
+
+void tst_QXmlStream::readElementText_data() const
+{
+ QTest::addColumn<QXmlStreamReader::ReadElementTextBehaviour>("behaviour");
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("expected");
+
+ QString validInput("<p>He was <em>never</em> going to admit<!-- TODO: rephrase --> his mistake.</p>");
+ QString invalidInput("<p>invalid...<p>");
+ QString invalidOutput("invalid...");
+
+ QTest::newRow("ErrorOnUnexpectedElement")
+ << QXmlStreamReader::ErrorOnUnexpectedElement
+ << validInput << QString("He was ");
+
+ QTest::newRow("IncludeChildElements")
+ << QXmlStreamReader::IncludeChildElements
+ << validInput << QString("He was never going to admit his mistake.");
+
+ QTest::newRow("SkipChildElements")
+ << QXmlStreamReader::SkipChildElements
+ << validInput << QString("He was going to admit his mistake.");
+
+ QTest::newRow("ErrorOnUnexpectedElement Invalid")
+ << QXmlStreamReader::ErrorOnUnexpectedElement
+ << invalidInput << invalidOutput;
+
+ QTest::newRow("IncludeChildElements Invalid")
+ << QXmlStreamReader::IncludeChildElements
+ << invalidInput << invalidOutput;
+
+ QTest::newRow("SkipChildElements Invalid")
+ << QXmlStreamReader::SkipChildElements
+ << invalidInput << invalidOutput;
+}
+
+void tst_QXmlStream::crashInUTF16Codec() const
+{
+ QEventLoop eventLoop;
+
+ QNetworkAccessManager networkManager;
+ QNetworkRequest request(QUrl::fromLocalFile(QLatin1String(SRCDIR "data/051reduced.xml")));
+ QNetworkReply *const reply = networkManager.get(request);
+ eventLoop.connect(reply, SIGNAL(finished()), SLOT(quit()));
+
+ QCOMPARE(eventLoop.exec(), 0);
+
+ QXmlStreamReader reader(reply);
+ while(!reader.atEnd())
+ {
+ reader.readNext();
+ continue;
+ }
+
+ QVERIFY(!reader.hasError());
+}
+
+/*
+ In addition to QTestLib's flags, one can specify "-c <filename>" and have that file output in its canonical form.
+*/
+int main(int argc, char *argv[])
+{
+ QCoreApplication app(argc, argv);
+
+ if (argc == 3 && QByteArray(argv[1]).startsWith("-c")) {
+ // output canonical only
+ bool error = false;
+ QByteArray canonical = makeCanonical(argv[2], "doc", error);
+ QTextStream myStdOut(stdout);
+ myStdOut << canonical << endl;
+ exit(0);
+ }
+
+ tst_QXmlStream tc;
+ return QTest::qExec(&tc, argc, argv);
+}
+
+void tst_QXmlStream::hasAttributeSignature() const
+{
+ /* These functions should be const so invoke all
+ * of them on a const object. */
+ const QXmlStreamAttributes atts;
+ atts.hasAttribute(QLatin1String("localName"));
+ atts.hasAttribute(QString::fromLatin1("localName"));
+ atts.hasAttribute(QString::fromLatin1("http://example.com/"), QLatin1String("localName"));
+
+ /* The input arguments should be const references, not mutable references
+ * so pass const references. */
+ const QLatin1String latin1StringLocalName(QLatin1String("localName"));
+ const QString qStringLocalname(QLatin1String("localName"));
+ const QString namespaceURI(QLatin1String("http://example.com/"));
+
+ /* QLatin1String overload. */
+ atts.hasAttribute(latin1StringLocalName);
+
+ /* QString overload. */
+ atts.hasAttribute(latin1StringLocalName);
+
+ /* namespace/local name overload. */
+ atts.hasAttribute(namespaceURI, qStringLocalname);
+}
+
+void tst_QXmlStream::hasAttribute() const
+{
+ QXmlStreamReader reader(QLatin1String("<e xmlns:p='http://example.com/2' xmlns='http://example.com/' "
+ "attr1='value' attr2='value2' p:attr3='value3' emptyAttr=''><noAttributes/></e>"));
+
+ QCOMPARE(reader.readNext(), QXmlStreamReader::StartDocument);
+ QCOMPARE(reader.readNext(), QXmlStreamReader::StartElement);
+ const QXmlStreamAttributes &atts = reader.attributes();
+
+ /* QLatin1String overload. */
+ QVERIFY(atts.hasAttribute(QLatin1String("attr1")));
+ QVERIFY(atts.hasAttribute(QLatin1String("attr2")));
+ QVERIFY(atts.hasAttribute(QLatin1String("p:attr3")));
+ QVERIFY(atts.hasAttribute(QLatin1String("emptyAttr")));
+ QVERIFY(!atts.hasAttribute(QLatin1String("DOESNOTEXIST")));
+
+ /* Test with an empty & null namespaces. */
+ QVERIFY(atts.hasAttribute(QString(), QLatin1String("attr2"))); /* A null string. */
+ QVERIFY(atts.hasAttribute(QLatin1String(""), QLatin1String("attr2"))); /* An empty string. */
+
+ /* QString overload. */
+ QVERIFY(atts.hasAttribute(QString::fromLatin1("attr1")));
+ QVERIFY(atts.hasAttribute(QString::fromLatin1("attr2")));
+ QVERIFY(atts.hasAttribute(QString::fromLatin1("p:attr3")));
+ QVERIFY(atts.hasAttribute(QString::fromLatin1("emptyAttr")));
+ QVERIFY(!atts.hasAttribute(QString::fromLatin1("DOESNOTEXIST")));
+
+ /* namespace/local name overload. */
+ QVERIFY(atts.hasAttribute(QString(), QString::fromLatin1("attr1")));
+ /* Attributes do not pick up the default namespace. */
+ QVERIFY(!atts.hasAttribute(QLatin1String("http://example.com/"), QString::fromLatin1("attr1")));
+ QVERIFY(atts.hasAttribute(QLatin1String("http://example.com/2"), QString::fromLatin1("attr3")));
+ QVERIFY(atts.hasAttribute(QString(), QString::fromLatin1("emptyAttr")));
+ QVERIFY(!atts.hasAttribute(QLatin1String("http://example.com/2"), QString::fromLatin1("DOESNOTEXIST")));
+ QVERIFY(!atts.hasAttribute(QLatin1String("WRONG_NAMESPACE"), QString::fromLatin1("attr3")));
+
+ /* Invoke on an QXmlStreamAttributes that has no attributes at all. */
+ QCOMPARE(reader.readNext(), QXmlStreamReader::StartElement);
+
+ const QXmlStreamAttributes &atts2 = reader.attributes();
+ QVERIFY(atts2.isEmpty());
+
+ /* QLatin1String overload. */
+ QVERIFY(!atts.hasAttribute(QLatin1String("arbitraryName")));
+
+ /* QString overload. */
+ QVERIFY(!atts.hasAttribute(QString::fromLatin1("arbitraryName")));
+
+ /* namespace/local name overload. */
+ QVERIFY(!atts.hasAttribute(QLatin1String("http://example.com/"), QString::fromLatin1("arbitraryName")));
+
+ while(!reader.atEnd())
+ reader.readNext();
+
+ QVERIFY(!reader.hasError());
+}
+
+
+void tst_QXmlStream::writeWithCodec() const
+{
+ QByteArray outarray;
+ QXmlStreamWriter writer(&outarray);
+ writer.setAutoFormatting(true);
+
+ QTextCodec *codec = QTextCodec::codecForName("ISO 8859-15");
+ QVERIFY(codec);
+ writer.setCodec(codec);
+
+ const char *latin2 = "hé hé";
+ const QString string = codec->toUnicode(latin2);
+
+
+ writer.writeStartDocument("1.0");
+
+ writer.writeTextElement("foo", string);
+ writer.writeEndElement();
+ writer.writeEndDocument();
+
+ QVERIFY(outarray.contains(latin2));
+ QVERIFY(outarray.contains(codec->name()));
+}
+
+void tst_QXmlStream::writeWithUtf8Codec() const
+{
+ QByteArray outarray;
+ QXmlStreamWriter writer(&outarray);
+
+ QTextCodec *codec = QTextCodec::codecForMib(106); // utf-8
+ QVERIFY(codec);
+ writer.setCodec(codec);
+
+ writer.writeStartDocument("1.0");
+ static const char begin[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
+ QVERIFY(outarray.startsWith(begin));
+}
+
+void tst_QXmlStream::writeWithStandalone() const
+{
+ {
+ QByteArray outarray;
+ QXmlStreamWriter writer(&outarray);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument("1.0", true);
+ writer.writeEndDocument();
+ const char *ref = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n";
+ QCOMPARE(outarray.constData(), ref);
+ }
+ {
+ QByteArray outarray;
+ QXmlStreamWriter writer(&outarray);
+ writer.setAutoFormatting(true);
+ writer.writeStartDocument("1.0", false);
+ writer.writeEndDocument();
+ const char *ref = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n";
+ QCOMPARE(outarray.constData(), ref);
+ }
+}
+
+void tst_QXmlStream::entitiesAndWhitespace_1() const
+{
+ QXmlStreamReader reader(QLatin1String("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\"><test>&extEnt;</test>"));
+
+ int entityCount = 0;
+ int characterCount = 0;
+ while(!reader.atEnd())
+ {
+ QXmlStreamReader::TokenType token = reader.readNext();
+ switch(token)
+ {
+ case QXmlStreamReader::Characters:
+ characterCount++;
+ break;
+ case QXmlStreamReader::EntityReference:
+ entityCount++;
+ break;
+ default:
+ ;
+ }
+ }
+
+ QCOMPARE(entityCount, 1);
+ QCOMPARE(characterCount, 0);
+ QVERIFY(!reader.hasError());
+}
+
+void tst_QXmlStream::entitiesAndWhitespace_2() const
+{
+ QXmlStreamReader reader(QLatin1String("<test>&extEnt;</test>"));
+
+ int entityCount = 0;
+ int characterCount = 0;
+ while(!reader.atEnd())
+ {
+ QXmlStreamReader::TokenType token = reader.readNext();
+ switch(token)
+ {
+ case QXmlStreamReader::Characters:
+ characterCount++;
+ break;
+ case QXmlStreamReader::EntityReference:
+ entityCount++;
+ break;
+ default:
+ ;
+ }
+ }
+
+ QCOMPARE(entityCount, 0);
+ QCOMPARE(characterCount, 0);
+ QVERIFY(reader.hasError());
+}
+
+void tst_QXmlStream::garbageInXMLPrologDefaultCodec() const
+{
+ QBuffer out;
+ QVERIFY(out.open(QIODevice::ReadWrite));
+
+ QXmlStreamWriter writer (&out);
+ writer.writeStartDocument();
+ writer.writeEmptyElement("Foo");
+ writer.writeEndDocument();
+
+ QCOMPARE(out.data(), QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Foo/>\n"));
+}
+
+void tst_QXmlStream::garbageInXMLPrologUTF8Explicitly() const
+{
+ QBuffer out;
+ QVERIFY(out.open(QIODevice::ReadWrite));
+
+ QXmlStreamWriter writer (&out);
+ writer.setCodec("UTF-8");
+ writer.writeStartDocument();
+ writer.writeEmptyElement("Foo");
+ writer.writeEndDocument();
+
+ QCOMPARE(out.data(), QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Foo/>\n"));
+}
+
+void tst_QXmlStream::clear() const // task 228768
+{
+ QString xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><body></body>";
+ QXmlStreamReader reader;
+
+ reader.addData(xml);
+ while (!reader.atEnd()) {
+ reader.readNext();
+ }
+ QCOMPARE(reader.tokenType(), QXmlStreamReader::EndDocument);
+
+ reader.clear();
+ reader.addData(xml);
+ while (!reader.atEnd()) {
+ reader.readNext();
+ }
+ QCOMPARE(reader.tokenType(), QXmlStreamReader::EndDocument);
+
+
+ // now we stop in the middle to check whether clear really works
+ reader.clear();
+ reader.addData(xml);
+ reader.readNext();
+ reader.readNext();
+ QCOMPARE(reader.tokenType(), QXmlStreamReader::StartElement);
+
+ // and here the final read
+ reader.clear();
+ reader.addData(xml);
+ while (!reader.atEnd()) {
+ reader.readNext();
+ }
+ QCOMPARE(reader.tokenType(), QXmlStreamReader::EndDocument);
+}
+
+void tst_QXmlStream::checkCommentIndentation_data() const
+{
+
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("expectedOutput");
+
+ QString simpleInput = "<a><!-- bla --></a>";
+ QString simpleOutput = "<?xml version=\"1.0\"?>\n"
+ "<a>\n"
+ " <!-- bla -->\n"
+ "</a>\n";
+ QTest::newRow("simple-comment") << simpleInput << simpleOutput;
+
+ QString advancedInput = "<a><!-- bla --><!-- bla --><b><!-- bla --><c><!-- bla --></c><!-- bla --></b></a>";
+ QString advancedOutput = "<?xml version=\"1.0\"?>\n"
+ "<a>\n"
+ " <!-- bla -->\n"
+ " <!-- bla -->\n"
+ " <b>\n"
+ " <!-- bla -->\n"
+ " <c>\n"
+ " <!-- bla -->\n"
+ " </c>\n"
+ " <!-- bla -->\n"
+ " </b>\n"
+ "</a>\n";
+ QTest::newRow("advanced-comment") << advancedInput << advancedOutput;
+}
+
+void tst_QXmlStream::checkCommentIndentation() const // task 256468
+{
+ QFETCH(QString, input);
+ QFETCH(QString, expectedOutput);
+ QString output;
+ QXmlStreamReader reader(input);
+ QXmlStreamWriter writer(&output);
+ writer.setAutoFormatting(true);
+ writer.setAutoFormattingIndent(3);
+
+ while (!reader.atEnd()) {
+ reader.readNext();
+ if (reader.error()) {
+ QFAIL("error reading XML input");
+ } else {
+ writer.writeCurrentToken(reader);
+ }
+ }
+ QCOMPARE(output, expectedOutput);
+}
+
+void tst_QXmlStream::qtbug9196_crash() const
+{
+ // the following input used to produce a crash in the stream reader
+ 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>"
+ "<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>");
+ QXmlStreamReader xml(ba);
+ while (!xml.atEnd()) {
+ xml.readNext();
+ }
+}
+
+class FakeBuffer : public QBuffer
+{
+protected:
+ qint64 writeData(const char *c, qint64 i)
+ {
+ qint64 ai = qMin(m_capacity, i);
+ m_capacity -= ai;
+ return ai ? QBuffer::writeData(c, ai) : 0;
+ }
+public:
+ void setCapacity(int capacity) { m_capacity = capacity; }
+private:
+ qint64 m_capacity;
+};
+
+void tst_QXmlStream::hasError() const
+{
+ {
+ FakeBuffer fb;
+ QVERIFY(fb.open(QBuffer::ReadWrite));
+ fb.setCapacity(1000);
+ QXmlStreamWriter writer(&fb);
+ writer.writeStartDocument();
+ writer.writeEndDocument();
+ QVERIFY(!writer.hasError());
+ QCOMPARE(fb.data(), QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"));
+ }
+
+ {
+ // Failure caused by write(QString)
+ FakeBuffer fb;
+ QVERIFY(fb.open(QBuffer::ReadWrite));
+ fb.setCapacity(strlen("<?xml version=\""));
+ QXmlStreamWriter writer(&fb);
+ writer.writeStartDocument();
+ QVERIFY(writer.hasError());
+ QCOMPARE(fb.data(), QByteArray("<?xml version=\""));
+ }
+
+ {
+ // Failure caused by write(char *)
+ FakeBuffer fb;
+ QVERIFY(fb.open(QBuffer::ReadWrite));
+ fb.setCapacity(strlen("<?xml version=\"1.0"));
+ QXmlStreamWriter writer(&fb);
+ writer.writeStartDocument();
+ QVERIFY(writer.hasError());
+ QCOMPARE(fb.data(), QByteArray("<?xml version=\"1.0"));
+ }
+
+ {
+ // Failure caused by write(QStringRef)
+ FakeBuffer fb;
+ QVERIFY(fb.open(QBuffer::ReadWrite));
+ fb.setCapacity(strlen("<?xml version=\"1.0\" encoding=\"UTF-8\"?><test xmlns:"));
+ QXmlStreamWriter writer(&fb);
+ writer.writeStartDocument();
+ writer.writeStartElement("test");
+ writer.writeNamespace("http://foo.bar", "foo");
+ QVERIFY(writer.hasError());
+ QCOMPARE(fb.data(), QByteArray("<?xml version=\"1.0\" encoding=\"UTF-8\"?><test xmlns:"));
+ }
+
+ {
+ // Refusal to write after 1st failure
+ FakeBuffer fb;
+ QVERIFY(fb.open(QBuffer::ReadWrite));
+ fb.setCapacity(10);
+ QXmlStreamWriter writer(&fb);
+ writer.writeStartDocument();
+ QVERIFY(writer.hasError());
+ QCOMPARE(fb.data(), QByteArray("<?xml vers"));
+ fb.setCapacity(1000);
+ writer.writeStartElement("test"); // literal & qstring
+ writer.writeNamespace("http://foo.bar", "foo"); // literal & qstringref
+ QVERIFY(writer.hasError());
+ QCOMPARE(fb.data(), QByteArray("<?xml vers"));
+ }
+
+}
+
+#include "tst_qxmlstream.moc"
+// vim: et:ts=4:sw=4:sts=4