如何在不增加变量的情况下在XSLT中执行此操作? (调整Xalan以创建全局XSLT迭代器。我还有其他选择吗?)

时间:2011-11-01 09:57:03

标签: xslt xerces xalan

我试图用XSLT术语来尽可能地考虑功能,但在这种情况下,我真的不知道如何在没有调整的情况下做到这一点。我大致有这个数据结构:

<transactions>
  <trx>
    <text>abc</text>
    <text>def</text>

    <detail>
      <text>xxx</text>
      <text>yyy</text>
      <text>zzz</text>
    </detail>
  </trx>
</transactions>

我粗略想要压缩成这种形式

<row>abc</row>
<row>def</row>
<row>xxx</row>
<row>yyy</row>
<row>zzz</row>

但棘手的是:我想创建40个文本行的块,并且不能跨块分割事务。即如果我当前的块已经有38行,则上述事务必须进入下一个块。当前的块需要填充两个空行来完成40:

<row/>
<row/>

在命令式/程序式编程中,它非常简单。只需创建一个计数为40的倍数的全局迭代器变量,并在需要时插入空行(I have provided an answer showing how to tweak XSLT/Xalan to allow for such variables)。但是如何使用XSLT呢? N.B:考虑到我正在处理的数据大小,我担心递归是不可能的......但也许我错了

3 个答案:

答案 0 :(得分:3)

