【问题标题】:XSLT and temporary documentsXSLT 和临时文档
【发布时间】:2011-07-03 12:20:49
【问题描述】:

我正在尝试处理具有多个不同状态组的 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">
...

我已经完成了简单的部分,即向所有子元素添加“chgrpno”属性和 stateid 属性(它复制除子组和状态之外的所有元素,将属性添加到这两个元素。

<xsl:template match="/">
  <xsl:apply-templates mode="numb"/>
</xsl:template>

这行得通,结果所有孩子都有属性,所以我可以在下一次传递中重新组合它们,并且状态有数字,所以我以后可以做同样的事情。但是当我尝试这样做时,试图以“临时文件”来效仿 M.Kay 的例子

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

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

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

我使用 XSLT 1.0,而不是明确使用 XSLT 2.0。

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

【问题讨论】:

  • 好问题,+1。有关 XSLT 1.0 中必需的 RTF 转换为临时树的解释,请参阅我的答案。此外,还提供了两遍 XSLT 1.0 处理的完整代码示例。

标签: xml xslt


【解决方案1】:

这里是一个如何一步一步使用 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>

【讨论】:

    【解决方案2】:

    使用 XSLT 1.0 进行多遍处理的关键在于,包含第一遍结果的变量实际上并不包含 XML 文档(树)。

    此变量包含一个 RTF(结果树片段)。 RTF 仅在 XSLT 1.0 中定义。可以使用 RTF 完成的唯一操作是 &lt;xsl:copy-of&gt;&lt;xsl:value-of&gt; 或将其作为参数传递——任何将 RTF 视为字符串的操作。

    根据定义,任何尝试使用包含位置测试的 XPath 表达式的 RTF 内部结构的尝试都必须失败。

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

    这是一个使用 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>
    

    【讨论】:

      【解决方案3】:

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

      • 将节点集复制为变量
      • 使用变量引用在同一组的相邻兄弟节点上递归地构建相交节点集的谓词

      <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"/>
      

      【讨论】:

        【解决方案4】:

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

        <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>
        

        【讨论】:

        • 这正是我的问题:像你提供的代码在 Kay 的书中用于临时结果,它不起作用 - 如果我在
        • 考虑发布最少但完整且真实的示例,以便我们重现问题。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多