Группировка по 2 полям в XSL

У меня есть следующий xml:

<page>
   <document>
      <id>1001</id>
      <cur>USD</cur>
      <date>01.01.2009</date>
      <amount>10</amount>
   </document>
   <document>
      <id>1001</id>
      <cur>USD</cur>
      <date>02.01.2009</date>
      <amount>15</amount>
   </document>
   <document>
      <id>1001</id>
      <cur>JPY</cur>
      <date>01.01.2009</date>
      <amount>5</amount>
   </document>
   <document>
      <id>1002</id>
      <cur>USD</cur>
      <date>01.01.2009</date>
      <amount>5</amount>
   </document>
   ...
</page>

И мне нужно преобразовать его в html. Записи должны быть сгруппированы по id и cur. И после каждой группы должна быть показана общая сумма. Итак, мы хотим что-то вроде этого:

Bill: id=1001, cur=USD
      date=01.01.2009   amount=10
      date=02.01.2009   amount=15
      total amount=25
Bill: id=1001, cur=JPY
      date=01.01.2009   amount=5
      total amount=5
Bill: id=1002, cur=USD
      date=01.01.2009   amount=5
      total amount=5
...

Как я могу добиться этого с помощью XSL?

Когда я пытался найти ответ в google, я нашел метод Muenchian, но он слишком сложен, когда мы хотим сгруппировать результат по 2 полям. Я новичок в xsl, и мне это немного сложно. Я также нашел оператора xslt 2.0 для каждой группы. Поддерживается ли он основными браузерами? Можно ли его использовать или нам следует полагаться только на xslt 1.0?


person Roman    schedule 10.11.2009    source источник
comment
XSLT 2.0 еще не так широко распространен, и полагаться на него было бы ошибкой, если только вы не можете контролировать, где происходит преобразование. На вашем месте я бы серьезно подумал и действительно подумал о том, чтобы разместить преобразование на стороне сервера.   -  person Peter Lindqvist    schedule 10.11.2009
comment
Нет, к сожалению, я не могу этого сделать, трансформация должна быть на стороне клиента (это не мое решение)   -  person Roman    schedule 10.11.2009
comment
Я знаю это чувство, у меня никогда не было успеха в попытках запустить преобразования на стороне клиента, по крайней мере, без какой-либо кросс-браузерной совместимости.   -  person Peter Lindqvist    schedule 10.11.2009


Ответы (2)


Вы можете сделать это с помощью XSLT 1.0

Метод, который я использую здесь, заключается в создании составного ключа с двумя полями, id и cur. Позже я применяю шаблоны к первому документу в каждой группе. Затем в шаблоне я перебираю отдельные документы и, наконец, суммирую поле суммы документов.

<?xml version='1.0'?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:key name="idcur" match="document" use="concat(id,cur)"/>

<xsl:template match="/page">
    <xsl:apply-templates select="document[generate-id() = generate-id(key('idcur',concat(id,cur))[1])]"/>
</xsl:template>

<xsl:template match="document">
<xsl:variable name="document" select="key('idcur',concat(id,cur))"/>
Bill: id=<xsl:value-of select="id"/>, cur=<xsl:value-of select="cur"/>
    <xsl:for-each select="$document">
      date=<xsl:value-of select="date"/>   amount=<xsl:value-of select="amount"/>
    </xsl:for-each>
      total amount=<xsl:value-of select="sum($document/amount)"/>
</xsl:template>
</xsl:stylesheet>

Выход:

Bill: id=1001, cur=USD
      date=01.01.2009   amount=10
      date=02.01.2009   amount=15
      total amount=25
Bill: id=1001, cur=JPY
      date=01.01.2009   amount=5
      total amount=5
Bill: id=1002, cur=USD
      date=01.01.2009   amount=5
      total amount=5
person Peter Lindqvist    schedule 10.11.2009
comment
Для удобочитаемости кода я рекомендую использовать <xsl:text> вместо литеральных строк в XSL. Таким образом, формат вывода намного легче контролировать, а формат кода может иметь правильные отступы. - person Tomalak; 10.11.2009
comment
Верный момент! Но я чувствую, что это не окончательное решение в любом случае. - person Peter Lindqvist; 10.11.2009
comment
Большое спасибо! Это не только хорошее решение, но и полезный пример для всей теории, которую я читал ранее о xsl. - person Roman; 11.11.2009
comment
Нет проблем, я часто борюсь с такими проблемами, рад, что смог помочь. Но обязательно проверьте предложенное решение от erlock! - person Peter Lindqvist; 11.11.2009

Объединение двух полей для создания составного ключа небезопасно, если только вы не уверены, что все такие созданные ключи уникальны. Этот метод безопаснее, я думаю:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="text"/>

<xsl:key name="ids" match="document" use="id"/>
<xsl:key name="currencies" match="document" use="cur"/>

<xsl:template match="/page">
    <xsl:apply-templates select="document[generate-id() = generate-id(key('ids',id)[1])]"/>
</xsl:template>

<xsl:template match="document">
  <xsl:for-each select="key('ids',id)[generate-id() = generate-id(key('currencies', cur)[id=current()/id][1])]">
    <xsl:variable name="bills" select="key('ids', id)[cur = current()/cur]"/>
    <xsl:text>Bill: id=</xsl:text>
    <xsl:value-of select="id"/>
    <xsl:text>, cur=</xsl:text>
    <xsl:value-of select="cur"/>
    <xsl:for-each select="$bills">
      <xsl:text>&#10;date=</xsl:text>
      <xsl:value-of select="date"/>
      <xsl:text>   amount=</xsl:text>
      <xsl:value-of select="amount"/>
    </xsl:for-each>
    <xsl:text>&#10;total amount=</xsl:text>
    <xsl:value-of select="sum($bills/amount)"/>
    <xsl:text>&#10;</xsl:text>
  </xsl:for-each>
</xsl:template>
</xsl:stylesheet>
person Erlock    schedule 10.11.2009