When switching from Qt 4.8 to Qt 5.x you may notice that each time you save XML document it produce random attribute order inside the file. It makes no problem for programaticaly reading the XML document because attributes are allowed to be stored in any order when XML is deserialized. It is a problem when you track changes of output XML files using GIT, SVN, etc. - it is not possible to tell if the data inside XML file changed or the attribute structure changed.
Is it possible to generate XML files in Qt 5.x the same way as in Qt 4.8?
I tried play with the hash seed, but it works fine only if you use one machine. If a file created on first machine open on second the same code doesn't produce the same order even if i set the hash seed to the same value. So, i just decided to find another solution.
I decided to use QXmlStreamWriter class as a wrapper. Each time i save QDomDocument i parse it and write through QXmlStreamWriter. This helps me convert an xml DOM to canonical form.
Here is code i used. Maybe someone will find it useful.
bool MyDomDocument::SaveCanonicalXML(QIODevice *file, int indent, QString &error) const
{
QXmlStreamWriter stream(file);
stream.setAutoFormatting(true);
stream.setAutoFormattingIndent(indent);
stream.writeStartDocument();
QDomNode root = documentElement();
while (not root.isNull())
{
SaveNodeCanonically(stream, root);
if (stream.hasError())
{
break;
}
root = root.nextSibling();
}
stream.writeEndDocument();
if (stream.hasError())
{
error = tr("Fail to write Canonical XML.");
return false;
}
return true;
}
void SaveNodeCanonically(QXmlStreamWriter &stream, const QDomNode &domNode)
{
if (stream.hasError())
{
return;
}
if (domNode.isElement())
{
const QDomElement domElement = domNode.toElement();
if (not domElement.isNull())
{
stream.writeStartElement(domElement.tagName());
if (domElement.hasAttributes())
{
QMap<QString, QString> attributes;
const QDomNamedNodeMap attributeMap = domElement.attributes();
for (int i = 0; i < attributeMap.count(); ++i)
{
const QDomNode attribute = attributeMap.item(i);
attributes.insert(attribute.nodeName(), attribute.nodeValue());
}
QMap<QString, QString>::const_iterator i = attributes.constBegin();
while (i != attributes.constEnd())
{
stream.writeAttribute(i.key(), i.value());
++i;
}
}
if (domElement.hasChildNodes())
{
QDomNode elementChild = domElement.firstChild();
while (not elementChild.isNull())
{
SaveNodeCanonically(stream, elementChild);
elementChild = elementChild.nextSibling();
}
}
stream.writeEndElement();
}
}
else if (domNode.isComment())
{
stream.writeComment(domNode.nodeValue());
}
else if (domNode.isText())
{
stream.writeCharacters(domNode.nodeValue());
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With