将模板重新应用于XSLT中其他模板的输出

时间:2012-10-06 13:34:55

标签: recursion xslt-1.0

以下应该是简单的或不可能的,但是现在我无法知道如何,所以我问。在我的XSLT中,我有生成元素的模板,然后应该再次进行转换。更精确的是,只要模板输出原始输入中将要进行变换的元素,就应该再次对其进行变换。因此,无限循环是可能的,但是要通过仔细设计模板来避免。以此为例:

input.xml中

<?xml version="1.0" encoding="utf-8" ?>
<example>
    <a />
    <b />
</example>

transform.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="@*|node()"><xsl:copy>
        <xsl:apply-templates select="@*|node()" />
    </xsl:copy></xsl:template>

    <xsl:template match="a">a</xsl:template>

    <xsl:template match="b">
        <B>b <a /></B>
    </xsl:template>
</xsl:transform>

电流的Output.xml

<?xml version="1.0"?>
<example>
    a
    <B>b <a/></B>
</example>

期望-的Output.xml

<?xml version="1.0"?>
<example>
    a
    <B>b a</B>
</example>

只需一次转换即可实现这一目标的最佳解决方案是什么?

3 个答案:

答案 0 :(得分:2)

这是一个两遍转换,可以产生想要的结果

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">

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

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

 <xsl:template match="/">
  <xsl:variable name="vrtfPass1">
    <xsl:apply-templates/>
  </xsl:variable>

  <xsl:variable name="vPass1" select="ext:node-set($vrtfPass1)/*"/>

  <xsl:apply-templates select="$vPass1" mode="pass2"/>
 </xsl:template>

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

 <xsl:template match="replace">
  <xsl:text>&#xA;Hello world&#xA;</xsl:text>
 </xsl:template>

 <xsl:template match="@*|node()" mode="pass2">
  <xsl:call-template name="identity"/>
 </xsl:template>
</xsl:stylesheet>

应用于提供的XML文档

<example>
    <content>Lorem ipsum</content>
    <content><replace /></content>
</example>

产生了想要的正确结果

<example>
   <wrapper>
Hello world
Lorem ipsum</wrapper>
   <wrapper>
Hello world

Hello world
</wrapper>
</example>

请注意

在XSLT 1.0中,应用模板的结果是臭名昭着的RTF(结果树片段),根据定义,除了使用xsl:copy-of和标准字符串函数之外,它无法进一步访问和处理。

这就是为什么几乎每个XSLT 1.0处理器都提供特定于供应商的扩展函数xxx:node-set(),它接受​​RTF并将其转换为“常规”树,可以使用任何XPath表达式访问后代。这里xxx前缀必须绑定到vendor-specifix namespace-uri。

EXSLT ext:node-set()由大多数XSLT处理器实现 - 因此它的使用保证了不同XSLT处理器之间的高度可移植性。

有关其他多遍转换示例,请参阅

https://stackoverflow.com/a/3200026/36305

以及

http://www.dpawson.co.uk/xsl/sect1/N169.html#d860e392

答案 1 :(得分:1)

它并不适用于所有情况,但有一种非常简单的方法可以实现您的要求。您只需为replace模板命名,然后使用xsl:call-template进行调用即可。这只需要对现有样式表进行一些小的更改:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="@*|node()"><xsl:copy>
        <xsl:apply-templates select="@*|node()" />
    </xsl:copy></xsl:template>

    <xsl:template match="content">
        <wrapper>
            <xsl:call-template name="replace"/>
            <xsl:apply-templates select="@*|node()" />
        </wrapper>
    </xsl:template>

    <xsl:template match="replace" name="replace">
        Hello world
    </xsl:template>
</xsl:transform>

答案 2 :(得分:0)

基于其他答案的输入,我想出了解决问题的方法。考虑作为更恰当的示例输入:

<?xml version="1.0" encoding="utf-8" ?>
<example>
    <a />
    <b />
    <c />
</example>

元素<a /><b /><c />在我的原始示例中与<replace />类似,但如转换中所示,它们的模板每个都包含对它们的进一步引用:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext">

    <xsl:template match="@*|node()">
        <xsl:variable name="this">
            <xsl:apply-templates select="." mode="normal" />
        </xsl:variable>
        <xsl:apply-templates select="ext:node-set($this)" mode="normal" />
    </xsl:template>

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

    <xsl:template match="a" mode="normal">a</xsl:template>

    <xsl:template match="b" mode="normal">
        <B>b <a /></B>
    </xsl:template>

    <xsl:template match="c" mode="normal">
        <C>c <b /></C>
    </xsl:template>
</xsl:transform>

当应用于示例输入时,此转换会生成我想要的结果:

<?xml version="1.0"?>
<example>
    a
    <B>b a</B>
    <C>c <B>b a</B></C>
</example>

显然所有递归引用都已解决。虽然它是用一些更复杂的例子进行测试的,但我不确定它是否适用于所有情况。

这背后的想法如下:前两个模板用于复制。具有mode="normal"的那个是简单的身份转换而没有递归的那个。使用模式是为了防止递归模板中的无限循环。

请告诉我,您对此有何看法以及是否存在明显缺陷!