我尝试使用哈希种子,但是只有在使用一台计算机的情况下,它才能正常工作。如果在第一台计算机上打开的文件在第二台打开,即使我将哈希种子设置为相同的值,相同的代码也不会产生相同的顺序。因此,我只是决定寻找另一种解决方案。
我决定使用QXmlStreamWriter类作为包装器。每次保存QDomDocument时,我都会对其进行解析并通过QXmlStreamWriter进行写入。这可以帮助我将xml DOM转换为规范形式。
这是我使用的代码。也许有人会发现它有用。
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());
}
}
Run Code Online (Sandbox Code Playgroud)
不可能以与 Qt 4.8 相同的方式生成,Qt 4.8 按照读取 XML 属性的顺序保存 XML 属性。但有一种方法可以消除随机性,并始终以相同的顺序生成 XML 文件,该顺序可能与读取它们的顺序相同,也可能不同。换句话说,重复保存会产生相同的结果。
为什么Qt 5.x随机保存属性?因为它使用 QHash 来存储属性,并且 QHash 类已被修改以修补算法复杂性攻击,如下所述:http ://qt-project.org/doc/qt-5/qhash.html#algorithmic-complexity-attacks
如果您使用 QHash 按特定顺序存储一些数据,那么切换到 Qt 5.x 将破坏您的代码。
解决方案:
1)使用环境变量锁定哈希种子:
void main( void )
{
qputenv("QT_HASH_SEED", "0");
...
}
Run Code Online (Sandbox Code Playgroud)
它将在进程之间将哈希种子锁定为一个值,并且每次都会生成相同的 XML 输出。
我注意到一切都很好,直到我在另一台机器上运行该程序 - 然后输出再次不同,但每次我保存 XML 时都是相同的。
2)使用全局变量锁定哈希种子
extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed;
void main( void )
{
qt_qhash_seed.store(0);
...
}
Run Code Online (Sandbox Code Playgroud)
这次 Iv 在进程之间和计算机之间得到了相同的输出,这或多或少与使用 Qt 4.8 保存的方式相同。