如何订购自引用xml

时间:2014-03-05 20:09:50

标签: sorting xslt xpath self-reference

我有一个订单行列表,其中包含每个产品。产品可能形成自引用层次结构。我需要以这样一种方式订购这些行,即所有没有父订单或父订单中缺少订单的产品都在顶部,然后是他们的子订单。在最终结果中,没有孩子可能高于其父母。

那么如何订购以下xml:

<order>
  <line><product code="3" parent="1"/></line>
  <line><product code="2" parent="1"/></line>
  <line><product code="6" parent="X"/></line>
  <line><product code="1" /></line>
  <line><product code="4" parent="2"/></line>
</order>

进入这个:

<order>
  <line><product code="6" parent="X"/></line>
  <line><product code="1" /></line>
  <line><product code="2" parent="1"/></line>
  <line><product code="3" parent="1"/></line>
  <line><product code="4" parent="2"/></line>
</order>

请注意,特定级别内的顺序并不重要,只要子节点在其父节点之后的某个点跟随即可。

我有一个适用于不超过预定义深度的层次结构的解决方案:

<order>

<xsl:variable name="level-0" 
  select="/order/line[ not(product/@parent=../line/product/@code) ]"/>
<xsl:for-each select="$level-0">
   <xsl:copy-of select="."/>
</xsl:for-each>

<xsl:variable name="level-1"
  select="/order/line[ product/@parent=$level-0/product/@code ]"/>
<xsl:for-each select="$level-1">
   <xsl:copy-of select="."/>
</xsl:for-each>

<xsl:variable name="level-2"
  select="/order/line[ product/@parent=$level-1/product/@code ]"/>
<xsl:for-each select="$level-2">
   <xsl:copy-of select="."/>
</xsl:for-each>

</order>

上面的示例xslt适用于最大深度为3级的层次结构,并且可以很容易地扩展到更多,但是我如何推广它并使xslt正确排序任意级别的深度?

2 个答案:

答案 0 :(得分:1)

首先,您可以定义几个键,以帮助您通过代码属性查找元素

<xsl:key name="products-by-parent" match="line" use="product/@parent" />
<xsl:key name="products-by-code" match="line" use="product/@code" />

您首先选择没有父级的元素,然后使用密钥进行此项检查:

<xsl:apply-templates select="line[not(key('products-by-code', product/@parent))]"/>

然后,在匹配元素的模板中,您只需复制该元素,然后使用另一个键选择其“子”,如下所示

<xsl:apply-templates select="key('products-by-parent', product/@code)"/>

这将是一个递归调用,因此它将递归查找其子项,直到找不到更多。

试试这个XSLT

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
   <xsl:output method="xml" indent="yes"/>
   <xsl:key name="products-by-parent" match="line" use="product/@parent"/>
   <xsl:key name="products-by-code" match="line" use="product/@code"/>

   <xsl:template match="order">
      <xsl:copy>
         <xsl:apply-templates select="line[not(key('products-by-code', product/@parent))]"/>
      </xsl:copy>
   </xsl:template>

   <xsl:template match="line">
      <xsl:call-template name="identity"/>
      <xsl:apply-templates select="key('products-by-parent', product/@code)"/>
   </xsl:template>

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

请注意使用XSLT identity transform复制XML中的现有节点。

答案 1 :(得分:0)

非常有趣的问题。我会在两个过程中执行此操作:首先,根据其层次结构嵌套元素。然后输出元素,按其祖先的数量排序。

XSLT 1.0(+ EXSLT node-set()函数):

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="product-by-code" match="product" use="@code" />

<!-- first pass -->
<xsl:variable name="nested">
    <xsl:apply-templates select="/order/line/product[not(key('product-by-code', @parent))]" mode="nest"/>
</xsl:variable>

<xsl:template match="product" mode="nest">
    <xsl:copy>
        <xsl:copy-of select="@*"/>
        <xsl:apply-templates select="../../line/product[@parent=current()/@code]" mode="nest"/>
    </xsl:copy>
</xsl:template>

<!-- output -->
<xsl:template match="/order">
    <xsl:copy>
        <xsl:for-each select="exsl:node-set($nested)//product">
        <xsl:sort select="count(ancestor::*)" data-type="number" order="ascending"/>
            <line><product><xsl:copy-of select="@*"/></product></line>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

</xsl:stylesheet> 

当应用于您的输入时,结果为:

<?xml version="1.0" encoding="UTF-8"?>
<order>
   <line>
      <product code="6" parent="X"/>
   </line>
   <line>
      <product code="1"/>
   </line>
   <line>
      <product code="3" parent="1"/>
   </line>
   <line>
      <product code="2" parent="1"/>
   </line>
   <line>
      <product code="4" parent="2"/>
   </line>
</order>

这仍然存在现有/缺少父X的问题 - 我稍后会尝试解决这个问题。