Search code examples
xsltxpathxslt-grouping

XSLT sibling from grandparent level


I have an xml that looks like the following. I need to find all the distinct Currencies. Using the following

 <xsl:for-each select="$itemPrices/Relationships/Relationship/Target
                       /Properties/PropertyItem[cs:Key='Currency']/cs:Value">

I have been able to get all the currency types, but there are duplicates. I need to find distinct values using XSLT 1.0. I came across solution that used preceding and following siblings, but I was able to get siblings at the same level. I was unable to construct an XPath that would go three of four level up and then look at the comparable next sibling.

  <Relationship>
    <ModelName>Entities.Relationship</ModelName>
    <Properties />
    <Target>
      <ModelName>ItemPrice</ModelName>
      <Properties>
        <PropertyItem>
          <Key>Currency</Key>
          <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">US</Value>
        </PropertyItem>
        <PropertyItem>
          <Key>PriceValue</Key>
          <Value i:type="a:decimal" xmlns:a="http://www.w3.org/2001/XMLSchema">13.51</Value>
        </PropertyItem>
        <PropertyItem>
          <Key>ProductId</Key>
          <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">0600</Value>
        </PropertyItem>
      </Properties>
    </Target>
  </Relationship>
  <Relationship>
    <ModelName>Entities.Relationship</ModelName>
    <Properties />
    <Target>
      <ModelName>ItemPrice</ModelName>
      <Properties>
        <PropertyItem>
          <Key>Currency</Key>
          <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">US</Value>
        </PropertyItem>
        <PropertyItem>
          <Key>PriceValue</Key>
          <Value i:type="a:decimal" xmlns:a="http://www.w3.org/2001/XMLSchema">11.82</Value>
        </PropertyItem>
        <PropertyItem>
          <Key>ProductId</Key>
          <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">0600</Value>
        </PropertyItem>
      </Properties>
    </Target>
  </Relationship>
  <Relationship>
    <ModelName>Entities.Relationship</ModelName>
    <Properties />
    <Target>
      <ModelName>ItemPrice</ModelName>
      <Properties>
        <PropertyItem>
          <Key>Currency</Key>
          <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">Canadian</Value>
        </PropertyItem>
        <PropertyItem>
          <Key>PriceValue</Key>
          <Value i:type="a:decimal" xmlns:a="http://www.w3.org/2001/XMLSchema">10.95</Value>
        </PropertyItem>
        <PropertyItem>
          <Key>ProductId</Key>
          <Value i:type="a:string" xmlns:a="http://www.w3.org/2001/XMLSchema">0600</Value>
        </PropertyItem>
      </Properties>
    </Target>
  </Relationship>

So in the above XML, I should get US and Canada just once, not US twice and Canada once. How can I do that?


Solution

  • While you could use preceding:: instead of preceding-sibling::, the efficient way to select distinct values in XSLT 1.0 is to use Muenchian grouping:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
      <xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
      <xsl:key name="kCurrency" match="PropertyItem[Key = 'Currency']/Value"
               use="."/>
    
      <xsl:template match="/">
        <xsl:variable name="allCurrencies"
                      select="Relationships/Relationship/Target/Properties
                              /PropertyItem[Key = 'Currency']/Value" />
        <xsl:for-each select="$allCurrencies[generate-id() = 
                      generate-id(key('kCurrency', .)[1])]">
          <currency>
            <xsl:value-of select="."/>
          </currency>
        </xsl:for-each>
      </xsl:template>
    </xsl:stylesheet>
    

    When a <Relationships> element is wrapped around your sample XML and fed into this XSLT, the result is:

    <currency>US</currency>
    <currency>Canadian</currency>