【问题标题】:Deterministic sorting of XML using XSLT, based on child nodes基于子节点使用 XSLT 对 XML 进行确定性排序
【发布时间】:2012-06-20 12:00:25
【问题描述】:

我想在我的 [OWL] (http://www.w3.org/TR/owl-ref/) 文件中引入确定性排序,以便我可以将修改后的文件与原始文件进行比较并更轻松地查看它的位置已经变了。该文件由工具(Protege)生成,元素的顺序是半随机变化的。

问题是排序不能基于简单的事情,比如给定元素的名称和属性。通常差异只出现在下面几级的子节点中。

例子:

  <owl:Class rdf:about="#SomeFooClass">
    <rdfs:subClassOf><!-- subclass definition 1 -->
      <owl:Restriction>
        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:maxCardinality>
        <owl:onProperty>
          <owl:DatatypeProperty rdf:ID="negate"/>
        </owl:onProperty>
      </owl:Restriction>
    </rdfs:subClassOf>
    <rdfs:subClassOf><!-- subclass definition 2 -->
      <owl:Restriction>
        <owl:onProperty>
          <owl:DatatypeProperty rdf:about="#name"/>
        </owl:onProperty>
        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:maxCardinality>
      </owl:Restriction>
    </rdfs:subClassOf>

这里的子类定义 1 和 2(以及其中的其他子元素)按顺序变化,有时 1 是第一个,有时是 2。

我基于一些常见的直接属性(例如 s about 和 ID)实现了排序,虽然这修复了许多模棱两可的排序,但它无法解决这个问题。 XSLT:

<xsl:stylesheet version="2.0" 
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space  elements="*"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()">
                <xsl:sort select="@rdf:about" data-type="text"/>
                <xsl:sort select="@rdf:ID" data-type="text"/>
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

我在想,也许解决方案需要能够为每个元素计算某种“哈希码”,这会考虑到它的子元素的所有内容。这样子类定义 1 的哈希码为 3487631,子类定义 2 的哈希码为 45612,它们之间的排序是确定性的(如果它们的子元素未修改)。

编辑:刚刚意识到哈希码计算不应该关心子注释排序来实现它想要做的事情。

我可以主要使用直接已知的属性值,然后使用哈希码,如果它们相等的话。我可能会得到类似的结果:

<xsl:stylesheet version="2.0" 
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space  elements="*"/>

    <xsl:template match="@* | node()">
        <xsl:copy>
            <xsl:apply-templates select="@* | node()">
                <xsl:sort select="@rdf:about" data-type="text"/>
                <xsl:sort select="@rdf:ID" data-type="text"/>
                <xsl:sort select="my:hashCode(.)" />
            </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

   <xsl:function name="my:hashCode" as="xs:string">
      ...
   </xsl:function>
</xsl:stylesheet>

但不知道如何实现 my:hashCode。

编辑:根据要求,举几个例子。该工具在保存相同数据时可能或多或少随机产生例如以下类型的结果 (1-3):

1.

<owl:Class rdf:about="#SomeFooClass">
    <rdfs:subClassOf><!-- subclass definition 1 -->
      <owl:Restriction>
        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:maxCardinality>
        <owl:onProperty>
          <owl:DatatypeProperty rdf:ID="negate"/>
        </owl:onProperty>
      </owl:Restriction>
    </rdfs:subClassOf>
    <rdfs:subClassOf><!-- subclass definition 2 -->
      <owl:Restriction>
        <owl:onProperty>
          <owl:DatatypeProperty rdf:about="#name"/>
        </owl:onProperty>
        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:maxCardinality>
      </owl:Restriction>
    </rdfs:subClassOf>
</owl:Class>

2.

<owl:Class rdf:about="#SomeFooClass">
    <rdfs:subClassOf><!-- subclass definition 2 -->
      <owl:Restriction>
        <owl:onProperty>
          <owl:DatatypeProperty rdf:about="#name"/>
        </owl:onProperty>
        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:maxCardinality>
      </owl:Restriction>
    </rdfs:subClassOf>
    <rdfs:subClassOf><!-- subclass definition 1 -->
      <owl:Restriction>
        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:maxCardinality>
        <owl:onProperty>
          <owl:DatatypeProperty rdf:ID="negate"/>
        </owl:onProperty>
      </owl:Restriction>
    </rdfs:subClassOf>
</owl:Class>

3.

<owl:Class rdf:about="#SomeFooClass">
    <rdfs:subClassOf><!-- subclass definition 2 -->
      <owl:Restriction>
        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:maxCardinality>
        <owl:onProperty>
          <owl:DatatypeProperty rdf:about="#name"/>
        </owl:onProperty>
      </owl:Restriction>
    </rdfs:subClassOf>
    <rdfs:subClassOf><!-- subclass definition 1 -->
      <owl:Restriction>
        <owl:onProperty>
          <owl:DatatypeProperty rdf:ID="negate"/>
        </owl:onProperty>
        <owl:maxCardinality rdf:datatype="http://www.w3.org/2001/XMLSchema#int"
        >1</owl:maxCardinality>
      </owl:Restriction>
    </rdfs:subClassOf>
</owl:Class>

这些示例是结构的简化版本,但应该显示原理。我想实现一个 XSLT 排序,它将为所有 3 个示例产生相同的输出。转换后的结果看起来像版本 1、2 还是 3(或其他排序)并不那么重要。

【问题讨论】:

  • 感谢哈希计算参考。当我编辑我的 OP 时,我认为简单的哈希计算是行不通的。这是因为我有这样的案例
  • 我不明白。你的问题说你想要一个散列函数,但你的 cmets 说你不想要一个散列函数。
  • 感谢哈希计算参考。当我编辑我的 OP 时,我认为简单的哈希计算是行不通的。这是因为我有 12 3 3 21 这里内容实际上是相同的,并且应该排序为相同的结果 XML,但是“c”和“d”元素是随机排序的。现在,如果计算哈希,则带有子 c,d 的“b”将导致与带有子 d,c 的“b”不同。最终结果可能不同,但应该相同。
  • 哈希计算只是一个潜在解决方案的快速想法。我想要的是对我的数据进行确定性排序。不知道是否可以通过某种排序前的哈希计算来改进它?或者如果我可以让 XSLT 从叶节点开始排序?这种方式哈希总是基于具有确定顺序的元素计算。

标签: xslt sorting xslt-2.0


【解决方案1】:

我假设两个节点相等,如果节点名称相同且文本内容相同,则出于排序目的。据我了解,输入文档需要以某种确定性方式进行排序,但排序是什么并不重要,只要它是确定性的。因此,我建议对节点名称和直接文本子项的组合进行排序。不需要散列。

试试这个样式表:

<xsl:stylesheet version="2.0" 
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output omit-xml-declaration="yes" indent="yes"/>
    <xsl:strip-space  elements="*"/>

    <xsl:template match="@*|node()">
        <xsl:copy>
          <xsl:apply-templates select="@*" />
          <xsl:apply-templates select="node()">
            <xsl:sort select="namespace-uri()" data-type="text" />
            <xsl:sort select="local-name()" data-type="text"/>
            <xsl:sort select="string-join(./text(),' ')" data-type="text"/>
          </xsl:apply-templates>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

【讨论】:

  • 谢谢,但不幸的是第一个假设不成立。如果您查看 OP 中的示例,即使“子类定义 1”和“子类定义 2”交换了位置,1 和 2 也应被视为相同的结构并排序为相同的结果。此解决方案对案例 1 和案例 2 产生不同的最终结果。
  • 案例 2 和 3 有效(它们被认为是相同的)。我认为解决方案需要从叶节点开始工作,然后对根元素进行排序。这样,当对文档 a 中的节点 n1a 和 n2a 以及文档 b 中的节点 n1b 和 n2b 进行排序时,它们的内容已经处于相同的顺序,即使它们的子元素最初可能处于不同的顺序。我想我可能需要用 Java 来实现它,因为它开始听起来很复杂......?
【解决方案2】:

我最终还是用 Java 实现了排序。

基本上我从孩子开始递归地对 DOM 进行排序:

private Element sort(Element e) {
    // first, sort children's contents recursively
    List<Element> elementNodes = removeElementChildNodes(e);
    for (Element child: elementNodes) {
        sort(child);
    }
    // after that, sort the order of these children
    List<Element> sortedElementNodes = sortElements(elementNodes);
    // add them back
    for (Element child: sortedElementNodes) {
        e.appendChild(child);
    }
    return e;
}

实际排序首先比较元素名称和几个重要的属性名称,如果都相等,比较节点的规范化字符串转换及其子节点(此时保证子节点的内容已经排序)

private class ElementSortingComparator implements Comparator<Element> {

    public int compare(Element o1, Element o2) {
        CompareToBuilder c = new CompareToBuilder(); 
        c.append(o1.getTagName(), o2.getTagName());
        c.append(o1.getAttribute(ID_ATTRIBUTE),
                o2.getAttribute(ID_ATTRIBUTE));
        c.append(o1.getAttribute(ABOUT_ATTRIBUTE),
                o2.getAttribute(ABOUT_ATTRIBUTE));
        int result = c.toComparison();
        if (result == 0) {
            String node1 = partialNodeToString(o1);
            String node2 = partialNodeToString(o2);
            result = node1.compareTo(node2);
        }
        return result;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-25
    • 1970-01-01
    • 2021-01-16
    • 1970-01-01
    • 2017-09-02
    • 1970-01-01
    相关资源
    最近更新 更多