我有一个订单行列表,其中包含每个产品。产品可能形成自引用层次结构。我需要以这样一种方式订购这些行,即所有没有父订单或父订单中缺少订单的产品都在顶部,然后是他们的子订单。在最终结果中,没有孩子可能高于其父母。
那么如何订购以下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正确排序任意级别的深度?
答案 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的问题 - 我稍后会尝试解决这个问题。