Fix renamed and duplicated namespaces in QXmlStreamWriter
The XML stream writer previously added namespace declarations with the same URL as existing ones, but new names, and renamed the XML elements to use the new namespaces instead of the existing ones. [ChangeLog] Fix renamed and duplicated namespaces in QXmlStreamWriter. Pick-to: 6.5 Fixes: QTBUG-75456 Change-Id: I90706e067ac9991e9e6cd79ccb2373e4c6210b7b Done-With: Philip Allgaier <philip.allgaier@bpcompass.com> Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: Edward Welbourne <edward.welbourne@qt.io> (cherry picked from commit 13f673939d9dadbcc398aefa4c1449a8a21d2308) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
parent
c1f74f051b
commit
a92db9d5fa
@ -2879,6 +2879,11 @@ class QXmlStreamWriterPrivate : public QXmlStreamPrivateTagStack
|
|||||||
QXmlStreamWriter *q_ptr;
|
QXmlStreamWriter *q_ptr;
|
||||||
Q_DECLARE_PUBLIC(QXmlStreamWriter)
|
Q_DECLARE_PUBLIC(QXmlStreamWriter)
|
||||||
public:
|
public:
|
||||||
|
enum class StartElementOption {
|
||||||
|
KeepEverything = 0, // write out every attribute, namespace, &c.
|
||||||
|
OmitNamespaceDeclarations = 1,
|
||||||
|
};
|
||||||
|
|
||||||
QXmlStreamWriterPrivate(QXmlStreamWriter *q);
|
QXmlStreamWriterPrivate(QXmlStreamWriter *q);
|
||||||
~QXmlStreamWriterPrivate() {
|
~QXmlStreamWriterPrivate() {
|
||||||
if (deleteDevice)
|
if (deleteDevice)
|
||||||
@ -2888,7 +2893,8 @@ public:
|
|||||||
void write(QAnyStringView s);
|
void write(QAnyStringView s);
|
||||||
void writeEscaped(QAnyStringView, bool escapeWhitespace = false);
|
void writeEscaped(QAnyStringView, bool escapeWhitespace = false);
|
||||||
bool finishStartElement(bool contents = true);
|
bool finishStartElement(bool contents = true);
|
||||||
void writeStartElement(QAnyStringView namespaceUri, QAnyStringView name);
|
void writeStartElement(QAnyStringView namespaceUri, QAnyStringView name,
|
||||||
|
StartElementOption option = StartElementOption::KeepEverything);
|
||||||
QIODevice *device;
|
QIODevice *device;
|
||||||
QString *stringDevice;
|
QString *stringDevice;
|
||||||
uint deleteDevice :1;
|
uint deleteDevice :1;
|
||||||
@ -2903,6 +2909,7 @@ public:
|
|||||||
NamespaceDeclaration emptyNamespace;
|
NamespaceDeclaration emptyNamespace;
|
||||||
qsizetype lastNamespaceDeclaration;
|
qsizetype lastNamespaceDeclaration;
|
||||||
|
|
||||||
|
NamespaceDeclaration &addExtraNamespace(QAnyStringView namespaceUri, QAnyStringView prefix);
|
||||||
NamespaceDeclaration &findNamespace(QAnyStringView namespaceUri, bool writeDeclaration = false, bool noDefault = false);
|
NamespaceDeclaration &findNamespace(QAnyStringView namespaceUri, bool writeDeclaration = false, bool noDefault = false);
|
||||||
void writeNamespaceDeclaration(const NamespaceDeclaration &namespaceDeclaration);
|
void writeNamespaceDeclaration(const NamespaceDeclaration &namespaceDeclaration);
|
||||||
|
|
||||||
@ -3043,6 +3050,32 @@ bool QXmlStreamWriterPrivate::finishStartElement(bool contents)
|
|||||||
return hadSomethingWritten;
|
return hadSomethingWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QXmlStreamPrivateTagStack::NamespaceDeclaration &
|
||||||
|
QXmlStreamWriterPrivate::addExtraNamespace(QAnyStringView namespaceUri, QAnyStringView prefix)
|
||||||
|
{
|
||||||
|
const bool prefixIsXml = prefix == "xml"_L1;
|
||||||
|
const bool namespaceUriIsXml = namespaceUri == "http://www.w3.org/XML/1998/namespace"_L1;
|
||||||
|
if (prefixIsXml && !namespaceUriIsXml) {
|
||||||
|
qWarning("Reserved prefix 'xml' must not be bound to a different namespace name "
|
||||||
|
"than 'http://www.w3.org/XML/1998/namespace'");
|
||||||
|
} else if (!prefixIsXml && namespaceUriIsXml) {
|
||||||
|
const QString prefixString = prefix.toString();
|
||||||
|
qWarning("The prefix '%ls' must not be bound to namespace name "
|
||||||
|
"'http://www.w3.org/XML/1998/namespace' which 'xml' is already bound to",
|
||||||
|
qUtf16Printable(prefixString));
|
||||||
|
}
|
||||||
|
if (namespaceUri == "http://www.w3.org/2000/xmlns/"_L1) {
|
||||||
|
const QString prefixString = prefix.toString();
|
||||||
|
qWarning("The prefix '%ls' must not be bound to namespace name "
|
||||||
|
"'http://www.w3.org/2000/xmlns/'",
|
||||||
|
qUtf16Printable(prefixString));
|
||||||
|
}
|
||||||
|
auto &namespaceDeclaration = namespaceDeclarations.push();
|
||||||
|
namespaceDeclaration.prefix = addToStringStorage(prefix);
|
||||||
|
namespaceDeclaration.namespaceUri = addToStringStorage(namespaceUri);
|
||||||
|
return namespaceDeclaration;
|
||||||
|
}
|
||||||
|
|
||||||
QXmlStreamPrivateTagStack::NamespaceDeclaration &QXmlStreamWriterPrivate::findNamespace(QAnyStringView namespaceUri, bool writeDeclaration, bool noDefault)
|
QXmlStreamPrivateTagStack::NamespaceDeclaration &QXmlStreamWriterPrivate::findNamespace(QAnyStringView namespaceUri, bool writeDeclaration, bool noDefault)
|
||||||
{
|
{
|
||||||
for (NamespaceDeclaration &namespaceDeclaration : reversed(namespaceDeclarations)) {
|
for (NamespaceDeclaration &namespaceDeclaration : reversed(namespaceDeclarations)) {
|
||||||
@ -3621,11 +3654,7 @@ void QXmlStreamWriter::writeNamespace(QAnyStringView namespaceUri, QAnyStringVie
|
|||||||
if (prefix.isEmpty()) {
|
if (prefix.isEmpty()) {
|
||||||
d->findNamespace(namespaceUri, d->inStartElement);
|
d->findNamespace(namespaceUri, d->inStartElement);
|
||||||
} else {
|
} else {
|
||||||
Q_ASSERT(!((prefix == "xml"_L1) ^ (namespaceUri == "http://www.w3.org/XML/1998/namespace"_L1)));
|
auto &namespaceDeclaration = d->addExtraNamespace(namespaceUri, prefix);
|
||||||
Q_ASSERT(namespaceUri != "http://www.w3.org/2000/xmlns/"_L1);
|
|
||||||
QXmlStreamWriterPrivate::NamespaceDeclaration &namespaceDeclaration = d->namespaceDeclarations.push();
|
|
||||||
namespaceDeclaration.prefix = d->addToStringStorage(prefix);
|
|
||||||
namespaceDeclaration.namespaceUri = d->addToStringStorage(namespaceUri);
|
|
||||||
if (d->inStartElement)
|
if (d->inStartElement)
|
||||||
d->writeNamespaceDeclaration(namespaceDeclaration);
|
d->writeNamespaceDeclaration(namespaceDeclaration);
|
||||||
}
|
}
|
||||||
@ -3774,7 +3803,8 @@ void QXmlStreamWriter::writeStartElement(QAnyStringView namespaceUri, QAnyString
|
|||||||
d->writeStartElement(namespaceUri, name);
|
d->writeStartElement(namespaceUri, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void QXmlStreamWriterPrivate::writeStartElement(QAnyStringView namespaceUri, QAnyStringView name)
|
void QXmlStreamWriterPrivate::writeStartElement(QAnyStringView namespaceUri, QAnyStringView name,
|
||||||
|
StartElementOption option)
|
||||||
{
|
{
|
||||||
if (!finishStartElement(false) && autoFormatting)
|
if (!finishStartElement(false) && autoFormatting)
|
||||||
indent(tagStack.size());
|
indent(tagStack.size());
|
||||||
@ -3790,8 +3820,10 @@ void QXmlStreamWriterPrivate::writeStartElement(QAnyStringView namespaceUri, QAn
|
|||||||
write(tag.name);
|
write(tag.name);
|
||||||
inStartElement = lastWasStartElement = true;
|
inStartElement = lastWasStartElement = true;
|
||||||
|
|
||||||
for (qsizetype i = lastNamespaceDeclaration; i < namespaceDeclarations.size(); ++i)
|
if (option != StartElementOption::OmitNamespaceDeclarations) {
|
||||||
writeNamespaceDeclaration(namespaceDeclarations[i]);
|
for (qsizetype i = lastNamespaceDeclaration; i < namespaceDeclarations.size(); ++i)
|
||||||
|
writeNamespaceDeclaration(namespaceDeclarations[i]);
|
||||||
|
}
|
||||||
tag.namespaceDeclarationsSize = lastNamespaceDeclaration;
|
tag.namespaceDeclarationsSize = lastNamespaceDeclaration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3805,6 +3837,7 @@ void QXmlStreamWriterPrivate::writeStartElement(QAnyStringView namespaceUri, QAn
|
|||||||
*/
|
*/
|
||||||
void QXmlStreamWriter::writeCurrentToken(const QXmlStreamReader &reader)
|
void QXmlStreamWriter::writeCurrentToken(const QXmlStreamReader &reader)
|
||||||
{
|
{
|
||||||
|
Q_D(QXmlStreamWriter);
|
||||||
switch (reader.tokenType()) {
|
switch (reader.tokenType()) {
|
||||||
case QXmlStreamReader::NoToken:
|
case QXmlStreamReader::NoToken:
|
||||||
break;
|
break;
|
||||||
@ -3815,12 +3848,19 @@ void QXmlStreamWriter::writeCurrentToken(const QXmlStreamReader &reader)
|
|||||||
writeEndDocument();
|
writeEndDocument();
|
||||||
break;
|
break;
|
||||||
case QXmlStreamReader::StartElement: {
|
case QXmlStreamReader::StartElement: {
|
||||||
writeStartElement(reader.namespaceUri(), reader.name());
|
// Namespaces must be added before writeStartElement is called so new prefixes are found
|
||||||
const QXmlStreamNamespaceDeclarations decls = reader.namespaceDeclarations();
|
QList<QXmlStreamPrivateTagStack::NamespaceDeclaration> extraNamespaces;
|
||||||
for (const auto &namespaceDeclaration : decls) {
|
for (const auto &namespaceDeclaration : reader.namespaceDeclarations()) {
|
||||||
writeNamespace(namespaceDeclaration.namespaceUri(),
|
auto &extraNamespace = d->addExtraNamespace(namespaceDeclaration.namespaceUri(),
|
||||||
namespaceDeclaration.prefix());
|
namespaceDeclaration.prefix());
|
||||||
|
extraNamespaces.append(extraNamespace);
|
||||||
}
|
}
|
||||||
|
d->writeStartElement(
|
||||||
|
reader.namespaceUri(), reader.name(),
|
||||||
|
QXmlStreamWriterPrivate::StartElementOption::OmitNamespaceDeclarations);
|
||||||
|
// Namespace declarations are written afterwards
|
||||||
|
for (const auto &extraNamespace : std::as_const(extraNamespaces))
|
||||||
|
d->writeNamespaceDeclaration(extraNamespace);
|
||||||
writeAttributes(reader.attributes());
|
writeAttributes(reader.attributes());
|
||||||
} break;
|
} break;
|
||||||
case QXmlStreamReader::EndElement:
|
case QXmlStreamReader::EndElement:
|
||||||
|
@ -1778,6 +1778,22 @@ void tst_QXmlStream::roundTrip_data() const
|
|||||||
"<child xmlns:unknown=\"http://mydomain\">Text</child>"
|
"<child xmlns:unknown=\"http://mydomain\">Text</child>"
|
||||||
"</father>"
|
"</father>"
|
||||||
"</root>\n";
|
"</root>\n";
|
||||||
|
|
||||||
|
// When a namespace is introduced by an attribute of an element,
|
||||||
|
// that element can exercise the namespace in its tag.
|
||||||
|
// This used (QTBUG-75456) to lead to the namespace definition
|
||||||
|
// being wrongly duplicated, with a new name.
|
||||||
|
QTest::newRow("QTBUG-75456") <<
|
||||||
|
"<?xml version=\"1.0\"?>"
|
||||||
|
"<abc:root xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:abc=\"ns1\">"
|
||||||
|
"<abc:parent>"
|
||||||
|
"<abc:child xmlns:unknown=\"http://mydomain\">Text</abc:child>"
|
||||||
|
"</abc:parent>"
|
||||||
|
"<def:parent xmlns:def=\"ns2\" id=\"test\">"
|
||||||
|
"<def:child id=\"Timmy\">More text</def:child>"
|
||||||
|
"<def:child id=\"Jimmy\">Even more text</def:child>"
|
||||||
|
"</def:parent>"
|
||||||
|
"</abc:root>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
void tst_QXmlStream::entityExpansionLimit() const
|
void tst_QXmlStream::entityExpansionLimit() const
|
||||||
|
Loading…
x
Reference in New Issue
Block a user