XSLT 对重复元素进行排序的最佳方式

XSLT Best Way to Sort Duplicate Elements

提问人:Justin Weller 提问时间:10/24/2023 最后编辑:Justin Weller 更新时间:10/24/2023 访问量:88

问:

我是XSLT的新手,并且正在尝试根据某些说明对任意大小的XML文档进行排序:

  1. 所有属性都应按字母顺序排序
  2. 子元素应按元素名称的字母顺序排序
  3. 如果两个子元素具有相同的元素名称,则应使用一致、定义明确的过程(最好以某种方式按字母顺序)对它们进行排序

我目前被困在实施第 3 步。我参考第 1 步和第 2 步的这篇文章,并试图实现第 3 步。我目前的实现如下:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="3.0">

  <xsl:output method="xml" indent="yes"/>

  <xsl:template match="node()|@*">
    <xsl:copy>
      <xsl:apply-templates select="@*">
        <xsl:sort select="name()"/>
      </xsl:apply-templates>

      <xsl:apply-templates select="node()">
        <xsl:sort select="name()"/>
        <xsl:sort select="."/>
      </xsl:apply-templates>
    </xsl:copy>
  </xsl:template>
</xsl:stylesheet>

但是,我注意到由于某些边缘情况,这不是一个完美的解决方案,例如当我在以下输入上运行它时:

<colours b="x" c="y" a="t">
    <blue/>
    <aa>
      <bb>y</bb>
      <bb>a</bb>
    </aa>
    <aa>
      <bb>a</bb>
      <bb>z</bb>
    </aa>
    <violet/>
</colours>

它输出以下内容:

<?xml version="1.0" encoding="UTF-8"?>
<colours a="t" b="x" c="y">
   <aa>
      <bb>a</bb>
      <bb>z</bb>
   </aa>
   <aa>
      <bb>a</bb>
      <bb>y</bb>
   </aa>
   <blue/>
   <violet/>
</colours>

根据排序的工作方式,此输出的 aa 部分顺序错误(a,z 应该在 a,y 之后),我知道这是因为排序发生的顺序,但似乎唯一的解决方法是在输出上重复执行此 xslt,直到不再进行更改。

根据我的理解,我的实现中的辅助排序键 (<xsl:sort select=“.”/>) 我用来实现步骤 3 在遇到无法按字母顺序排序的重复元素时触发,并使用重复元素的字符串值作为排序键。从我所读到的内容来看,这个字符串值是其所有文本节点后代的字符串值按文档顺序串联的。我相信如果元素是空的,那么它的字符串值就是零长度的字符串。但是,我认为这伴随着另一种边缘情况,即无法对以下输入进行排序:

<colours b="x" c="y" a="t">
    <aa>
      <cc/>
    </aa>
    <aa>
      <bb/>
    </aa>
    <violet/>
</colours>

无论 aa 部分以何种顺序放置,输出都与输入相同,这意味着在这种情况下不会发生排序。

是否有任何改进的解决方案来处理这些重复元素名称的排序?或者我的实现是 xslt 可以实现的最佳方法,我必须坚持第一种情况的解决方法并接受第二种情况无法修复? 一个想法可能是,如果有两个重复的元素,它会通过递归检查它们的下一个子节点(按字母顺序)或类似的东西(添加第三个排序键?)来对它们进行排序,尽管我不确定如何在 xslt 中实现它。

任何帮助将不胜感激。

编辑:正如评论者所建议的那样,下面我添加了第三条指令以建立更具体的规范。

  1. 如果两个子元素具有相同的元素名称,则应使用一致、定义明确的过程(最好以某种方式按字母顺序)对它们进行排序

如果两个子元素具有相同的元素名称,是否可以将其排序键按文档顺序定义为其元素名称子树的串联?例如,对于以下代码:

<colours>
    <aa>
      <cc>
        <rr/>
      </cc>
      <ff/>
    </aa>
    <aa>
      <cc>
        <ee/>
      </cc>
      <zz/>
    </aa>
    <violet/>
</colours>

第一个 aa 排序键是 ccrrff,而下一个是 cceezz。这样,它们可以按字母顺序排序(即,第二个 aa 部分将排在最前面)。

这应该可以解决我的第二个示例中的问题(我对第一个示例的解决方法感到满意),并且我认为这个排序键将是我当前实现中的两个排序键之间的辅助排序键。

XML XSLT XSLT-3.0

评论


答:

0赞 Michael Kay 10/24/2023 #1

首先,XDM数据模型中的属性是无序的。这意味着不能保证它们将按任何特定顺序序列化。你可能很幸运,这取决于实现。

至于元素排序,我将首先确保消除空格文本节点:use .但我认为你真正的问题是指定你的需求,而不是编写代码来实现它们。您为我们提供了几个示例,但两个示例并不构成规范。您可以通过计算类似于 的排序键来实现两个示例,但这可能不会为下一个示例提供所需的结果。xsl:strip-space elements="*"string-join(*/(local-name()||string()))

评论

0赞 Justin Weller 10/24/2023
很抱歉,我意识到我应该尝试在我的第三步规范中更具体。我已经更新了帖子,提供了更多信息。虽然您的排序键适用于我提供的示例,但您能否查看我更新的帖子以获取解决方案的想法,因为您是正确的,还有其他示例仍然无法输出所需的结果。我试图让这个 xslt 能够对任何抽象的 xml 文档进行排序,因此我必须保持排序的通用性足以处理任何大小和结构的 xml。
0赞 Martin Honnen 10/24/2023
@JustinWeller,我认为您低估了“对任何抽象的 xml 文档进行排序”并让它“适用于任何大小和结构”的任务。您想如何处理混合内容文档?
0赞 Michael Kay 10/24/2023
我认为您仍在尝试“通过示例进行规范”,并且缺少很多细节,例如名称处理。
0赞 Martin Honnen 10/24/2023 #2

正如 Michael Kay 所说,没有确切的规范,我不知道你想对混合内容或任何注释或处理指令节点做什么,但也许,你基本上在属性和叶子元素中拥有所有文本数据,以下可能会有所帮助:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="#all">
  
  <xsl:template match="*">
    <xsl:copy>
      <xsl:apply-templates
        select="sort(@*, (), function($a) { namespace-uri($a), local-name($a) }),
                sort(*, (), function($el) { namespace-uri($el), local-name($el), $el/descendant::*!(namespace-uri(), local-name(), .[not(*)]!data()) })" />
    </xsl:copy>
  </xsl:template>
  
  <xsl:template match="*[not(*)]">
    <xsl:copy>
      <xsl:apply-templates
        select="sort(@*, (), function($a) { namespace-uri($a), local-name($a) }),
                sort(node(), (), function($n) { data($n) })" />
      
    </xsl:copy>
  </xsl:template>
  
  <xsl:strip-space elements="*"/>
  <xsl:output indent="yes"/>

  <xsl:mode on-no-match="shallow-copy"/>
  
</xsl:stylesheet>

它利用了该函数的强大功能,允许您根据需要/想要动态生成任意数量的排序键,因此您不会受到一定数量的 s 的约束。sortxsl:sort