2

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

除非有人可以另外解释,否则似乎进一步考虑到 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属性在输入文档的元素中很常见,语义意图正如我上面的示例所描述的那样。

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

如果有一种创建通用for模板的策略,可以使用该模板执行任何转换操作,那将是非常壮观的。如果我可以在不使用任何递归迭代器的情况下摆脱困境,如果在 XSLT 1.0 中隐藏了一些用于此目的的函数的珍宝,那将同样壮观。

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

4

2 回答 2

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>
于 2013-04-23T04:35:29.157 回答
2

如果有一种策略可以为模板创建一个通用的模板,使用它可以执行任何转换操作,那将是非常壮观的。如果我可以在不使用任何递归迭代器的情况下摆脱困境,如果在 XSLT 1.0 中隐藏了一些用于此目的的函数的瑰宝,那将同样壮观。

无论如何,我怎样才能做到这一点?我需要求助于 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 通过使用最通用和最强大的高阶函数(折叠、地图、扫描、拉链等)的通用和强大实现来帮助解决任何此类“困难”问题。


二、使用Piez 方法(当 的值存在已知上限时@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,从那时起我已经提到过数千次,包括两篇会议论文,却不知道我应该提供免责声明:)

于 2013-04-23T04:44:10.730 回答