我有一个订单行列表,上面有每个产品。中的产品可以形成一个自引用层次结构。我需要以这样一种方式订购行,即所有没有父级或其父级在订单中缺少父级的产品都位于顶部,其次是其子项。在最终结果中,任何子项都不得高于其父项。
那么我该如何订购以下 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 正确排序任意深度级别?
首先,您可以定义几个键来帮助您通过代码或父属性查找行元素
<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 标识转换来复制 XML 中的现有节点。
非常有趣的问题。我将分两次执行此操作:首先,根据元素的层次结构嵌套元素。然后输出元素,按其祖先的计数排序。
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>