I have an approximately this kind of 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>
How can I group the charges by name field having fixed language? For instance group charges by english version of name using xlt 1.0? I suppose there wouldn't be an issues with xslt 2.0 where for-each-group is present. But in 1.0 I couldn't even create an xsl:key with complex instructions.
<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
Very approximately: I want my xslt rendering to transform it like this:
mobile 6
computer 14
It groups Charges by name and summurizes prices. An we have a complex rules for getting the translation: 1. We define a default language - if we have no this language specified in XML, we take a default language for xslt(manually set by developer). 2. If the node have no translation for default language, we check for translation on FallbackLanguage(always en-us). 3. If we didn't specify the translation before, we set a translated value to [NO NAME]
My idea was to incapsulate translation logic into the separate template:
<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>
So after that I wanted to have variable ChargesToDisplay consist of many pairs look like
<name>SomeName</name>
<Charge>.... Charge content ....<Charge>
and do all grouping on ChargesToDisplay. GetLocalizedEntity implementation:
<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>
I believe this should work. Please give it a try.
<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), '
')"/>
</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>
When run on this input XML (with a root node and a few extra <charges>
added to your sample):
<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>
Produces this output:
mobile 6
computer 14
[NO NAME] 8
shelf 13