2014-03-05 36 views
1

我有一个订单行列表,每个产品都在它们上面。这些产品可能会形成一个自我引用的层次结构。我需要以这样一种方式排序:所有没有父母或父母在订单中缺失的产品位于顶部,其次是子女。最终结果中,没有一个孩子可能超过其父母。如何订购自引用xml

那么,如何可以订购以下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

“*所有没有父母或父母在订单中缺失的产品都位于顶部,其次是子女。*”您是否想让父母立即跟随**,孩子,或一起列出同一代的所有项目。 –

回答

0

非常有趣的问题。我会在两遍中做到这一点:首先,根据层次结构嵌套元素。然后输出元素,按祖先的数量排序。

XSLT 1.0(+ EXSLT节点集()函数):

<?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 - 我将尽力在稍后解决。

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复制。

+0

如果输入包含两个或更多的祖先**和孩子**,我不认为你会得到正确的答案。但也许我误解了这个问题。 –