Как сделать это в XSLT без увеличения переменных? (Настройка Xalan для создания глобального итератора XSLT. У меня есть другие варианты?) - PullRequest
1 голос
/ 01 ноября 2011

Я стараюсь максимально функционально, в терминах 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?NB: боюсь, рекурсия невозможна, учитывая объем обрабатываемых данных ... Но, возможно, я ошибаюсь

Ответы [ 3 ]

3 голосов
/ 01 ноября 2011

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 элементов и не помещается в оставшееся пространство первого блока - она ​​помещается в новый блок.

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. Специальный вариант DVC (Divide and Conquer) f:foldl(), позволяющий избежать переполнения стека рекурсии для всех практических целей - например, максимальная глубина стека рекурсии для 1000000 (1M) trx элементов всего 19.

1 голос
/ 01 ноября 2011

Создайте полную структуру данных XML, как вам нужно в Java. Затем выполните простую итерацию в XSL над подготовленным XML.

Вы можете сэкономить много усилий и обеспечить удобное решение.

0 голосов
/ 01 ноября 2011

Как и было обещано, в упрощенном примере ответа показано, как можно настроить 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();
  }
}
...