XSLT和临时文档

时间:2011-07-03 12:20:49

标签: xml xslt

我正在尝试处理一个xml文件,该文件有几个不同的状态组,如

<root>
<childgroup>16</childgroup>
<setstate>init</setstate>
<child1>...</child1>
<child2>...</child2>
<setstate>process<setstate>
<child2>...</child2>
<child3>...</child3>
.....
<childgroup>17</childgroup>
...

我需要的是实际得到的东西

<childgroup no="16">
  <state statename="init">
    <child1>...</child1>
    <child2>...</child2>
  </state>
  <state statename="process">
    <child2>...</child2>
    <child3>...</child3>
  </state>
</childgroup>
<childgroup no="17">
...

<childgroup no="16"> <state statename="init"> <child1>...</child1> <child2>...</child2> </state> <state statename="process"> <child2>...</child2> <child3>...</child3> </state> </childgroup> <childgroup no="17"> ...

我已经完成了一个简单的部分,并将“chgrpno”属性和stateid属性添加到所有子节点(它创建了所有元素的副本,但是子组和状态,将属性添加到这两个元素。

这是有效的,在结果中所有的孩子都有属性所以我可以在下一次传递中重新组合它们,状态有数字,所以我以后可以做同样的事情。但是当我尝试做

时,试图用“临时文件”来跟随M.Kay的例子
<xsl:template match="/">
  <xsl:apply-templates mode="numb"/>
</xsl:template>

然后它只是将原件返回给我,我在第一遍中所做的所有更改都消失了。那么我在这里做错了什么?

我使用的是XSLT 1.0,而不是XSLT 2.0。

(编辑:我当然命名变量,忘了把它复制到这里)。

4 个答案:

答案 0 :(得分:3)

这是一个如何在一步中使用XSLT 1.0进行分组的示例;样式表

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="1.0">

  <xsl:output indent="yes"/>

  <xsl:key name="k1" match="root/*[not(self::childgroup)]" 
    use="generate-id(preceding-sibling::childgroup[1])"/>

  <xsl:key name="k2" match="root/*[not(self::childgroup) and not(self::setstate)]"
    use="concat(generate-id(preceding-sibling::childgroup[1]), '|', generate-id(preceding-sibling::setstate[1]))"/>

  <xsl:template match="root">
    <xsl:copy>
      <xsl:apply-templates select="childgroup"/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="childgroup">
    <childgroup no="{.}">
      <xsl:apply-templates select="key('k1', generate-id())[self::setstate]"/>
    </childgroup>
  </xsl:template>

  <xsl:template match="setstate">
    <state statename="{.}">
      <xsl:copy-of select="key('k2', concat(generate-id(preceding-sibling::childgroup[1]), '|', generate-id()))"/>
    </state>
  </xsl:template>

</xsl:stylesheet>

转换输入样本

<root>
  <childgroup>16</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
  <childgroup>17</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
</root>

<root>
   <childgroup no="16">
      <state statename="init">
         <child1>...</child1>
         <child2>...</child2>
      </state>
      <state statename="process">
         <child2>...</child2>
         <child3>...</child3>
      </state>
   </childgroup>
   <childgroup no="17">
      <state statename="init">
         <child1>...</child1>
         <child2>...</child2>
      </state>
      <state statename="process">
         <child2>...</child2>
         <child3>...</child3>
      </state>
   </childgroup>
</root>

答案 1 :(得分:2)

使用XSLT 1.0进行多次传递处理的关键是包含第一次传递结果的变量实际上不包含XML文档(树)。

此变量包含RTF(结果树片段)。 RTF仅在XSLT 1.0中定义。只有使用RTF才能完成的操作是<xsl:copy-of><xsl:value-of>或将其作为参数传递 - 任何将RTF视为字符串的内容。

根据定义,任何对带有位置测试的XPath表达式的RTF内部的尝试都必须失败。

这个解决方法是使用扩展函数,通常名为xxx:node-set()(但Xalan使用名称“nodeset”no dash),其中"xxx:"前缀绑定到特定的,实现定义的XSLT处理器。正如Martin Honnen所建议的那样,为了达到某种程度的可移植性,只要特定的XSLT处理器实现,就应该尝试使用EXSLT common:nodeset()扩展。

以下是使用XSLT 1.0进行双程处理的示例

<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()|@*">
  <xsl:copy>
   <xsl:apply-templates select="node()|@*"/>
  </xsl:copy>
 </xsl:template>

 <xsl:template match="num[not(. mod 2)]"/>

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

  <nums>
   <xsl:apply-templates mode="pass2"
        select="ext:node-set($vrtfPass1)/*"/>
  </nums>
 </xsl:template>

 <xsl:template match="num" mode="pass2">
 <x>
  <xsl:apply-templates/>
 </x>
 </xsl:template>
</xsl:stylesheet>

应用于此XML文档

<nums>
  <num>01</num>
  <num>02</num>
  <num>03</num>
  <num>04</num>
  <num>05</num>
  <num>06</num>
  <num>07</num>
  <num>08</num>
  <num>09</num>
  <num>10</num>
</nums>

第一遍中的这种变换产生一个过滤树,其中只存在具有奇数值的num个元素。然后第二遍将每个num元素重命名为x。最终结果是:

<nums>
   <x>01</x>
   <x>03</x>
   <x>05</x>
   <x>07</x>
   <x>09</x>
</nums>

答案 2 :(得分:1)

另一种(单遍)可能的方法(不是说更简单的)是:

  • 将节点集复制为变量
  • 使用变量引用以递归方式构建相同组的相邻兄弟的相交节点集的谓词

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output indent="yes"/>
    <xsl:template match="text()"/>

    <xsl:template match="*" mode="childx">
        <xsl:copy-of select="."/>
    </xsl:template>

    <xsl:template match="childgroup">
        <xsl:variable name="fw" select="following-sibling::*"/>
        <childgroup no="{.}">
            <xsl:variable name="fwrw" 
                select="$fw[self::childgroup][1]
                /preceding-sibling::*"/>
            <xsl:apply-templates select="following-sibling::*[
                count( . | $fwrw ) = count( $fwrw ) 
                or count( $fwrw )=0]
                [self::setstate] " mode="setstate"/>
        </childgroup>
    </xsl:template>

    <xsl:template match="setstate" mode="setstate">
        <xsl:variable name="fw" select="following-sibling::*"/>
        <state name="{.}">
            <xsl:variable name="fwrw" 
                select="$fw[ self::setstate or self::childgroup ][1]/
                preceding-sibling::*"/>
            <xsl:apply-templates select="following-sibling::*[
                count( . | $fwrw) = count( $fwrw ) 
                or count($fwrw) = 0]" mode="childx"/>
        </state>
    </xsl:template>
