Как сгруппировать узлы в xslt 1.0 по счетному полю?

У меня есть примерно такой xml:

<charge>
    <price></price>
    <amount></amount>
    <name>
       <KeyValuePair>
         <Key>
            en-us
         </Key>
         <Value>
            Name in english
         </Value>
       </KeyValuePair>
       <KeyValuePair>
         <Key>
            ru-ru
         </Key>
         <Value>
            Name in russian
         </Value>
       </KeyValuePair>
    </name>
</charge>

Как я могу сгруппировать начисления по полю имени с фиксированным языком? Например, групповые сборы по английской версии имени с использованием xlt 1.0? Я полагаю, что не будет проблем с xslt 2.0, где присутствует для каждой группы. Но в версии 1.0 я даже не мог создать xsl:key со сложными инструкциями.

<charge>
  <price>2</price>
  <amount>3</amount>
  <name>
  <KeyValuePair>
    <key>en-us</key>
    <value>mobile</value>    
  </KeyValuePair>
  </name>
</charge>
<charge>
  <price>4</price>
  <amount>3</amount>
  <name>
  <KeyValuePair>
    <key>en-us</key>
    <value>mobile</value>    
  </KeyValuePair>
  </name>
</charge>
<charge>
  <price>6</price>
  <amount>3</amount>
  <name>
  <KeyValuePair>
    <key>en-us</key>
    <value>computer</value>    
  </KeyValuePair>
  </name>
</charge>
<charge>
  <price>8</price>
  <amount>3</amount>
  <name>
  <KeyValuePair>
    <key>en-us</key>
    <value>computer</value>    
  </KeyValuePair>
  </name>
</charge>

en-us

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

mobile  6
computer 14

Он группирует сборы по названию и суммирует цены. У нас есть сложные правила для получения перевода: 1. Мы определяем язык по умолчанию - если у нас нет этого языка, указанного в XML, мы берем язык по умолчанию для xslt (задается разработчиком вручную). 2. Если узел не имеет перевода для языка по умолчанию, мы проверяем наличие перевода на FallbackLanguage (всегда en-us). 3. Если мы не указывали перевод ранее, мы устанавливаем переведенное значение [NO NAME]

Моя идея заключалась в том, чтобы инкапсулировать логику перевода в отдельный шаблон:

<xsl:variable name="ChargesForDisplay">
    <xsl:for-each select="/i:Invoice/i:Charges/i:Charge[not(@*[1]='TaxCharge')]">
      <chargeset>
        <chargeName>
          <xsl:call-template name="GetLocalizedEntity">
            <xsl:with-param name="ContainerPath" select="./i:Product/i:Name"></xsl:with-param>
          </xsl:call-template>
        </chargeName>
        <charge>
          <xsl:value-of select="current()"/>
        </charge>
      </chargeset>      
    </xsl:for-each>
  </xsl:variable> 

Итак, после этого я хотел, чтобы переменная ChargesToDisplay состояла из множества пар, выглядящих как

<name>SomeName</name>
<Charge>.... Charge content ....<Charge>

и сделать всю группировку на ChargesToDisplay. Реализация GetLocalizedEntity:

  <xsl:template name ="GetLocalizedEntity">
    <xsl:param name="ContainerPath"></xsl:param>

    <xsl:choose>
      <xsl:when test="$ContainerPath/a:KeyValueOfstringstring[a:Key=$TemplateLanguage]/a:Value != ''">
        <xsl:value-of select="$ContainerPath/a:KeyValueOfstringstring[a:Key=$TemplateLanguage]/a:Value"/>
      </xsl:when>

      <xsl:when test="$ContainerPath/a:KeyValueOfstringstring[a:Key=$FallBackLanguage]/a:Value != ''">
        <xsl:value-of select="$ContainerPath/a:KeyValueOfstringstring[a:Key=$FallBackLanguage]/a:Value"/>
      </xsl:when>

      <xsl:otherwise>
        <xsl:text>[NO NAME]</xsl:text>
      </xsl:otherwise>
    </xsl:choose>

  </xsl:template>

