XSLT for循环或节点复制的替代方案

时间:2013-04-23 04:08:33

标签: xml for-loop iteration xslt-1.0

在我的阅读中,很明显,XSLT 1.0中for i .. m循环唯一合理的解决方法是使用递归模板。

除非有人能够另外解释,否则考虑到XSLT处理的限制,这种方法通常不会重复使用。

无论如何,给定一个输入片段(本例中的上下文节点):

<items count="3">
    <item>
        <name>Name</name>
        <description>Description</description>
    </item>
</items>

是否存在基于<item>属性复制count子项的可重用策略?这里的预期输出只是

<item>
    <name>Name</name>
    <description>Description</description>
</item>
<item>
    <name>Name</name>
    <description>Description</description>
</item>
<item>
    <name>Name</name>
    <description>Description</description>
</item>

我打算在<item>节点上执行进一步的转换,但我不认为它们是相关的。

可重用性是我关注的一个问题,原因很简单,count属性在输入文档的元素中很常见,语义意图就像我上面的例子所描述的那样。

如果我要使用递归迭代器方法,我必须将其烘焙到每个模板中(非常不干;更像是非常WET,就像“为什么要尝试”;但我离题

如果创建通用for模板的策略,可以使用该模板执行任何转换操作,这将是非常壮观的。如果我可以在不使用任何递归迭代器的情况下离开,如果在XSLT 1.0中隐藏了一个用于此目的的函数的宝石,那将同样引人注目。

无论如何,我怎么能做到这一点?我是否需要采用WET方法,还是有更好的方法?

2 个答案:

答案 0 :(得分:3)

递归没有错。对于这种情况,应该很容易使它足够通用。

这是一个例子。除了输出与count相同的子元素之外,我还将name元素更改为new_elem(仅显示其他转换)...

XSLT 1.0

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="*[@count]">
        <xsl:apply-templates select="*" mode="dupe">
            <xsl:with-param name="count" select="@count"/>
        </xsl:apply-templates>
    </xsl:template>

    <xsl:template match="*" mode="dupe">
        <xsl:param name="count"/>
        <xsl:apply-templates select="."/>
        <xsl:if test="$count > 1">
            <xsl:apply-templates mode="dupe" select=".">
                <xsl:with-param name="count" select="$count - 1"/>
            </xsl:apply-templates>
        </xsl:if>
    </xsl:template>

    <xsl:template match="name">
        <new_elem><xsl:value-of select="."/></new_elem>
    </xsl:template>

</xsl:stylesheet>

输出(使用问题中的输入)

<item>
   <new_elem>Name</new_elem>
   <description>Description</description>
</item>
<item>
   <new_elem>Name</new_elem>
   <description>Description</description>
</item>
<item>
   <new_elem>Name</new_elem>
   <description>Description</description>
</item>

如果你可以使用XSLT 2.0,你可以按照你在问题开头所说的那样进行迭代:

XSLT 2.0 (产生与上面相同的输出。如果*[@count]有多个子元素,它会略有不同)

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>
    <xsl:strip-space elements="*"/>

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

    <xsl:template match="*[@count]">
        <xsl:variable name="curr" select="."/>
        <xsl:for-each select="1 to @count">
            <xsl:apply-templates select="$curr/*"/>
        </xsl:for-each>
    </xsl:template>

    <xsl:template match="name">
        <new_elem><xsl:value-of select="."/></new_elem>
    </xsl:template>

</xsl:stylesheet>

答案 1 :(得分:2)

  

如果存在为模板创建通用的策略,请使用该策略   可以执行任何转换操作   壮观。如果我可以在不使用任何递归迭代器的情况下逃脱,   如果在XSLT 1.0中隐藏了一个用于此目的的函数的某个gem,   那同样很壮观。

     

无论如何,我怎么能做到这一点?我是否需要求助于WET   方法,还是有更好的方法?

想要的DRY-ness是FXSL模板/函数iter的一个简单应用:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:f="http://fxsl.sf.net/" xmlns:myRepeat="f:myRepeat"
 exclude-result-prefixes="xsl f myRepeat">
 <xsl:import href="iter.xsl"/>

 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

  <myRepeat:myRepeat/>
  <xsl:variable name="vFunRepeat" select="document('')/*/myRepeat:*[1]"/>

  <xsl:variable name="vAdditive" select="/*/*[1]"/>

  <xsl:template match="/*">
        <xsl:call-template name="iter">
          <xsl:with-param name="pTimes" select="@count"/>
          <xsl:with-param name="pFun" select="$vFunRepeat"/>
          <xsl:with-param name="pX" select="/.."/>
        </xsl:call-template>
  </xsl:template>

  <xsl:template match="myRepeat:*" mode="f:FXSL">
    <xsl:param name="arg1"/>

    <xsl:copy-of select="$arg1 | $vAdditive"/>
  </xsl:template>
</xsl:stylesheet>

在提供的XML文档上应用此转换时:

<items count="3">
    <item>
        <name>Name</name>
        <description>Description</description>
    </item>
</items>

产生了想要的正确结果:

<item>
   <name>Name</name>
   <description>Description</description>
</item>
<item>
   <name>Name</name>
   <description>Description</description>
</item>
<item>
   <name>Name</name>
   <description>Description</description>
</item>

请注意

  1. 您根本不必编写任何递归模板。

  2. 与大多数其他FXSL模板一样,iter模板非常通用且功能强大,无需程序员一次又一次地编写和调试递归。

  3. FXSL提供了想要的DRY-ness并激发更抽象的思维 - 因此更强大的构造 - 函数,折叠,迭代,......等。

  4. 碰巧这个问题在XSLT 2.0(<xsl:for-each select="1 to @count">)中有一个简单的解决方案,但还有许多其他问题,其在XSLT 2.0中的解决方案并不那么简单。 FXSL通过使用其最通用和最强大的高阶函数的通用和强大实现来帮助解决任何此类“困难”问题 - 折叠,地图,扫描,拉链等......


  5. II。 使用Piez method (当@count的值存在已知上限时)

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>
    
     <xsl:variable name="vStyle" select="document('')"/>
     <xsl:variable name="vNodes" select=
       "$vStyle//node()|$vStyle//@* | $vStyle//namespace::*"/>
    
     <xsl:variable name="vAdditive" select="/*/*[1]"/>
    
     <xsl:template match="/*">
         <xsl:for-each select="$vNodes[not(position() > current()/@count)]">
           <xsl:copy-of select="$vAdditive"/>
         </xsl:for-each>
     </xsl:template>
    </xsl:stylesheet>
    

    当此转换应用于同一XML文档(上图)时,会产生相同的正确结果

    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
    <item>
       <name>Name</name>
       <description>Description</description>
    </item>
    

    请注意

    当可以使用Piez方法时,完全避免递归。

    <强>声明

    我很乐意在11-12年前开发FXSL,自那时以来我已经提到过数千次,包括两篇会议论文,但我不知道应该提供免责声明:)