</xsl:stylesheet>

应用于以下XML时:

<root>
  <childgroup>16</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
  <childgroup>17</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
  <childgroup>18</childgroup>
  <childgroup>19</childgroup>
  <setstate>init</setstate>
  <child1>...</child1>
  <child2>...</child2>
  <setstate>process</setstate>
  <child2>...</child2>
  <child3>...</child3>
  <childgroup>20</childgroup>
</root>

产地:

<childgroup no="16">
   <state name="init">
      <child1>...</child1>
      <child2>...</child2>
   </state>
   <state name="process">
      <child2>...</child2>
      <child3>...</child3>
   </state>
</childgroup>
<childgroup no="17">
   <state name="init">
      <child1>...</child1>
      <child2>...</child2>
   </state>
   <state name="process">
      <child2>...</child2>
      <child3>...</child3>
   </state>
</childgroup>
<childgroup no="18"/>
<childgroup no="19">
   <state name="init">
      <child1>...</child1>
      <child2>...</child2>
   </state>
   <state name="process">
      <child2>...</child2>
      <child3>...</child3>
   </state>
</childgroup>
<childgroup no="20"/>

答案 3 :(得分:0)

您应该为变量命名,然后您可以复制它,例如

<xsl:variable name="rtf1">
  <xsl:apply-templates mode="numb"/>
</xsl:variable>

<xsl:template match="/">
  <xsl:copy-of select="$rtf1"/>
</xsl:template>

使用XSLT 1.0,如果您不想使用copy-of复制变量内容,而是希望使用apply-templates处理它,那么您需要一个像exsl:node-set这样的扩展函数,例如

<xsl:variable name="rtf1">
  <xsl:apply-templates mode="numb"/>
</xsl:variable>

<xsl:template match="/">
  <xsl:apply-templates select="exsl:node-set($rtf1)/node()"/>
</xsl:template>