0

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>
valerii.sverdlik
  • 559
  • 4
  • 18
  • Could you provide an example of the output you want? – JLRishe Jan 18 '13 at 14:03
  • Thank you very much for the updated explanation. I understand it a lot better now. I will give this a try soon. What XSLT processor will you be using? I ask because I'm wondering if it implements some form of the `node-set()` function. Also, is it a requirement to be able to pass in `ContainerPath` as a parameter? Evaluation of XPath from a string value isn't provided in most (any?) XSLT 1.0 implementations. – JLRishe Jan 18 '13 at 15:41
  • sorry for the big delay in answer. I'm using XSLT 1.0 in ASP.Net application. Incide CotnainerPath I'm just sending an XPath string. If it's can be useful for you I can post an implementation of GetLocalizedEntity template – valerii.sverdlik Jan 18 '13 at 16:19
  • Yes, certainly I'd like to see the implementation of GetLocalizedEntry. Thanks. – JLRishe Jan 18 '13 at 17:06
  • Updated with implementation – valerii.sverdlik Jan 18 '13 at 17:09
  • Ok, I believe my updated XSLT should work. I made some changes to the way ChargesForDisplay and GetLocalizedEntry to minmize the code, and I think you were using `` in one place where you needed ``. – JLRishe Jan 18 '13 at 17:46

1 Answers1

1

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), '&#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>

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
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • sorry, but probably I explained the issue not good enough. We have a pretty complex rules to define the translation of the node Name, we can't only filter it by language. My idea was to create a variable, which would contain 2 xml nodes: (already counted) and . And then do a grouping on it. Is it possible? – valerii.sverdlik Jan 18 '13 at 15:10
  • I'm afraid I don't entirely understand what you're describing. Could you modify your example so that it illustrates this additional point? In your sample output, it looks like you've summed the `` value, grouping on ``. If it's something beyond that, please show it. – JLRishe Jan 18 '13 at 15:13
  • Updated the description in order to try to make it more clear – valerii.sverdlik Jan 18 '13 at 15:23
  • spent lot's of time today, but can't get done with your example - it prints out a lot of extra xml. One more thing I can't understand - why do we need instruction? – valerii.sverdlik Jan 20 '13 at 17:32
  • 1
    My XSLT works for the sample XML. If you're having trouble adapting it to your actual XML, please provide a sample of that XML that you're having trouble getting to work and the XSLT that you have so far. The purpose of the `` is to prevent any extraneous text output on account of the XSLT default template behavior. – JLRishe Jan 20 '13 at 17:38
  • can I send you an example over email? It's pretty big piece of code? – valerii.sverdlik Jan 20 '13 at 17:41
  • Could you put it up on Pastebin.com? – JLRishe Jan 20 '13 at 17:44
  • Sure, my xslt: http://pastebin.com/wnsrSb6D and my xml: http://pastebin.com/pNpD5ywW. Have no idea how do the extra code appears there – valerii.sverdlik Jan 20 '13 at 17:50
  • 1
    Thanks. I figured out the issue. To fix it, you just need to add this to your XSLT: ``. It looks like you might have accidentally added a stray question mark to your `i:Charge` template, or was that intentional? – JLRishe Jan 20 '13 at 18:06
  • yeeeee-haaa. That really works. I can't understand why, but I will read about it in the evening. Of course, question mark was intentional. Your help was the biggest I ever had here. Thank you very much! – valerii.sverdlik Jan 20 '13 at 18:18