Improve QDomDocument::setContent() API

Added new setContent() overloads, that:

 - take parameter of new ParseOptions enum type for specifying the parse
   options that can be used for enabling namepsace processing and, in
   future, whitespace-only text nodes, etc.

 - use ParseResult for returning the information about error message,
   line and coulmn number, instead of three parameters for each.

 - use QAnyStringView for a QString input data.

To avoid ambiguities when calling setContent() with one argument,
removed the default argument for errorString from all the overloads.

[ChangeLog][QtXml][QDomDocument] Added new setContent() overloads that
allow specifying different parse options through ParseOptions flags.
These overloads use a new ParseResult struct for returning the
information about an error, and QAnyStringView for passing string input.

[ChangeLog][QtXml][QDomDocument][Potentially Source-Incompatible Changes]
setContent() overloads that take only one argument now return
ParseResult instead of a bool. ParseResult explicitly converts to bool,
so the expressions calling setContent() with one argument will continue
compiling, if they are contextually convertible to bool. If an implicit
convertion is required (e.g. bool b = doc.setConetnt(data)), the result
needs to be explicitly converted to bool first
(e.g. bool b = bool(doc.setConetnt(data)).

Task-number: QTBUG-104507
Change-Id: If6a78f8c9b1458f0e3ae719bfd3703a0b965449c
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Sona Kurazyan 2022-06-24 15:57:26 +02:00
parent b65e37b744
commit 8cc389068b
6 changed files with 360 additions and 67 deletions

View File

@ -5609,8 +5609,8 @@ void QDomDocumentPrivate::clear()
QDomNodePrivate::clear();
}
bool QDomDocumentPrivate::setContent(QXmlStreamReader *reader, bool namespaceProcessing,
QString *errorMsg, int *errorLine, int *errorColumn)
QDomDocument::ParseResult QDomDocumentPrivate::setContent(QXmlStreamReader *reader,
QDomDocument::ParseOptions options)
{
clear();
impl = new QDomImplementationPrivate;
@ -5618,23 +5618,18 @@ bool QDomDocumentPrivate::setContent(QXmlStreamReader *reader, bool namespacePro
type->ref.deref();
if (!reader) {
qWarning("Failed to set content, XML reader is not initialized");
return false;
const auto error = u"Failed to set content, XML reader is not initialized"_s;
qWarning("%s", qPrintable(error));
return { error };
}
QDomParser domParser(this, reader, namespaceProcessing);
QDomParser domParser(this, reader, options);
if (!domParser.parse()) {
if (errorMsg)
*errorMsg = std::get<0>(domParser.errorInfo());
if (errorLine)
*errorLine = std::get<1>(domParser.errorInfo());
if (errorColumn)
*errorColumn = std::get<2>(domParser.errorInfo());
return false;
const auto info = domParser.errorInfo();
return { std::get<0>(info), std::get<1>(info), std::get<2>(info) };
}
return true;
return {};
}
QDomNodePrivate* QDomDocumentPrivate::cloneNode(bool deep)
@ -6046,14 +6041,12 @@ QDomDocument::~QDomDocument()
Since \a text is already a Unicode string, no encoding detection
is done.
*/
bool QDomDocument::setContent(const QString& text, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
bool QDomDocument::setContent(const QString& text, bool namespaceProcessing,
QString *errorMsg, int *errorLine, int *errorColumn)
{
if (!impl)
impl = new QDomDocumentPrivate();
QXmlStreamReader streamReader(text);
streamReader.setNamespaceProcessing(namespaceProcessing);
return IMPL->setContent(&streamReader, namespaceProcessing, errorMsg, errorLine, errorColumn);
QXmlStreamReader reader(text);
reader.setNamespaceProcessing(namespaceProcessing);
return setContent(&reader, namespaceProcessing, errorMsg, errorLine, errorColumn);
}
/*!
@ -6085,6 +6078,7 @@ bool QDomDocument::setContent(const QString& text, bool namespaceProcessing, QSt
QDomNode::prefix(), QDomNode::localName() and
QDomNode::namespaceURI() return an empty string.
//! [entity-refs]
Entity references are handled as follows:
\list
\li References to internal general entities and character entities occurring in the
@ -6099,18 +6093,36 @@ bool QDomDocument::setContent(const QString& text, bool namespaceProcessing, QSt
occurs outside of the content is replaced with an empty string.
\li Any unparsed entity reference is replaced with an empty string.
\endlist
//! [entity-refs]
\sa QDomNode::namespaceURI(), QDomNode::localName(),
QDomNode::prefix(), QString::isNull(), QString::isEmpty()
*/
bool QDomDocument::setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
bool QDomDocument::setContent(const QByteArray &data, bool namespaceProcessing,
QString *errorMsg, int *errorLine, int *errorColumn)
{
if (!impl)
impl = new QDomDocumentPrivate();
QXmlStreamReader reader(data);
reader.setNamespaceProcessing(namespaceProcessing);
return setContent(&reader, namespaceProcessing, errorMsg, errorLine, errorColumn);
}
QXmlStreamReader streamReader(data);
streamReader.setNamespaceProcessing(namespaceProcessing);
return IMPL->setContent(&streamReader, namespaceProcessing, errorMsg, errorLine, errorColumn);
static inline QDomDocument::ParseOptions toParseOptions(bool namespaceProcessing)
{
return namespaceProcessing ? QDomDocument::ParseOption::UseNamespaceProcessing
: QDomDocument::ParseOption::Default;
}
static inline void unpackParseResult(const QDomDocument::ParseResult &parseResult,
QString *errorMsg, int *errorLine, int *errorColumn)
{
if (!parseResult) {
if (errorMsg)
*errorMsg = parseResult.errorMessage;
if (errorLine)
*errorLine = static_cast<int>(parseResult.errorLine);
if (errorColumn)
*errorColumn = static_cast<int>(parseResult.errorLine);
}
}
/*!
@ -6124,25 +6136,12 @@ bool QDomDocument::setContent(const QByteArray &data, bool namespaceProcessing,
This will change in Qt 7, which will no longer open \a dev. Applications
should therefore open the device themselves before calling setContent.
*/
bool QDomDocument::setContent(QIODevice* dev, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
bool QDomDocument::setContent(QIODevice* dev, bool namespaceProcessing,
QString *errorMsg, int *errorLine, int *errorColumn)
{
if (!impl)
impl = new QDomDocumentPrivate();
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
if (!dev->isOpen()) {
qWarning("QDomDocument called with unopened QIODevice. "
"This will not be supported in future Qt versions.");
if (!dev->open(QIODevice::ReadOnly)) {
qWarning("QDomDocument::setContent: Failed to open device.");
return false;
}
}
#endif
QXmlStreamReader streamReader(dev);
streamReader.setNamespaceProcessing(namespaceProcessing);
return IMPL->setContent(&streamReader, namespaceProcessing, errorMsg, errorLine, errorColumn);
ParseResult result = setContent(dev, toParseOptions(namespaceProcessing));
unpackParseResult(result, errorMsg, errorLine, errorColumn);
return bool(result);
}
/*!
@ -6207,12 +6206,159 @@ bool QDomDocument::setContent(QIODevice* dev, QString *errorMsg, int *errorLine,
\sa QXmlStreamReader
*/
bool QDomDocument::setContent(QXmlStreamReader *reader, bool namespaceProcessing, QString *errorMsg,
int *errorLine, int *errorColumn)
bool QDomDocument::setContent(QXmlStreamReader *reader, bool namespaceProcessing,
QString *errorMsg, int *errorLine, int *errorColumn)
{
ParseResult result = setContent(reader, toParseOptions(namespaceProcessing));
unpackParseResult(result, errorMsg, errorLine, errorColumn);
return bool(result);
}
/*!
\enum QDomDocument::ParseOption
\since 6.5
This enum describes the possible options that can be used when
parsing an XML document using the setContent() method.
\value Default No parse options are set.
\value UseNamespaceProcessing Namespace processing is enabled.
\sa setContent()
*/
/*!
\struct QDomDocument::ParseResult
\since 6.5
\inmodule QtXml
\ingroup xml-tools
\brief The struct is used to store the result of QDomDocument::setContent().
The QDomDocument::ParseResult struct is used for storing the result of
QDomDocument::setContent(). If an error is found while parsing an XML
document, the message, line and column number of an error are stored in
\c ParseResult.
\sa QDomDocument::setContent()
*/
/*!
\variable QDomDocument::ParseResult::errorMessage
The field contains the text message of an error found by
QDomDocument::setContent() while parsing an XML document.
\sa QDomDocument::setContent()
*/
/*!
\variable QDomDocument::ParseResult::errorLine
The field contains the line number of an error found by
QDomDocument::setContent() while parsing an XML document.
\sa QDomDocument::setContent()
*/
/*!
\variable QDomDocument::ParseResult::errorColumn
The field contains the column number of an error found by
QDomDocument::setContent() while parsing an XML document.
\sa QDomDocument::setContent()
*/
/*!
\fn QDomDocument::ParseResult::operator bool() const
Returns \c true if an error is found by QDomDocument::setContent();
otherwise returns \c false.
\sa QDomDocument::setContent()
*/
/*!
\fn ParseResult QDomDocument::setContent(const QByteArray &data, ParseOptions options)
\fn ParseResult QDomDocument::setContent(QAnyStringView text, ParseOptions options)
\fn ParseResult QDomDocument::setContent(QIODevice *device, ParseOptions options)
\fn ParseResult QDomDocument::setContent(QXmlStreamReader *reader, ParseOptions options)
\since 6.5
This function parses the XML document from the byte array \a
data, string view \a text, IO \a device, or stream \a reader
and sets it as the content of the document. It tries to
detect the encoding of the document, in accordance with the
XML specification. Returns the result of parsing in ParseResult,
which explicitly converts to \c bool.
You can use the \a options parameter to specify different parsing
options, for example, to enable namespace processing, etc.
By default, namespace processing is disabled. If it's disabled, the
parser does no namespace processing when it reads the XML file. The
functions QDomNode::prefix(), QDomNode::localName() and
QDomNode::namespaceURI() return an empty string.
If namespace processing is enabled via the parse \a options, the parser
recognizes namespaces in the XML file and sets the prefix name, local
name and namespace URI to appropriate values. The functions
QDomNode::prefix(), QDomNode::localName() and QDomNode::namespaceURI()
return a string for all elements and attributes and return an empty
string if the element or attribute has no prefix.
Text nodes consisting only of whitespace are stripped and won't
appear in the QDomDocument.
\include qdom.cpp entity-refs
\note The overload taking IO \a device will try to open it in read-only
mode if it is not already open. In that case, the caller is responsible
for calling close. This will change in Qt 7, which will no longer open
the IO \a device. Applications should therefore open the device themselves
before calling setContent().
\sa ParseResult, ParseOptions
*/
QDomDocument::ParseResult QDomDocument::setContentImpl(const QByteArray &data, ParseOptions options)
{
QXmlStreamReader reader(data);
reader.setNamespaceProcessing(options.testFlag(ParseOption::UseNamespaceProcessing));
return setContent(&reader, options);
}
QDomDocument::ParseResult QDomDocument::setContent(QAnyStringView data, ParseOptions options)
{
QXmlStreamReader reader(data);
reader.setNamespaceProcessing(options.testFlag(ParseOption::UseNamespaceProcessing));
return setContent(&reader, options);
}
QDomDocument::ParseResult QDomDocument::setContent(QIODevice *device, ParseOptions options)
{
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
if (!device->isOpen()) {
qWarning("QDomDocument called with unopened QIODevice. "
"This will not be supported in future Qt versions.");
if (!device->open(QIODevice::ReadOnly)) {
const auto error = u"QDomDocument::setContent: Failed to open device."_s;
qWarning("%s", qPrintable(error));
return { error };
}
}
#endif
QXmlStreamReader reader(device);
reader.setNamespaceProcessing(options.testFlag(ParseOption::UseNamespaceProcessing));
return setContent(&reader, options);
}
QDomDocument::ParseResult QDomDocument::setContent(QXmlStreamReader *reader, ParseOptions options)
{
if (!impl)
impl = new QDomDocumentPrivate();
return IMPL->setContent(reader, namespaceProcessing, errorMsg, errorLine, errorColumn);
return IMPL->setContent(reader, options);
}
/*!

View File

@ -265,6 +265,21 @@ private:
class Q_XML_EXPORT QDomDocument : public QDomNode
{
public:
enum class ParseOption {
Default = 0x00,
UseNamespaceProcessing = 0x01,
};
Q_DECLARE_FLAGS(ParseOptions, ParseOption)
struct ParseResult
{
QString errorMessage;
qsizetype errorLine = 0;
qsizetype errorColumn = 0;
explicit operator bool() const noexcept { return errorMessage.isEmpty(); }
};
QDomDocument();
explicit QDomDocument(const QString& name);
explicit QDomDocument(const QDomDocumentType& doctype);
@ -300,17 +315,26 @@ public:
bool setContent(const QByteArray &text, bool namespaceProcessing, QString *errorMsg = nullptr, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(const QString &text, bool namespaceProcessing, QString *errorMsg = nullptr, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(QIODevice *dev, bool namespaceProcessing, QString *errorMsg = nullptr, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(const QByteArray &text, QString *errorMsg = nullptr, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(const QString &text, QString *errorMsg = nullptr, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(QIODevice *dev, QString *errorMsg = nullptr, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(const QByteArray &text, QString *errorMsg, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(const QString &text, QString *errorMsg, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(QIODevice *dev, QString *errorMsg, int *errorLine = nullptr, int *errorColumn = nullptr);
bool setContent(QXmlStreamReader *reader, bool namespaceProcessing, QString *errorMsg = nullptr,
int *errorLine = nullptr, int *errorColumn = nullptr);
Q_WEAK_OVERLOAD
ParseResult setContent(const QByteArray &data, ParseOptions options = ParseOption::Default)
{ return setContentImpl(data, options); }
ParseResult setContent(QAnyStringView data, ParseOptions options = ParseOption::Default);
ParseResult setContent(QIODevice *device, ParseOptions options = ParseOption::Default);
ParseResult setContent(QXmlStreamReader *reader, ParseOptions options = ParseOption::Default);
// Qt extensions
QString toString(int = 1) const;
QByteArray toByteArray(int = 1) const;
private:
ParseResult setContentImpl(const QByteArray &data, ParseOptions options);
QDomDocument(QDomDocumentPrivate*);
friend class QDomNode;

View File

@ -425,8 +425,8 @@ public:
QDomDocumentPrivate(QDomDocumentPrivate *n, bool deep);
~QDomDocumentPrivate();
bool setContent(QXmlStreamReader *reader, bool namespaceProcessing, QString *errorMsg,
int *errorLine, int *errorColumn);
QDomDocument::ParseResult setContent(QXmlStreamReader *reader,
QDomDocument::ParseOptions options);
// Attributes
QDomDocumentTypePrivate *doctype() { return type.data(); }

View File

@ -22,13 +22,9 @@ using namespace Qt::StringLiterals;
*
**************************************************************/
QDomBuilder::QDomBuilder(QDomDocumentPrivate *d, QXmlStreamReader *r, bool namespaceProcessing)
: errorLine(0),
errorColumn(0),
doc(d),
node(d),
reader(r),
nsProcessing(namespaceProcessing)
QDomBuilder::QDomBuilder(QDomDocumentPrivate *d, QXmlStreamReader *r,
QDomDocument::ParseOptions options)
: errorLine(0), errorColumn(0), doc(d), node(d), reader(r), parseOptions(options)
{
Q_ASSERT(doc);
Q_ASSERT(reader);
@ -84,6 +80,8 @@ bool QDomBuilder::parseDTD(const QString &dtd)
bool QDomBuilder::startElement(const QString &nsURI, const QString &qName,
const QXmlStreamAttributes &atts)
{
const bool nsProcessing =
parseOptions.testFlag(QDomDocument::ParseOption::UseNamespaceProcessing);
QDomNodePrivate *n =
nsProcessing ? doc->createElementNS(nsURI, qName) : doc->createElement(qName);
if (!n)
@ -232,8 +230,9 @@ bool QDomBuilder::notationDecl(const QString &name, const QString &publicId,
*
**************************************************************/
QDomParser::QDomParser(QDomDocumentPrivate *d, QXmlStreamReader *r, bool namespaceProcessing)
: reader(r), domBuilder(d, r, namespaceProcessing)
QDomParser::QDomParser(QDomDocumentPrivate *d, QXmlStreamReader *r,
QDomDocument::ParseOptions options)
: reader(r), domBuilder(d, r, options)
{
}

View File

@ -4,6 +4,7 @@
#define QDOMHELPERS_P_H
#include <qcoreapplication.h>
#include <qdom.h>
#include <private/qglobal_p.h>
QT_BEGIN_NAMESPACE
@ -33,7 +34,7 @@ class QXmlStreamAttributes;
class QDomBuilder
{
public:
QDomBuilder(QDomDocumentPrivate *d, QXmlStreamReader *r, bool namespaceProcessing);
QDomBuilder(QDomDocumentPrivate *d, QXmlStreamReader *r, QDomDocument::ParseOptions options);
~QDomBuilder();
bool endDocument();
@ -68,7 +69,7 @@ private:
QDomNodePrivate *node;
QXmlStreamReader *reader;
QString entityName;
bool nsProcessing;
QDomDocument::ParseOptions parseOptions;
};
/**************************************************************
@ -81,7 +82,7 @@ class QDomParser
{
Q_DECLARE_TR_FUNCTIONS(QDomParser)
public:
QDomParser(QDomDocumentPrivate *d, QXmlStreamReader *r, bool namespaceProcessing);
QDomParser(QDomDocumentPrivate *d, QXmlStreamReader *r, QDomDocument::ParseOptions options);
bool parse();
QDomBuilder::ErrorInfo errorInfo() const;

View File

@ -30,6 +30,9 @@ private slots:
void namespacedAttributes() const;
void setContent_data();
void setContent();
void setContentOverloads();
void parseOptions();
void parseResult();
void toString_01_data();
void toString_01();
void toString_02_data();
@ -187,6 +190,126 @@ void tst_QDom::setContent()
QVERIFY( domDoc1.setContent( doc ) );
QVERIFY( domDoc2.setContent( eRes ) );
QVERIFY( compareDocuments( domDoc1, domDoc2 ) );
// Test overload taking a QAnyStringView
QDomDocument domFromStringView;
QStringView stringView(doc);
QVERIFY(domFromStringView.setContent(stringView));
QVERIFY(compareDocuments(domFromStringView, domDoc));
// Test overload taking a QByteArray
QDomDocument domFromByteArary;
QByteArray byteArray = doc.toUtf8();
QVERIFY(domFromByteArary.setContent(byteArray));
QVERIFY(compareDocuments(domFromByteArary, domDoc));
// Test overload taking a QIODevice
QDomDocument domFromIODevice;
QBuffer buffer(&byteArray);
QVERIFY(buffer.open(QIODevice::ReadOnly));
QVERIFY(domFromIODevice.setContent(&buffer));
QVERIFY(compareDocuments(domFromIODevice, domDoc));
}
void tst_QDom::setContentOverloads()
{
QDomDocument doc;
QString errorMessage;
QByteArray data("<a>test</a>");
QString text = QString::fromLatin1(data);
QXmlStreamReader reader(data);
QBuffer buffer(&data);
QVERIFY(buffer.open(QIODevice::ReadOnly));
QVERIFY(doc.setContent(data));
QVERIFY(doc.setContent(data.data()));
QVERIFY(doc.setContent(text));
QVERIFY(doc.setContent(&reader));
QVERIFY(doc.setContent(&buffer));
buffer.reset();
// With parse options
QVERIFY(doc.setContent(data, QDomDocument::ParseOption::Default));
QVERIFY(doc.setContent(data.data(), QDomDocument::ParseOption::Default));
QVERIFY(doc.setContent(text, QDomDocument::ParseOption::Default));
QVERIFY(doc.setContent(&reader, QDomDocument::ParseOption::Default));
QVERIFY(doc.setContent(&buffer, QDomDocument::ParseOption::Default));
buffer.reset();
// With output param
QVERIFY(doc.setContent(data, &errorMessage));
QVERIFY(doc.setContent(text, &errorMessage));
QVERIFY(doc.setContent(&buffer, &errorMessage));
buffer.reset();
// There's no setContent(QXmlStreamReader *, QString *, int *, int *) overload
// QVERIFY(doc.setContent(&reader, &errorMessage));
// With namespace processing param
QVERIFY(doc.setContent(data, false));
QVERIFY(doc.setContent(text, false));
QVERIFY(doc.setContent(&reader, false));
QVERIFY(doc.setContent(&buffer, false));
buffer.reset();
// With namespace processing and output params
QVERIFY(doc.setContent(data, false, &errorMessage));
QVERIFY(doc.setContent(text, false, &errorMessage));
QVERIFY(doc.setContent(&reader, false, &errorMessage));
QVERIFY(doc.setContent(&buffer, false, &errorMessage));
buffer.reset();
}
void tst_QDom::parseOptions()
{
QString input = u"<doc xmlns:ns='http://example.com/'>"
"<ns:nested/>"
"</doc>"_s;
{
QDomDocument document;
QVERIFY(document.setContent(input, QDomDocument::ParseOption::Default));
const QDomElement docElement = document.firstChildElement("doc");
QVERIFY(!docElement.isNull());
const QDomElement nestedElement = docElement.firstChildElement();
QCOMPARE(nestedElement.nodeName(), "ns:nested");
QVERIFY(!nestedElement.isNull());
QVERIFY(nestedElement.localName().isEmpty());
QVERIFY(nestedElement.prefix().isEmpty());
QVERIFY(nestedElement.namespaceURI().isEmpty());
}
{
QDomDocument document;
QVERIFY(document.setContent(input, QDomDocument::ParseOption::UseNamespaceProcessing));
const QDomElement docElement = document.firstChildElement("doc");
QVERIFY(!docElement.isNull());
const QDomElement nestedElement = docElement.firstChildElement("nested");
QVERIFY(!nestedElement.isNull());
QCOMPARE(nestedElement.nodeName(), "ns:nested");
QCOMPARE(nestedElement.localName(), "nested");
QCOMPARE(nestedElement.prefix(), "ns");
QCOMPARE(nestedElement.namespaceURI(), "http://example.com/");
}
}
void tst_QDom::parseResult()
{
QString input = u"<doc xmlns:b='http://example.com/'>"
"<b:element/>"_s;
QDomDocument document;
QDomDocument::ParseResult result = document.setContent(input);
QVERIFY(!result);
QVERIFY(!result.errorMessage.isEmpty());
QVERIFY(result.errorLine);
QVERIFY(result.errorColumn);
input.append("</doc>");
result = document.setContent(input);
QVERIFY(result);
QVERIFY(result.errorMessage.isEmpty());
QVERIFY(!result.errorLine);
QVERIFY(!result.errorColumn);
}
void tst_QDom::toString_01_data()
@ -1624,7 +1747,7 @@ void tst_QDom::checkLiveness() const
void tst_QDom::reportDuplicateAttributes() const
{
QDomDocument dd;
bool isSuccess = dd.setContent(QLatin1String("<test x=\"1\" x=\"2\"/>"));
bool isSuccess = bool(dd.setContent(QLatin1String("<test x=\"1\" x=\"2\"/>")));
QVERIFY2(!isSuccess, "Duplicate attributes are well-formedness errors, and should be reported as such.");
}
@ -1892,7 +2015,7 @@ void tst_QDom::setContentWhitespace() const
QDomDocument domDoc;
QCOMPARE(domDoc.setContent(doc), expectedValidity);
QCOMPARE(bool(domDoc.setContent(doc)), expectedValidity);
if(expectedValidity)
QCOMPARE(domDoc.documentElement().nodeName(), QString::fromLatin1("e"));