person valerii.sverdlik    schedule 18.01.2013    source источник
comment
Не могли бы вы привести пример желаемого результата?   -  person JLRishe    schedule 18.01.2013
comment
Большое спасибо за обновленное объяснение. Теперь я понимаю это намного лучше. Я скоро попробую. Какой процессор XSLT вы будете использовать? Я спрашиваю, потому что мне интересно, реализует ли он какую-то форму функции node-set(). Кроме того, необходимо ли передавать ContainerPath в качестве параметра? Оценка XPath из строкового значения не предусмотрена в большинстве (любых?) реализаций XSLT 1.0.   -  person JLRishe    schedule 18.01.2013
comment
извините за большую задержку с ответом. Я использую XSLT 1.0 в приложении ASP.Net. Incide CotnainerPath Я просто отправляю строку XPath. Если это может быть полезно для вас, я могу опубликовать реализацию шаблона GetLocalizedEntity.   -  person valerii.sverdlik    schedule 18.01.2013
comment
Да, конечно, я хотел бы увидеть реализацию GetLocalizedEntry. Спасибо.   -  person JLRishe    schedule 18.01.2013
comment
Обновлено с реализацией   -  person valerii.sverdlik    schedule 18.01.2013
comment
Хорошо, я считаю, что мой обновленный XSLT должен работать. Я внес некоторые изменения в методы ChargesForDisplay и GetLocalizedEntry, чтобы минимизировать код, и я думаю, что вы использовали <xsl:value-of> в одном месте, где вам нужно было <xsl:copy-of>.   -  person JLRishe    schedule 18.01.2013


Ответы (1)


Я считаю, что это должно сработать. Пожалуйста, попробуйте.

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="text" />

  <xsl:param name="templateLanguage" select="'ru-ru'" />
  <xsl:param name="fallbackLanguage" select="'en-us'" />

  <xsl:key name="itemName" match="chargeSet" use="chargeName"/>

  <xsl:template match="/">
    <!-- Retrieve chargesForDisplay -->
    <xsl:variable name="chargesForDisplay">
      <xsl:apply-templates select="//charge" mode="buildForDisplay" />
    </xsl:variable>

    <root>
      <xsl:apply-templates select="msxsl:node-set($chargesForDisplay)/*" />
    </root>
  </xsl:template>

  <xsl:template match="text()" />

  <xsl:template
   match="chargeSet[generate-id(.)=generate-id(key('itemName',chargeName)[1])]">
    <xsl:variable name="matchingItems" select="key('itemName', chargeName)" />
    <xsl:value-of 
       select="concat(chargeName, ' ', sum($matchingItems/charge/price), '&#xA;')"/>
  </xsl:template>

  <xsl:template match="charge" mode="buildForDisplay">
    <chargeSet>
      <chargeName>
        <xsl:call-template name="GetLocalizedEntry">
          <!-- Pass in all KeyValuePairs with present, non-blank values-->
          <xsl:with-param name="keyValuePairs" 
             select="name/KeyValuePair[normalize-space(value)]" />
        </xsl:call-template>
      </chargeName>
      <xsl:copy-of select="." />
    </chargeSet>
  </xsl:template>

  <xsl:template name="GetLocalizedEntry">
    <xsl:param name="keyValuePairs" />

    <xsl:variable name="templateLanguageMatch" 
      select="$keyValuePairs[key = $templateLanguage]/value" />
    <xsl:variable name="fallbackLanguageMatch" 
      select="$keyValuePairs[key = $fallbackLanguage]/value" />

    <xsl:choose>
      <xsl:when test="$templateLanguageMatch">
        <xsl:value-of select="$templateLanguageMatch"/>
      </xsl:when>
      <xsl:when test="$fallbackLanguageMatch">
        <xsl:value-of select="$fallbackLanguageMatch"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:text>[NO NAME]</xsl:text>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

При запуске на этом входном XML (с корневым узлом и несколькими дополнительными <charges>, добавленными к вашему образцу):

