Tuno
Tuno

Reputation: 1340

XPATH select condition with the latest date

I have the following XML:

<STATUSLIST>
    <STATUS>
        <TYPE VALUE="1"/>
        <DATE>19910000</DATE>
    </STATUS>
    <STATUS>
        <TYPE VALUE="1"/>
        <DATE>19470000</DATE>
    </STATUS>
    <STATUS>
        <TYPE VALUE="2"/>
        <DATE>19470000</DATE>
    </STATUS>
</STATUSLIST>

And I would like to match the STATUS where TYPE/@VALUE = '2' and not(//STATUSLIST/STATUS/DATE > DATE). In this case it would be the 3rd STATUS.

When I apply the type with the latest date I get nothing because it can't match both. What I would like is to match the TYPE/@VALUE = '2' first and in that match get the one with the latest date.

Any clue?

Cheers,

Tuno

Upvotes: 1

Views: 2100

Answers (3)

Ian Roberts
Ian Roberts

Reputation: 122394

While your own solution of STATUS[TYPE/@VALUE = '2'][not(//STATUSLIST/STATUS[TYPE/@VALUE = '2']/DATE > DATE)] will work, it is not particularly efficient for large numbers of entries as you're comparing every STATUS with every other STATUS, so O(N2). More efficient would be to implement it as a tail-recursive function (as you say you're using XSLT 2.0) such as the following:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
                xmlns:local="urn:local" exclude-result-prefixes="local">

  <xsl:function name="local:latestByDate" as="item()*">
    <xsl:param name="seq" as="item()*"/>
    <xsl:sequence select="local:latestByDate($seq, ())" />
  </xsl:function>

  <xsl:function name="local:latestByDate" as="item()*">
    <xsl:param name="seq" as="item()*" />
    <xsl:param name="maxSoFar" as="item()*" />
    <xsl:choose>
      <xsl:when test="$seq">
        <!-- calculate the new maxSoFar, comparing the current max with
             the first item in $seq.  Note the use of not(x<=y) instead of
             x>y, so the test is true if $maxSoFar is the empty sequence -->
        <xsl:variable name="newMax"
                      select="if(not($seq[1]/DATE &lt;= $maxSoFar/DATE))
                              then $seq[1] else $maxSoFar"/>
        <xsl:sequence select="local:latestByDate(
                                 $seq[position() gt 1], $newMax)" />
      </xsl:when>
      <xsl:otherwise>
        <!-- we have reached the end of $seq, return the max -->
        <xsl:sequence select="$maxSoFar" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:function>

  <!-- example of how to call the function -->
  <xsl:template match="/*">
    <xsl:copy-of select="local:latestByDate(STATUS[TYPE/@VALUE='2'])" />
  </xsl:template>
</xsl:stylesheet>

This function makes a single pass over the list of nodes (so O(N)), keeping track at each step of which element in the input sequence has the latest DATE.

Upvotes: 0

Tuno
Tuno

Reputation: 1340

My solution: STATUS[TYPE/@VALUE = '2'][not(//STATUSLIST/STATUS[TYPE/@VALUE = '2']/DATE > DATE)].

Upvotes: 1

Richard
Richard

Reputation: 109100

If I understand what you want correctly, you cannot do this all in XPath v1 due to the lack of a maximum function. So get all the STATUS elements with the desired TYPE:

/STATUSLIST/STATUS[TYPE/@VALUE=2]

and then sort or maximum function in the calling environment.

Upvotes: 0

Related Questions