1

我试图用 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 的倍数的全局迭代器变量,并在需要时插入空行(我提供了一个答案,展示了如何调整 XSLT/Xalan 以允许此类变量)。但是如何使用 XSLT 做到这一点?注意:考虑到我正在处理的数据的大小,恐怕递归是不可能的......但也许我错了

4

3 回答 3

3

I. 这是一个 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元素,不适合第一个块的剩余空间——它被放入一个新块中。

二、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. 一个特殊的 DVC(分而治之)变体,f:foldl()以便在所有实际用途中避免递归堆栈溢出 - 例如,1000000 (1M)trx个元素的最大递归堆栈深度仅为 19。

于 2011-11-01T18:11:43.903 回答
1

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

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

于 2011-11-01T10:55:06.940 回答
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();
  }
}
于 2011-11-01T10:11:58.010 回答