【问题标题】:Qt 5 produce random attribute order in XMLQt 5 在 XML 中生成随机属性顺序
【发布时间】:2014-12-09 11:43:13
【问题描述】:

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

在 Qt 5.x 中是否可以像在 Qt 4.8 中一样生成 XML 文件?

【问题讨论】:

    标签: xml qt random qt4 qt5


    【解决方案1】:

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

    我决定使用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());
      }
    }
    

    【讨论】:

    • 您可以使用全局变量锁定哈希种子 - 检查我的答案
    • 感谢您的评论,但我已经这样做了,它的工作并不稳定。是的,您在同一台机器上得到相同的结果,但如果移动到另一台机器上,结果会有所不同。我的方法更容易预测。
    • 我不同意 - 它在我们机器之间的生产软件中工作
    • 好。但我说的是您在第一台机器上创建文件的情况。然后将文件移动到另一个文件,打开并重新保存。在这种情况下,结果会有所不同。第二台机器上的每次下一次保存都会产生相同的订单。第二台机器的唯一顺序。但是首先在第二台机器上保存会改变顺序。这就是我提出这个解决方案的原因。如果您的方法对您和其他人有效,我不介意。
    • 我在上面的#dismine 评论中出于同样的原因使用了这种方式而不是接受的答案。
    【解决方案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 输出。

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

    2) 使用全局变量锁定哈希种子

    extern Q_CORE_EXPORT QBasicAtomicInt qt_qhash_seed;
    void main( void )
    {
        qt_qhash_seed.store(0);
        ...
    }
    

    这次 Iv 在进程之间和计算机之间得到了相同的输出,这与使用 Qt 4.8 保存的方式或多或少相同。

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

        猜你喜欢
        • 2012-05-06
        • 2014-02-27
        • 1970-01-01
        • 1970-01-01
        • 2017-08-16
        • 2018-03-10
        • 2015-10-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多