<强>予。这是一个XSLT 1.0解决方案(XSLT 2.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:param name="pChunkSize" select="8"/>
 <xsl:param name="vChunkSize" select="$pChunkSize+1"/>

 <xsl:variable name="vSheet" select="document('')"/>

 <xsl:variable name="vrtfEmptyChunk">
  <xsl:for-each select=
   "($vSheet//node())[not(position() > $pChunkSize)]">
    <row/>
  </xsl:for-each>
 </xsl:variable>

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

 <xsl:variable name="vrtfDummy">
  <delete/>
 </xsl:variable>

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

 <xsl:template match="/*">
  <chunks>
   <xsl:call-template name="fillChunks">
    <xsl:with-param name="pNodes" select="trx"/>
    <xsl:with-param name="pCurChunk" select="$vDummy"/>
   </xsl:call-template>
  </chunks>
 </xsl:template>

 <xsl:template name="fillChunks">
  <xsl:param name="pNodes"/>
  <xsl:param name="pCurChunk"/>

  <xsl:choose>
    <xsl:when test="not($pNodes)">
     <chunk>
      <xsl:apply-templates mode="rename" select="$pCurChunk[self::text]"/>
      <xsl:copy-of select=
        "$vEmptyChunk[not(position() > $vChunkSize - count($pCurChunk))]"/>
     </chunk>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="vAvailable" select=
          "$vChunkSize - count($pCurChunk)"/>

      <xsl:variable name="vcurNode" select="$pNodes[1]"/>

      <xsl:variable name="vTrans" select="$vcurNode//text"/>

      <xsl:variable name="vNumNewNodes" select="count($vTrans)"/>

      <xsl:choose>
        <xsl:when test="not($vNumNewNodes > $vAvailable)">
         <xsl:variable name="vNewChunk"
              select="$pCurChunk | $vTrans"/>

         <xsl:call-template name="fillChunks">
           <xsl:with-param name="pNodes" select="$pNodes[position() > 1]"/>
           <xsl:with-param name="pCurChunk" select="$vNewChunk"/>
         </xsl:call-template>
        </xsl:when>

        <xsl:otherwise>
         <chunk>
          <xsl:apply-templates mode="rename" select="$pCurChunk[self::text]"/>
          <xsl:copy-of select=
            "$vEmptyChunk[not(position() > $vAvailable)]"/>
         </chunk>

         <xsl:call-template name="fillChunks">
          <xsl:with-param name="pNodes" select="$pNodes"/>
          <xsl:with-param name="pCurChunk" select="$vDummy"/>
         </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:otherwise>
  </xsl:choose>
 </xsl:template>

 <xsl:template match="text" mode="rename">
  <row>
   <xsl:value-of select="."/>
  </row>
 </xsl:template>
</xsl:stylesheet>

将此转换应用于以下XML文档(基于提供的转换,但有三个trx元素):

<transactions>
  <trx>
    <text>abc</text>
    <text>def</text>

    <detail>
      <text>xxx</text>
      <text>yyy</text>
      <text>zzz</text>
    </detail>
  </trx>
  <trx>
    <text>abc2</text>
    <text>def2</text>
  </trx>
  <trx>
    <text>abc3</text>
    <text>def3</text>

    <detail>
      <text>xxx3</text>
      <text>yyy3</text>
      <text>zzz3</text>
    </detail>
  </trx>
</transactions>

生成了想要的正确结果(两个大小为8的块)

<chunks>
   <chunk>
      <row>abc</row>
      <row>def</row>
      <row>xxx</row>
      <row>yyy</row>
      <row>zzz</row>
      <row>abc2</row>
      <row>def2</row>
      <row/>
   </chunk>
   <chunk>
      <row>abc3</row>
      <row>def3</row>
      <row>xxx3</row>
      <row>yyy3</row>
      <row>zzz3</row>
      <row/>
      <row/>
      <row/>
   </chunk>
</chunks>

请注意

  1. 前两个交易'text元素总数为7,它们适合一个8位块。

  2. 第三个事务有5个text元素,不适合第一个块的剩余空间 - 它放在一个新块中。

  3. <强> II。 XSLT 2.0解决方案(使用FXSL

    <xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:f="http://fxsl.sf.net/"
    xmlns:dvc-foldl-func="dvc-foldl-func"
    exclude-result-prefixes="f dvc-foldl-func"
    >
    
       <xsl:import href="../f/func-dvc-foldl.xsl"/>
       <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
       <xsl:param name="pChunkSize" select="8"/>
    
       <dvc-foldl-func:dvc-foldl-func/>
    
       <xsl:variable name="vPadding">
        <row/>
       </xsl:variable>
    
       <xsl:variable name="vFoldlFun" select="document('')/*/dvc-foldl-func:*[1]"/>
    
        <xsl:template match="/">
    
          <xsl:variable name="vpaddingChunk" select=
           "for $i in 1 to $pChunkSize
             return ' '
           "/>
    
          <xsl:variable name="vfoldlResult" select=
              "f:foldl($vFoldlFun, (), /*/trx),
               $vpaddingChunk
              "/>
          <xsl:variable name="vresultCount"
               select="count($vfoldlResult)"/>
          <xsl:variable name="vFinalResult"
           select="subsequence($vfoldlResult, 1,
                               $vresultCount - $vresultCount mod $pChunkSize
                               )"/>
          <result>
           <xsl:for-each select="$vFinalResult">
            <row>
              <xsl:value-of select="."/>
            </row>
           </xsl:for-each>
           <xsl:text>&#xA;</xsl:text>
          </result>
        </xsl:template>
    
        <xsl:template match="dvc-foldl-func:*" mode="f:FXSL">
             <xsl:param name="arg1"/>
             <xsl:param name="arg2"/>
    
             <xsl:variable name="vCurCount" select="count($arg1)"/>
    
             <xsl:variable name="vNewCount" select="count($arg2//text)"/>
    
             <xsl:variable name="vAvailable" select=
             "$pChunkSize - $vCurCount mod $pChunkSize"/>
    
             <xsl:choose>
               <xsl:when test="$vNewCount le $vAvailable">
                 <xsl:sequence select="$arg1, $arg2//text"/>
               </xsl:when>
               <xsl:otherwise>
                 <xsl:sequence select="$arg1"/>
                 <xsl:for-each select="1 to $vAvailable">
                  <xsl:sequence select="$vPadding/*"/>
                  </xsl:for-each>
                  <xsl:sequence select="$arg2//text"/>
               </xsl:otherwise>
             </xsl:choose>
        </xsl:template>
    </xsl:stylesheet>
    

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

    <result>
       <row>abc</row>
       <row>def</row>
       <row>xxx</row>
       <row>yyy</row>
       <row>zzz</row>
       <row>abc2</row>
       <row>def2</row>
       <row/>
       <row>abc3</row>
       <row>def3</row>
       <row>xxx3</row>
       <row>yyy3</row>
       <row>zzz3</row>
       <row> </row>
       <row> </row>
       <row> </row>
    </result>
    

    请注意

    1. 使用f:foldl()功能。

    2. f:foldl()的特殊DVC(Divide and Conquer)变体,以便出于所有实际目的避免递归堆栈溢出 - 例如,1000000(1M)的最大递归堆栈深度{{1元素只有19。

答案 1 :(得分:1)

根据需要在Java中构建完整的XML数据结构。然后,在XSL中通过准备好的XML进行简单迭代。

您可以节省大量精力并提供可维护的解决方案。

答案 2 :(得分:0)

正如所承诺的那样,一个简单的示例答案显示了如何调整Xalan以允许增加这样的全局迭代器:

<xsl:stylesheet version="1.0" xmlns:f="xalan://com.example.Functions">
  <!-- the global row counter variable -->
  <xsl:variable name="row" select="0"/>

  <xsl:template match="trx">
    <!-- wherever needed, the $row variable can be globally incremented -->
    <xsl:variable name="iteration" value="f:increment('row')"/>

    <!-- based upon this variable, calculations can be made -->
    <xsl:variable name="remaining-rows-in-chunk" 
                  value="40 - (($iteration - 1) mod 40) "/>
    <xsl:if test="count(.//text) &gt; $remaining-rows-in-chunk">
      <xsl:call-template name="empty-row">
        <xsl:with-param name="rows" select="$remaining-rows-in-chunk"/>
      </xsl:call-template>
    </xsl:if>

    <!-- process transaction now, that previous chunk has been filled [...] -->
  </xsl:template>

  <xsl:template name="empty-row">
    <xsl:param name="rows"/>

    <xsl:if test="$rows &gt; 0">
      <row/>
      <xsl:variable name="dummy" select="f:increment('row')"/>

      <xsl:call-template name="empty-row">
        <xsl:with-param name="rows" select="$rows - 1"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

com.example.Functions的内容:

public class Functions {
  public static String increment(ExpressionContext context, String nodeName) {
    XNumber n = null;

    try {
      // Access the $row variable
      n = ((XNumber) context.getVariableOrParam(new QName(nodeName)));

      // Make it "mutable" using this tweak. I feel horrible about
      // doing this, though ;-)
      Field m_val = XNumber.class.getDeclaredField("m_val");
      m_val.setAccessible(true);

      // Increment it
      m_val.setDouble(n, m_val.getDouble(n) + 1.0);
    } catch (Exception e) {
      log.error("Error", e);
    }

    return n == null ? null : n.str();
  }
}