XSLT 2.0 — Отличительные значения с использованием ключа

У меня возникли некоторые проблемы при работе с листом XSLT. Это мой XML-документ:

<?xml version="1.0" encoding="UTF-8"?>
<catalog>
  <products>
    <product>
      <id>1</id>
    </product>
  </products>
  <stocks>
    <stock>
      <id>1</id>
      <size>S</size>
      <store>NYC</store>
    </stock>
    <stock>
      <id>1</id>
      <size>L</size>
      <store>NYC</store>
    </stock>
    <stock>
      <id>1</id>
      <size>S</size>
      <store>LA</store>
    </stock>
  </stocks>
</catalog>

Я хочу иметь такой выходной XML:

<?xml version="1.0" encoding="UTF-8"?>
<catalog>
  <products>
    <product>
      <id>1</id>
      <variants>
        <variant>
          <size>S</size>
          <stocks>
            <stock store-ref="NYC">
            <stock store-ref="LA">
          </stocks>
        <variant>
        <variant>
          <size>L</size>
          <stocks>
            <stock store-ref="NYC">
          </stocks>
        <variant>
      </variants>
    </product>
  </products>
</catalog> 

Сегодня я использую этот XSLT для выполнения этого преобразования:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" exclude-result-prefixes="xs fn" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
  <xsl:key name="sizes" match="stock" use="id"/>
  <xsl:key name="stocks" match="stock" use="fn:concat(id, '-', size)"/>
  <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <catalog>
      <products>
        <xsl:for-each select="/catalog/products/product">
          <product>
            <id><xsl:value-of select="id" /></id>
            <variants>
              <xsl:for-each select="key('sizes', id)">
                <variant>
                  <size><xsl:value-of select="size" /></size>
                  <stocks>
                    <xsl:for-each select="key('stocks', fn:concat(id, '-', size))">
                      <stock store-ref="{store}" />
                    </xsl:for-each>
                  </stocks>
                </variant>
              </xsl:for-each>
            </variants>
          </product>
        </xsl:for-each>
      </products>
    </catalog>
  </xsl:template>
</xsl:stylesheet>

и я получил этот результат:

<?xml version="1.0" encoding="UTF-8"?>
<catalog>
   <products>
      <product>
         <id>1</id>
         <variants>
            <variant>
               <size>S</size>
               <stocks>
                  <stock store-ref="NYC"/>
                  <stock store-ref="LA"/>
               </stocks>
            </variant>
            <variant>
               <size>L</size>
               <stocks>
                  <stock store-ref="NYC"/>
               </stocks>
            </variant>
            <variant>
               <size>S</size>
               <stocks>
                  <stock store-ref="NYC"/>
                  <stock store-ref="LA"/>
               </stocks>
            </variant>
         </variants>
      </product>
   </products>
</catalog>

Итак, моя проблема в том, что я хотел бы выбрать разные значения размера, но, похоже, это не работает. Я пытался использовать generate-id(), но я действительно не понимаю, как это работает, поэтому у меня не было хороших результатов :( Есть идеи, как это исправить? Спасибо!


person Remi    schedule 18.06.2014    source источник
comment
Вы пробовали использовать <xsl:for-each-group>? Посмотрите: stackoverflow.com/questions/19115109/   -  person Tomalak    schedule 18.06.2014
comment
У меня нет информации о размере в моем теге продукта, и если мне придется перебирать каждую акцию для каждого продукта, это займет вечность (на самом деле у меня 5 тысяч товаров и около 1 миллиона акций) :(   -  person Remi    schedule 18.06.2014
comment
Я попытался заменить свой for-each на <xsl:for-each select="stock[generate-id() = generate-id(key('sizes', id)[1])]">, но это не сработало: список вариантов пуст   -  person Remi    schedule 18.06.2014
comment
Хорошо, я понимаю, что вы имели в виду. Я попытался заменить свой foreach на <xsl:for-each-group select="key('sizes', id)" group-by="size">, и теперь он работает! Спасибо !   -  person Remi    schedule 18.06.2014


Ответы (2)


Благодаря @Tomalak я нашел решение. Вот мой рабочий XSLT:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" exclude-result-prefixes="xs fn" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:fn="http://www.w3.org/2005/xpath-functions">
  <xsl:key name="sizes" match="stock" use="id"/>
  <xsl:key name="stocks" match="stock" use="fn:concat(id, '-', size)"/>
  <xsl:output method="xml" encoding="UTF-8" indent="yes"/>
  <xsl:template match="/">
    <catalog>
      <products>
        <xsl:for-each select="/catalog/products/product">
          <product>
            <id><xsl:value-of select="id" /></id>
            <variants>
              <xsl:for-each-group select="key('sizes', id)" group-by="size">
                <variant>
                  <size><xsl:value-of select="size" /></size>
                  <stocks>
                    <xsl:for-each select="key('stocks', fn:concat(id, '-', size))">
                      <stock store-ref="{store}" />
                    </xsl:for-each>
                  </stocks>
                </variant>
              </xsl:for-each-group>
            </variants>
          </product>
        </xsl:for-each>
      </products>
    </catalog>
  </xsl:template>
</xsl:stylesheet>
person Remi    schedule 18.06.2014
comment
Это один из действительно редких случаев, когда, слегка подтолкнув кого-то с помощью комментариев, он на самом деле приводит к созданию решения самостоятельно (и публикации его!). Вы не представляете, насколько необычно такое поведение на этом сайте. Спасибо. - person Tomalak; 18.06.2014
comment
Тогда спасибо за наводку ;) - person Remi; 19.06.2014

В дополнение к решению, которое вы нашли сами, я хотел бы показать вам многоуровневую группировку.

Для удобства вам понадобится только один ключ:

<xsl:key name="kStock" match="stock" use="id"/>

и это:

<xsl:template match="product">
  <xsl:copy>
    <xsl:copy-of select="id" />
    <variants>
      <xsl:for-each-group select="key('kStock', id)" group-by="size">
        <variant>
          <xsl:copy-of select="size" />
          <stocks>
            <xsl:for-each-group select="current-group()" group-by="store">
              <stock store-ref="{current-grouping-key()}" />
            </xsl:for-each-group>
          </stocks>
        </variant>
      </xsl:for-each-group>
    </variants>
  </xsl:copy>
</xsl:template>

Обратите внимание на использование current-group() и current-grouping-key().

person Tomalak    schedule 18.06.2014
comment
Спасибо еще раз. Завтра утром протестирую! - person Remi; 19.06.2014