XSLT递归父/子组合



非常有趣的Python赏金问题,我知道这个问题可以用XSLT1.0解决。请注意,这不是一个重复的问题,因为上一篇文章以Python方法为中心,而这篇文章正试图用XSLT解决相同的问题。以下是我的尝试,但受限于预设数量的父/子组合,这里有四个级别,并有条件地遍历每个级别。

有没有一种方法可以将我的解决方案推广到任何组合级别?我知道这可能需要使用-->分隔符标记值。预期输出是当前输出,但需要动态解决方案。我包含了Python脚本来显示最终结果。为了明确利益冲突,我不会在上面的帖子中使用任何答案,但请您这样做!

XML输入

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<nodes>
<node name="Car" child="Engine"/>
<node name="Car" child="Wheel"/>
<node name="Engine" child="Piston"/>
<node name="Engine" child="Carb"/>
<node name="Carb" child="Bolt"/>
<node name="Spare Wheel"/>
<node name="Bolt" child="Thread"/>
<node name="Carb" child="Foat"/>
<node name="Truck" child="Engine"/>
<node name="Engine" child="Bolt"/>
<node name="Wheel" child="Hubcap"/>
</nodes>

XSLT

<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output version="1.0" encoding="UTF-8" indent="yes" />
<xsl:strip-space elements="*"/>
<xsl:template match="nodes">
<data>
<xsl:apply-templates select="node[not(@name=ancestor::nodes/node/@child)]"/>
</data>
</xsl:template>
<xsl:template match="node">
<xsl:variable select="@name" name="currname"/>
<xsl:variable select="@child" name="currchild"/>
<xsl:variable select="/nodes/node" name="nodeset1"/>
<xsl:variable select="/nodes/node[@name=$currchild]" name="nodeset2"/>
<xsl:variable select="/nodes/node[@name=$nodeset2/@child]" name="nodeset3"/>
<xsl:variable select="/nodes/node[@name=$nodeset3/@child]" name="nodeset4"/>
<xsl:for-each select="$nodeset2">
<xsl:variable select="@child" name="nodeset2child"/>
<xsl:for-each select="$nodeset3">
<xsl:variable select="@child" name="nodeset3child"/>              
<xsl:if test="@name=$nodeset2child">
<xsl:for-each select="$nodeset4">
<xsl:if test="@name=$nodeset3child">
<xsl:value-of select="$currname"/> --> <xsl:value-of select="$currchild"/> --> <xsl:value-of select="$nodeset2child"/> --> <xsl:value-of select="$nodeset3child"/> --> <xsl:value-of select="@child"/><xsl:text>&#xa;</xsl:text>
</xsl:if>                  
</xsl:for-each>
<xsl:if test="$nodeset2child!=$nodeset3/@child and $nodeset3child != $nodeset4/@name">                  
<xsl:value-of select="$currname"/> --> <xsl:value-of select="$currchild"/> --> <xsl:value-of select="$nodeset2child"/> --> <xsl:value-of select="$nodeset3child"/><xsl:text>&#xa;</xsl:text>
</xsl:if>
</xsl:if>              
</xsl:for-each>
<xsl:if test="not($nodeset2child=$nodeset3/@child or ancestor::nodes/node[@name=$nodeset2child]/@child)">
<xsl:value-of select="$currname"/> --> <xsl:value-of select="$currchild"/> --> <xsl:value-of select="$nodeset2child"/><xsl:text>&#xa;</xsl:text>
</xsl:if>
</xsl:for-each>
<xsl:value-of select="@name[not(ancestor::node/@child=$nodeset2/@name)]"/><xsl:text>&#xa;</xsl:text>
</xsl:template>  
</xsl:transform>

XML转换输出

<?xml version='1.0' encoding='UTF-8'?>
<data>Car --&gt; Engine --&gt; Piston
Car --&gt; Engine --&gt; Carb --&gt; Bolt --&gt; Thread
Car --&gt; Engine --&gt; Carb --&gt; Foat
Car --&gt; Engine --&gt; Bolt --&gt; Thread
Car --&gt; Wheel --&gt; Hubcap
Spare Wheel
Truck --&gt; Engine --&gt; Piston
Truck --&gt; Engine --&gt; Carb --&gt; Bolt --&gt; Thread
Truck --&gt; Engine --&gt; Carb --&gt; Foat
Truck --&gt; Engine --&gt; Bolt --&gt; Thread
</data>

Python脚本(在转换后的输出根节点上运行xpath)

import lxml.etree as ET
# LOAD XML AND XSL DOCS
dom = ET.parse('Input.xml')
xslt = ET.parse('XSLTScript.xsl')
# TRANSFORM XML
transform = ET.XSLT(xslt)
newdom = transform(dom)
# XPATH NEW DOM ROOT NODE (<data>)        
print(newdom.xpath('/data')[0].text.replace("nn", "n"))
# Car --> Engine --> Piston
# Car --> Engine --> Carb --> Bolt --> Thread
# Car --> Engine --> Carb --> Foat
# Car --> Engine --> Bolt --> Thread
# Car --> Wheel --> Hubcap
# Spare Wheel
# Truck --> Engine --> Piston
# Truck --> Engine --> Carb --> Bolt --> Thread
# Truck --> Engine --> Carb --> Foat
# Truck --> Engine --> Bolt --> Thread

