summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/xml/dom/qdom.cpp97
-rw-r--r--tests/auto/xml/dom/qdom/tst_qdom.cpp9
2 files changed, 75 insertions, 31 deletions
diff --git a/src/xml/dom/qdom.cpp b/src/xml/dom/qdom.cpp
index e8e2e5902a..557f5c10b4 100644
--- a/src/xml/dom/qdom.cpp
+++ b/src/xml/dom/qdom.cpp
@@ -23,6 +23,7 @@
#include <qxmlstream.h>
#include <private/qduplicatetracker_p.h>
#include <private/qstringiterator_p.h>
+#include <qvarlengtharray.h>
#include <stdio.h>
#include <limits>
@@ -4026,32 +4027,82 @@ void QDomElementPrivate::save(QTextStream& s, int depth, int indent) const
/* Write out attributes. */
if (!m_attr->map.isEmpty()) {
+ /*
+ * To ensure that we always output attributes in a consistent
+ * order, sort the attributes before writing them into the
+ * stream. (Note that the order may be different than the one
+ * that e.g. we've read from a file, or the program order in
+ * which these attributes have been populated. We just want to
+ * guarantee reproducibile outputs.)
+ */
+ struct SavedAttribute {
+ QString prefix;
+ QString name;
+ QString encodedValue;
+ };
+
+ /* Gather all the attributes to save. */
+ QVarLengthArray<SavedAttribute, 8> attributesToSave;
+ attributesToSave.reserve(m_attr->map.size());
+
QDuplicateTracker<QString> outputtedPrefixes;
- auto it = m_attr->map.constBegin();
- for (; it != m_attr->map.constEnd(); ++it) {
- s << ' ';
- if (it.value()->namespaceURI.isNull()) {
- s << it.value()->name << "=\"" << encodeText(it.value()->value, true, true) << '\"';
- } else {
- s << it.value()->prefix << ':' << it.value()->name << "=\"" << encodeText(it.value()->value, true, true) << '\"';
- /* This is a fix for 138243, as good as it gets.
- *
- * QDomElementPrivate::save() output a namespace declaration if
- * the element is in a namespace, no matter what. This function do as well, meaning
- * that we get two identical namespace declaration if we don't have the if-
- * statement below.
- *
- * This doesn't work when the parent element has the same prefix as us but
- * a different namespace. However, this can only occur by the user modifying the element,
- * and we don't do fixups by that anyway, and hence it's the user responsibility to not
- * arrive in those situations. */
- if ((!it.value()->ownerNode ||
- it.value()->ownerNode->prefix != it.value()->prefix) &&
- !outputtedPrefixes.hasSeen(it.value()->prefix)) {
- s << " xmlns:" << it.value()->prefix << "=\"" << encodeText(it.value()->namespaceURI, true, true) << '\"';
- }
+ for (const auto &[key, value] : std::as_const(m_attr->map).asKeyValueRange()) {
+ Q_UNUSED(key); /* We extract the attribute name from the value. */
+ bool mayNeedXmlNS = false;
+
+ SavedAttribute attr;
+ attr.name = value->name;
+ attr.encodedValue = encodeText(value->value, true, true);
+ if (!value->namespaceURI.isNull()) {
+ attr.prefix = value->prefix;
+ mayNeedXmlNS = true;
+ }
+
+ attributesToSave.push_back(std::move(attr));
+
+ /*
+ * This is a fix for 138243, as good as it gets.
+ *
+ * QDomElementPrivate::save() output a namespace
+ * declaration if the element is in a namespace, no matter
+ * what. This function do as well, meaning that we get two
+ * identical namespace declaration if we don't have the if-
+ * statement below.
+ *
+ * This doesn't work when the parent element has the same
+ * prefix as us but a different namespace. However, this
+ * can only occur by the user modifying the element, and we
+ * don't do fixups by that anyway, and hence it's the user
+ * responsibility to avoid those situations.
+ */
+
+ if (mayNeedXmlNS
+ && ((!value->ownerNode || value->ownerNode->prefix != value->prefix)
+ && !outputtedPrefixes.hasSeen(value->prefix)))
+ {
+ attr.prefix = QStringLiteral("xmlns");
+ attr.name = value->prefix;
+ attr.encodedValue = encodeText(value->namespaceURI, true, true);
+ attributesToSave.push_back(std::move(attr));
}
}
+
+ /* Sort the attributes by prefix and name. */
+ const auto savedAttributeComparator = [](const SavedAttribute &lhs, const SavedAttribute &rhs)
+ {
+ const int cmp = QString::compare(lhs.prefix, rhs.prefix);
+ return (cmp < 0) || ((cmp == 0) && (lhs.name < rhs.name));
+ };
+
+ std::sort(attributesToSave.begin(), attributesToSave.end(), savedAttributeComparator);
+
+ /* Actually stream the sorted attributes. */
+ for (const auto &attr : attributesToSave) {
+ s << ' ';
+ if (!attr.prefix.isEmpty())
+ s << attr.prefix << ':';
+ s << attr.name << "=\"" << attr.encodedValue << '\"';
+ }
}
if (last) {
diff --git a/tests/auto/xml/dom/qdom/tst_qdom.cpp b/tests/auto/xml/dom/qdom/tst_qdom.cpp
index 9ea09215a8..cfc869949f 100644
--- a/tests/auto/xml/dom/qdom/tst_qdom.cpp
+++ b/tests/auto/xml/dom/qdom/tst_qdom.cpp
@@ -2011,15 +2011,8 @@ void tst_QDom::doubleNamespaceDeclarations() const
QXmlStreamReader streamReader(&file);
QVERIFY(doc.setContent(&streamReader, QDomDocument::ParseOption::UseNamespaceProcessing));
- // tst_QDom relies on a specific QHash ordering, see QTBUG-25071
QString docAsString = doc.toString(0);
- QVERIFY(docAsString == QString::fromLatin1("<a>\n<b p:c=\"\" xmlns:p=\"NS\" p:d=\"\"/>\n</a>\n") ||
- docAsString == QString::fromLatin1("<a>\n<b p:c=\"\" p:d=\"\" xmlns:p=\"NS\"/>\n</a>\n") ||
- docAsString == QString::fromLatin1("<a>\n<b p:d=\"\" p:c=\"\" xmlns:p=\"NS\"/>\n</a>\n") ||
- docAsString == QString::fromLatin1("<a>\n<b p:d=\"\" xmlns:p=\"NS\" p:c=\"\"/>\n</a>\n") ||
- docAsString == QString::fromLatin1("<a>\n<b xmlns:p=\"NS\" p:c=\"\" p:d=\"\"/>\n</a>\n") ||
- docAsString == QString::fromLatin1("<a>\n<b xmlns:p=\"NS\" p:d=\"\" p:c=\"\"/>\n</a>\n")
- );
+ QCOMPARE(docAsString, "<a>\n<b p:c=\"\" p:d=\"\" xmlns:p=\"NS\"/>\n</a>\n");
}
void tst_QDom::setContentQXmlReaderOverload() const