Как выполнить XPath sum () для всех предыдущих узлов в цикле XSL for-each? - PullRequest
2 голосов
/ 19 октября 2011

Исходный XML (это просто данные foobar, в действительности это тысячи строк, которые могут быть как положительными, так и отрицательными):

<accounting>
    <entry id="1">
        <accounting_date>2010-10-29</accounting_date>
        <transfer_date>2010-10-29</transfer_date>
        <description>Start balance</description>
        <vat>0</vat>
        <sum>87287</sum>
    </entry>
    <entry id="2">
        <accounting_date>2011-01-24</accounting_date>
        <transfer_date>2011-02-17</transfer_date>
        <description>Bill 1</description>
        <vat>175</vat>
        <sum>875</sum>
    </entry>
    <entry id="3">
        <accounting_date>2011-01-31</accounting_date>
        <transfer_date>2011-01-18</transfer_date>
        <description>Bill 2</description>
        <vat>350</vat>
        <sum>1750</sum>
    </entry>
</accounting>

Я хочу преобразовать этот XML в таблицу HTML для отображенияпользователю.Большая часть преобразования просто помещает значения в нужных местах, но поле баланса вызывает у меня головную боль.

Мой XSLT (не работает):

<table>
    <tr>
        <th>Accounting date</th>
        <th>Description</th>
        <th>Sum</th>
        <th>Balanche</th>
    </tr>
    <xsl:for-each select="/accounting/entry">
        <tr>
            <td><xsl:value-of select="accounting_date" /></td>
            <td><xsl:value-of select="description" /></td>
            <td><xsl:value-of select="sum" /></td>
            <td><xsl:value-of select="sum(../entry[position() &lt; current()/position()]/sum)" /></td><!-- This XPath is the problem! -->
        </tr>
    </xsl:for-each>
</table>

Ожидаемый результат:

<table>
    <tr>
        <th>Accounting date</th>
        <th>Description</th>
        <th>Sum</th>
        <th>Balanche</th>
    </tr>
    <tr>
        <td>2010-10-29</td>
        <td>Start balance</td>
        <td>87287</td>
        <td>87287</td>
    </tr>
    <tr>
        <td>2011-01-24</td>
        <td>Bill 1</td>
        <td>875</td>
        <td>88162</td>
    </tr>
    <tr>
        <td>2011-01-31</td>
        <td>Bill 2</td>
        <td>1750</td>
        <td>89912</td>
    </tr>
</table>

Chrome пуст, и Firefox дает мне:

Error loading stylesheet: XPath parse failure: Name or Nodetype test expected:

Я застрял, пожалуйста, помогите.:)

Ответы [ 3 ]

4 голосов
/ 19 октября 2011

Лучшее решение может немного зависеть от того, используете ли вы XSLT 1.0 или XSLT 2.0.Вы действительно должны сказать, так как в настоящее время есть примерно одинаковое сочетание обоих в поле.(Кажется, вы запускаете его в браузере, который предлагает вам решение 1.0, поэтому я и дал вам это).

Но в любом случае, рекурсия - ваш друг.В данном случае это «рекурсия брата», где вы пишете шаблон для обработки записи, а он применяет шаблоны для обработки следующей записи, передавая итоговое значение в качестве параметра: что-то вроде

<xsl:template match="entry">
  <xsl:param name="total-so-far" select="0"/>
         <tr>
            <td><xsl:value-of select="accounting_date" /></td>
            <td><xsl:value-of select="description" /></td>
            <td><xsl:value-of select="sum" /></td>
            <td><xsl:value-of select="$total-so-far + sum"/></td><
        </tr>  
    <xsl:apply-templates select="following-sibling::entry[1]">
      <xsl:with-param name="total-so-far" select="$total-so-far + sum"/>
    </xsl:apply-templates>
</xsl:template>

Затем вам нужно запустить процесс с

<xsl:template match="accounting">
 <table>
   <xsl:apply-templates select="entry[1]"/>
 </table>
</xsl:template>

Если есть тысячи строк, это может привести к переполнению стека в процессоре XSLT, который не выполняет оптимизацию хвостовых вызовов.Я понятия не имею, реализуют ли эту оптимизацию процессоры XSLT в современных браузерах.

1 голос
/ 20 марта 2013

В качестве альтернативы вы можете использовать оси предшествующего брата

<table>
    <tr>
        <th>Accounting date</th>
        <th>Description</th>
        <th>Sum</th>
        <th>Balanche</th>
    </tr>
    <xsl:for-each select="/accounting/entry">
        <tr>
            <td>
                <xsl:value-of select="accounting_date" />
            </td>
            <td>
                <xsl:value-of select="description" />
            </td>
            <td>
                <xsl:value-of select="sum" />
            </td>
            <td>
                <xsl:value-of select="sum(preceding-sibling::*/sum)+sum" />
            </td>
        </tr>
    </xsl:for-each>
</table>
0 голосов
/ 19 октября 2011

В дополнение к правильному ответу @Michael Kay, здесь приведен общий шаблон / функция из FXSL для использования при вычислении текущих сумм.Его вариант DVC никогда не будет (для практических целей) падать из-за переполнения стека.С рекурсией DVC (Разделяй и властвуй) обработка последовательности из 1000000 (1M) элементов требует максимальной глубины стека всего 19.

Вот пример использования scanl template :

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
  <xsl:import href="scanlDVC.xsl"/>
  <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <myAdd:myAdd/>

  <myParam:myParam>0</myParam:myParam>

  <xsl:template match="/">

    <xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
    <xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>


    <xsl:call-template name="scanl">
      <xsl:with-param name="pFun" select="$vFun"/>
      <xsl:with-param name="pQ0" select="$vZero" />
      <xsl:with-param name="pList" select="/*/num"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="myAdd:*" mode="f:FXSL">
    <xsl:param name="pArg1" select="0"/>
    <xsl:param name="pArg2" select="0"/>

    <xsl:value-of select="$pArg1 + $pArg2"/>
  </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>

правильный результат (промежуточные итоги) получается :

<el>0</el>
<el>1</el>
<el>3</el>
<el>6</el>
<el>10</el>
<el>15</el>
<el>21</el>
<el>28</el>
<el>36</el>
<el>45</el>
<el>55</el>

Использование его для предоставленного XML-документа :

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:myAdd="f:myAdd"
xmlns:myParam="f:myParam"
>
  <xsl:import href="scanlDVC.xsl"/>
  <xsl:output omit-xml-declaration="yes" indent="yes"/>

  <myAdd:myAdd/>

  <myParam:myParam>0</myParam:myParam>

  <xsl:template match="/">

    <xsl:variable name="vFun" select="document('')/*/myAdd:*[1]"/>
    <xsl:variable name="vZero" select="document('')/*/myParam:*[1]"/>


    <xsl:call-template name="scanl">
      <xsl:with-param name="pFun" select="$vFun"/>
      <xsl:with-param name="pQ0" select="$vZero" />
      <xsl:with-param name="pList" select="/*/*/sum"/>
    </xsl:call-template>
  </xsl:template>

  <xsl:template match="myAdd:*" mode="f:FXSL">
    <xsl:param name="pArg1" select="0"/>
    <xsl:param name="pArg2" select="0"/>

    <xsl:value-of select="$pArg1 + $pArg2"/>
  </xsl:template>
</xsl:stylesheet>

и получается правильный результат: :

<el>0</el>
<el>87287</el>
<el>88162</el>
<el>89912</el>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...