Search code examples
xmlxpathxslt-2.0

Using distinct-values within an xpath predicate


I'm working with XSLT/Xpath 2.0 and am trying to write an xpath statement that will allow me to filter some duplicate nodesets I have in my XML. Here's an example XML:

<?xml version="1.0" encoding="utf-8"?>
<test>
    <card>
        <TimeCard>1234</TimeCard>
        <tax>HST</tax>
        <adjAmt>-112</adjAmt>
    </card>
    <card>
        <TimeCard>1234</TimeCard>
        <tax>GST</tax>
        <adjAmt>-112</adjAmt>
    </card>
    <card>
        <TimeCard>4321</TimeCard>
        <tax>HST</tax>
        <adjAmt>-50</adjAmt>
    </card>
    <card>
        <TimeCard>4321</TimeCard>
        <tax>GST</tax>
        <adjAmt>-50</adjAmt>
    </card>
    <card>
        <TimeCard>2121</TimeCard>
        <tax>GST</tax>
        <adjAmt>-55</adjAmt>
    </card>
</test>

I need an xpath that would return "adjAmt" but only once per "TimeCard". My host system creates duplicate "card" entries for "adjAmt" when different types of taxes are charged. In the above example, I only one it to return -112, -50, and -55; one entry per "timecard".

I've tried using distinct-values in a multitude of different spots against the timecard element but have not had any luck.

Anyone have any suggestions on what might work best?


Solution

  • In XSLT 2 or 3 you could use for-each-group group-by to group the card elements by the TimeCard child element and then return the context node's adjAmt child or its number value as follows:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        exclude-result-prefixes="xs mf"
        version="3.0">
    
      <xsl:output method="text"/>
    
      <xsl:function name="mf:group-and-select-first" as="xs:decimal*">
          <xsl:param name="cards" as="element(card)*"/>
          <xsl:for-each-group select="$cards" group-by="TimeCard">
              <xsl:sequence select="xs:decimal(adjAmt)"/>
          </xsl:for-each-group>
      </xsl:function>
    
      <xsl:template match="/">
         <xsl:value-of select="mf:group-and-select-first(test/card)"/>
      </xsl:template>
    
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/bFDb2BP

    I have wrapped that grouping code into a function as your question talks about using the code in an XPath predicate, the example simply calls the function in a function call of an xsl:value-of select expression but of course you can as well use that function inside of a predicate if needed.