<charges>
  <charge>
    <price>2</price>
    <amount>3</amount>
    <name>
      <KeyValuePair>
        <key>en-us</key>
        <value>mobile</value>
      </KeyValuePair>
    </name>
  </charge>
  <charge>
    <price>4</price>
    <amount>3</amount>
    <name>
      <KeyValuePair>
        <key>en-us</key>
        <value>mobile</value>
      </KeyValuePair>
    </name>
  </charge>
  <charge>
    <price>6</price>
    <amount>3</amount>
    <name>
      <KeyValuePair>
        <key>en-us</key>
        <value>computer</value>
      </KeyValuePair>
    </name>
  </charge>
  <charge>
    <price>8</price>
    <amount>3</amount>
    <name>
      <KeyValuePair>
        <key>en-us</key>
        <value>computer</value>
      </KeyValuePair>
    </name>
  </charge>
  <charge>
    <price>8</price>
    <amount>3</amount>
    <name>
      <KeyValuePair>
        <key>ja-jp</key>
        <value>計算機</value>
      </KeyValuePair>
    </name>
  </charge>
  <charge>
    <price>13</price>
    <amount>3</amount>
    <name>
      <KeyValuePair>
        <key>ru-ru</key>
        <value>shelf</value>
      </KeyValuePair>
    </name>
  </charge>
</charges>

Производит этот вывод:

mobile 6
computer 14
[NO NAME] 8
shelf 13
person JLRishe    schedule 18.01.2013
comment
извините, но, вероятно, я недостаточно хорошо объяснил проблему. У нас есть довольно сложные правила для определения перевода имени узла, мы не можем фильтровать его только по языку. Моя идея заключалась в том, чтобы создать переменную, которая содержала бы 2 узла xml: ‹name›(уже подсчитано) и ‹charge›. И затем сделать группировку на нем. Является ли это возможным? - person valerii.sverdlik; 18.01.2013
comment
Боюсь, я не совсем понимаю, что вы описываете. Не могли бы вы изменить свой пример, чтобы он иллюстрировал этот дополнительный момент? В вашем образце вывода похоже, что вы просуммировали значение <price>, сгруппировав по <value>. Если это что-то сверх этого, пожалуйста, покажите это. - person JLRishe; 18.01.2013
comment
Обновлено описание, чтобы попытаться сделать его более понятным - person valerii.sverdlik; 18.01.2013
comment
сегодня потратил много времени, но не могу справиться с вашим примером - он печатает много лишнего xml. Еще одного не могу понять - зачем нужна инструкция ‹xsl:template match=text() /›? - person valerii.sverdlik; 20.01.2013
comment
Мой XSLT работает для примера XML. Если у вас возникли проблемы с его адаптацией к вашему реальному XML, предоставьте образец того XML, с которым у вас возникли проблемы, и XSLT, который у вас уже есть. Целью <xsl:template match="text()" /> является предотвращение вывода лишнего текста из-за поведения шаблона XSLT по умолчанию. - person JLRishe; 20.01.2013
comment
могу ли я отправить вам пример по электронной почте? Это довольно большой кусок кода? - person valerii.sverdlik; 20.01.2013
comment
Не могли бы вы разместить его на Pastebin.com? - person JLRishe; 20.01.2013
comment
Конечно, мой xslt: pastebin.com/wnsrSb6D и мой xml: pastebin.com/pNpD5ywW. Понятия не имею, как там появляется лишний код - person valerii.sverdlik; 20.01.2013
comment
Спасибо. Я понял проблему. Чтобы исправить это, вам просто нужно добавить это в свой XSLT: <xsl:template match="text()" mode="SummaryAmounts" />. Похоже, вы случайно добавили лишний вопросительный знак в свой шаблон i:Charge или это было сделано намеренно? - person JLRishe; 20.01.2013
comment
йееее-хааа. Это действительно работает. Не могу понять почему, но я прочитаю об этом вечером. Конечно, знак вопроса был поставлен намеренно. Твоя помощь была самой большой, которую я когда-либо получал здесь. Большое тебе спасибо! - person valerii.sverdlik; 20.01.2013