重新访问:递归对任意 XML 文档的元素进行排序



我的 XSLT 传奇中的这一章是这里问题的扩展。 感谢所有帮助我走到这一步的人(@Martin Honnen,@Ian Roberts,@Tim C以及我错过的任何其他人)!

这是我目前的问题:

  1. 我在A_v1.xml中重新排序了一些兄弟姐妹以创建A_v2.xml我现在认为这两个文件是同一文件的不同"版本"。 文件两个文件具有完全相同的内容,只是一些兄弟姐妹的顺序不同。 另一种说法是,A_v2.xml中的每个元素仍然具有与A_v1.xml相同的父元素,但它现在可能发生在它曾经发生的兄弟姐妹之前,或者可能发生在以前发生的兄弟姐妹之后。
  2. 我把A_v1.xml变成A_v1_transformed.xml
  3. 我把A_v2.xml变成A_v2_transformed.xml
  4. 我把A_v1_transformed.xml比作A_v2_transformed.xml,令我沮丧的是,它们并不完全相同。 此外,它们都不符合expected.xml中显示的预期顺序。 它们具有相同的内容,但元素的排序顺序不同。

我的第一个排序是<xsl:sort select="local-name()"/>. @G. Ken Holman把我转到<xsl:sort select="."/>(它与我正在使用的<xsl:sort select="self::*"/>具有相同的效果)。 当我结合使用这两种排序时,我几乎完全得到了我想要的,但在某些地方,预期的字母顺序似乎只是随机中断的。

我已经加强了我的示例文件。 为了保持问题简短,我只是把它们放在 pastebin 上。

A_v1.xml

A_v2.xml

A_v1_transformed.xml

A_v2_transformed.xml

这是其中一个转换后的文件,其中包含我添加的评论,以帮助您了解我认为转换错误地对这些文件进行排序的位置/原因。 我没有评论另一个转换后的文件,因为它有类似的"失败"。

A_v1_transformed_with_comments.xml

转换后的两个文档都应具有与 expected.xml 相同的校验和,但它们没有。 这是我最关心的问题。 按字母顺序排序似乎是最理智的排序方式,但只要转换以某种理智的方式排序,只要排序在同一文件的不同"版本"之间是可重复的,我就不在乎排序是如何发生的。

意料之中.xml

以下 XLS 文件都产生相同的结果,但"多通道"版本可能更容易理解。

xsl_concise.xsl

xsl_multi_pass.xsl

讨论要点:

  1. 我注意到,按字母顺序排序时,大写字母优先。 即使大写字母按字母顺序出现在小写字母之后,它也会在排序中排在第一位。

部分成功...

我想我自己可能偶然发现了部分解决方案,但我不清楚它为什么有效。 如果你看一下我的xsl_multi_pass.xsl文件,你会看到:

    <!-- Third pass with sortElements mode templates -->
    <xsl:variable name="sortElementsRslt">
        <xsl:apply-templates mode="sortElements" select="$sortAttributesRslt"/>
    </xsl:variable>
    <!-- Fourth pass with deDup mode templates -->
    <xsl:apply-templates mode="deDup" select="$sortElementsRslt"/>

如果我把它变成:

    <!-- Third pass with sortElements mode templates -->
    <xsl:variable name="sortElementsRslt1">
        <xsl:apply-templates mode="sortElements" select="$sortAttributesRslt"/>
    </xsl:variable>
    <!-- Fourth pass with sortElements mode templates -->
    <xsl:variable name="sortElementsRslt2">
        <xsl:apply-templates mode="sortElements" select="$sortElementsRslt1"/>
    </xsl:variable>
    <!-- Fifth pass with deDup mode templates -->
    <xsl:apply-templates mode="deDup" select="$sortElementsRslt2"/>

这对元素进行了两次排序,我不知道为什么有必要。 使用我提供的示例文件的结果是我期望的减去优先的大写字母,但只要结果看起来是一致的,这就不会打扰我。 问题是这个"解决方案"导致我正在使用的实际文件的另一部分排序不一致。

成功!

我想我终于按照我想要的方式 100% 工作了。 我将 Novatchev @Dimitre 的答案中给出的函数合并到元素的属性名称和值中。 出于某种原因,我仍然必须执行两次传递来对元素进行排序(应用完全相同的模板两次),但对于 3MB 的文件只需要额外的 20 秒,所以我不太担心它。

这是最终结果:

xsl_2.0_full_document_sorter.xsl

简而言之,我所有 XSLT 问题的最终目标是样式表,当应用于文件时,即使运行在该文件的不同"版本"上,也始终会生成相同的结果。文件的不同"版本"将是具有完全相同内容的文件,只是顺序不同。这意味着元素的属性可能已被移动,并且元素可能比以前更晚地出现。

您是否考虑过使用其他工具而不是 XSLT 来实现此目的? 在我看来,您描述的目标几乎完全符合 XMLUnit 中similar()的定义

// control and test are the two XML documents you want to compare, they can
// be String, Reader, org.w3c.dom.Document or org.xml.sax.InputSource
Diff d = new Diff(control, test);
assert d.similar();

成功!

我想我终于按照我想要的方式 100% 工作了。 我合并了 Novatchev @Dimitre 在答案中给出的函数,以按属性名称和值对元素进行排序。 出于某种原因,我仍然必须执行两次传递来对元素进行排序(应用完全相同的模板两次),但对于 3MB 的文件只需要额外的 20 秒,所以我不太担心它。

这是最终结果:

xsl_2.0_full_document_sorter.xsl

此转换是 100% 通用的,应该能够在任何 XML 文档上使用,以我认为最合理的方式对其进行排序。 此样式表的主要优点是,它将以完全相同的方式以不同的顺序转换具有相同内容的多个文件,以具有相同内容的所有文件的转换结果将是相同的。

最新更新