Qt 5在XML中生成随机属性顺序

时间:2014-12-09 11:43:13

标签: xml qt random qt4 qt5

从Qt 4.8切换到Qt 5.x时,您可能会注意到每次保存XML文档时,它都会在文件中生成随机属性顺序。 编程阅读XML文档没有问题,因为在反序列化XML时允许以任何顺序存储属性。使用GIT,SVN等跟踪输出XML文件的更改时会出现问题 - 无法判断XML文件中的数据是否已更改或属性结构是否已更改。

是否可以在Qt 5.x中生成与Qt 4.8相同的XML文件?

3 个答案:

答案 0 :(得分:4)

我尝试使用散列种子,但只有使用一台机器才能正常工作。如果在第一台机器上创建的文件在第二台机器上打开,则相同的代码即使我将散列种子设置为相同的值也不会产生相同的顺序。所以,我决定找到另一种解决方案。

我决定使用QXmlStreamWriter类作为包装器。每次我保存QDomDocument我解析它并通过QXmlStreamWriter写。这有助于我将xml DOM转换为canonical form

这是我使用的代码。也许有人会发现它很有用。

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());
  }
}

答案 1 :(得分:2)

无法生成与Qt 4.8中相同的方式,它保存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");
    ...
}

它会将哈希种子锁定到进程之间的一个值,并且每次都会生成相同的XML输出。

我注意到在我在另一台机器上运行程序之前,eveything很好 - 然后输出再次不同,但每次我保存XML时都是相同的。

2)使用全局变量

锁定散列种子
extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed;
void main( void )
{
    qt_qhash_seed.store(0);
    ...
}

这次Iv在进程之间和计算机之间获得相同的输出,这与使用Qt 4.8保存的方式大致相同。

答案 2 :(得分:2)

在最新版本的Qt中,您可以按以下方式覆盖哈希种子:

#include <QHash>
int main() {
    qSetGlobalQHashSeed( 0 );
}