这里有一个更短(23行)且高效的解决方案

这也是计算上最简单的一个——将嵌套级别1与嵌套级别3-4进行比较。。。

这个解决方案是尾递归,这意味着任何好的XSLT处理器都会通过迭代来优化它,从而避免堆栈溢出的可能性,因为最大调用堆栈深度保持不变(1):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:key name="kNodeByChild" match="node" use="@child"/>
<xsl:key name="kNodeByName" match="node" use="@name"/>
<xsl:template match="/*">
<xsl:apply-templates select="node[not(key('kNodeByChild', @name))]"/>
</xsl:template>
<xsl:template match="node[not(key('kNodeByName', @child))]">
<xsl:param name="pParentPath"/>
<xsl:value-of select="concat($pParentPath, @name, ' ---> ', @child, '&#xA;')"/>
</xsl:template>
<xsl:template match="node">
<xsl:param name="pParentPath"/>
<xsl:apply-templates select="key('kNodeByName', @child)">
<xsl:with-param name="pParentPath" select="concat($pParentPath, @name, ' ---> ')"/>
</xsl:apply-templates>
</xsl:template>
</xsl:stylesheet>

当此转换应用于所提供的XML文档时:

<nodes>
<node name="Car" child="Engine"/>
<node name="Car" child="Wheel"/>
<node name="Engine" child="Piston"/>
<node name="Engine" child="Carb"/>
<node name="Carb" child="Bolt"/>
<node name="Spare Wheel"/>
<node name="Bolt" child="Thread"/>
<node name="Carb" child="Foat"/>
<node name="Truck" child="Engine"/>
<node name="Engine" child="Bolt"/>
<node name="Wheel" child="Hubcap"/>
</nodes>

生成所需的正确结果

Car ---> Engine ---> Piston
Car ---> Engine ---> Carb ---> Bolt ---> Thread
Car ---> Engine ---> Carb ---> Foat
Car ---> Engine ---> Bolt ---> Thread
Car ---> Wheel ---> Hubcap
Spare Wheel ---> 
Truck ---> Engine ---> Piston
Truck ---> Engine ---> Carb ---> Bolt ---> Thread
Truck ---> Engine ---> Carb ---> Foat
Truck ---> Engine ---> Bolt ---> Thread

如果您只想要叶节点

<xsl:transform version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node">
<xsl:param name="already" select="''"/>
<xsl:choose>
<xsl:when test="$already = '' and not(@child)">
<!-- no child (sparewheel) -->
<xsl:value-of select="concat(@name,'&#xa;')"/>
</xsl:when>
<xsl:when test="not(../node[@child = current()/@name])">
<!-- else will already have prefix -->
<xsl:choose>
<xsl:when test="../node[@name = current()/@child]">
<xsl:apply-templates select="../node[@name = current()/@child]">
<xsl:with-param name="already">
<xsl:value-of select="concat(@name, ' --&gt; ', @child)"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat(@name, ' --&gt; ', @child,'&#xa;')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$already != ''">
<!-- called with prefix -->
<xsl:choose>
<xsl:when test="../node[@name = current()/@child]">
<xsl:apply-templates select="../node[@name = current()/@child]">
<xsl:with-param name="already">
<xsl:value-of select="concat($already, ' --&gt; ', @child)"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat($already, ' --&gt; ', @child,'&#xa;')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:transform>

这应该执行任何递归级别,但也会输出所有中间步骤:

<xsl:transform version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node">
<xsl:param name="already" select="''"/>
<xsl:choose>
<xsl:when test="$already = '' and not(@child)">
<!-- no child (sparewheel) -->
<xsl:value-of select="concat(@name,'&#xa;')"/>
</xsl:when>
<xsl:when test="not(../node[@child = current()/@name])">
<!-- else will already have prefix -->
<xsl:value-of select="concat(@name, ' --&gt; ', @child,'&#xa;')"/>
<xsl:apply-templates select="../node[@name = current()/@child]">
<xsl:with-param name="already">
<xsl:value-of select="concat(@name, ' --&gt; ', @child)"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:when>
<xsl:when test="$already != ''">
<!-- called with prefix -->
<xsl:value-of select="concat($already, ' --&gt; ', @child,'&#xa;')"/>
<xsl:apply-templates select="../node[@name = current()/@child]">
<xsl:with-param name="already">
<xsl:value-of select="concat($already, ' --&gt; ', @child)"/>
</xsl:with-param>
</xsl:apply-templates>
</xsl:when>
</xsl:choose>
</xsl:template>
</xsl:transform>

Ups,在第三种情况下不得不调整输出,@name不再需要,因为已经在$ready中了。。。

相关内容

  • 没有找到相关文章

最新更新