3

I was wondering whether it's possible to sort some elements first and store them (already sorted) in a variable. I would need to refer to them thought XSLT that's why I'd like to store them in a variable.

I was trying to do the following, but it doesn't seem to work

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 version="1.0">

<xsl:variable name="deposits">
  <xsl:for-each select="/BookingCostings/MultiDeposits">
    <xsl:sort select="substring(@DepositDate, 1, 4)" />
    <xsl:sort select="substring(@DepositDate, 6, 2)" />
    <xsl:sort select="substring(@DepositDate, 9, 2)" />
 </xsl:for-each>
</xsl:variable>

I was trying to sort the elements by @DepositDate in the format 'yyyy-mm-dd' and store them all in the $deposits variable. So that later, I could access them using $deposits[1].

I would appreciate any help and tips!

Thanks a lot!

DashaLuna
  • 666
  • 2
  • 12
  • 22
  • Your question indicates that you are probably trying to solve a problem the wrong way on a bigger scale. *Why* do you want to store a sorted node-set in a variable? What exactly are you trying to do? ("I want to access them as `$deposits[1]`" is not the answer I'm looking for.) – Tomalak Feb 16 '10 at 12:12
  • Tomalak, unfortunately, I'm working with badly structured XML. There are seposits that must be listed in order and also they are required at different parts of outcome document. There is no attribute to determine deposits sequence, and I decided not to rely in the position. That's why I decided to sort them and store, so that I can easily refer to all of them of specific ones whenever I need to. Hope that makes sense? – DashaLuna Feb 17 '10 at 09:04
  • Yeah it does. Sometimes it just is like that. Thought about an intermediary transformation that re-structures the bad input sensibly and then build your final transformation upon that? May be worthwhile in the long run. – Tomalak Feb 17 '10 at 17:45

3 Answers3

5
  1. Using XSLT version 2.0 you could use perform-sort and tell that your variable is of type of a sequence of MultiDeposits using the as keyword (as="element(MultiDeposits)+")
  2. Since your data is already as yyyy-mm-dd you can avoid to use the subtring to get each part of the date and use the sort directly on the field

with this sample xml:

<?xml version="1.0" encoding="ISO-8859-1"?>
<BookingCostings>
  <MultiDeposits depositDate="2001-10-09">1</MultiDeposits>
  <MultiDeposits depositDate="1999-10-09">2</MultiDeposits>
  <MultiDeposits depositDate="2010-08-09">3</MultiDeposits>
  <MultiDeposits depositDate="2010-07-09">4</MultiDeposits>
  <MultiDeposits depositDate="1998-01-01">5</MultiDeposits>
</BookingCostings>

and using the XSLT version 2.0 sheet:

<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:template match="/">
 <html>
  <body>

  <xsl:variable name="deposits" as="element(MultiDeposits)+">
   <xsl:perform-sort select="BookingCostings/MultiDeposits">
    <xsl:sort select="@depositDate"/>
   </xsl:perform-sort>
  </xsl:variable>

  first date:<xsl:value-of select="$deposits[1]/@depositDate"/>,
  last date:<xsl:value-of select="$deposits[last()]/@depositDate"/>

  </body>
 </html>
 </xsl:template>

</xsl:stylesheet>

the ouput will be:

first date:1998-01-01, last date:2010-08-09
Patrick
  • 15,702
  • 1
  • 39
  • 39
  • Thank you Patrick. I assume as="element(MultiDeposits)+" isn't possible in XSLT 1.0? Unfortunately, I don't have date in the format yyyy-mm-dd. I use function in every sort statement before substring function. I didn't mention it to simplify code a bit. I tried to have a local variable before sort statement with the whole date converted to yyyy-mm-dd but apparently sort instruction must be first. So yes. Thank you for the response :) – DashaLuna Feb 17 '10 at 09:18
  • Unfortunately no, it is 2.0 only. – Patrick Feb 17 '10 at 10:27
  • @Patrick, what if it has a namespace. I am getting `Required item type of value of variable $xx is element(Q{}yy); supplied value has item type element(Q{http://www.w3.org/1999/xhtml}yy)`. – rdonuk Aug 01 '19 at 11:46
4

Firstly, in your variable declaration, you do need to do something to create new nodes. Strictly speaking, you are not sorting them, but just reading through them in a given order. I think you need to add some sort of xsl:copy command.

<xsl:variable name="deposits"> 
  <xsl:for-each select="/BookingCostings/MultiDeposits"> 
    <xsl:sort select="substring(@DepositDate, 1, 4)" /> 
    <xsl:sort select="substring(@DepositDate, 6, 2)" /> 
    <xsl:sort select="substring(@DepositDate, 9, 2)" /> 
    <xsl:copy-of select=".|@*" />
 </xsl:for-each> 
</xsl:variable> 

What this creates is a 'node-set', but to access it you will need to make use of an extension function in XSLT. Which one you use depends on the XSLT processor you are using. In the example I am about to give, I am using the Microsoft one.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt" version="1.0"> 

Then, to access the nodes in your variable, you can do something like this

<xsl:value-of select="ms:node-set($deposits)/MultiDeposits[1]/@DepositDate" />

Here is a good article to read up on node-sets

Xml.com article on Node-Sets

Tim C
  • 70,053
  • 14
  • 74
  • 93
  • 2
    Tim is right that inside your for-each you need to output something if you want to populate the variable so use that inside of the for-each. The type of the variable however is not node-set but rather result tree fragment. Only by calling the extension function on the variable the result tree fragment is converted to a node-set you can apply XPath to. – Martin Honnen Feb 16 '10 at 12:05
  • Thank you guys! Yes, I definitely need . Tim thank you for the article it was helpful. Martin, do you know by any chance good read on tree fragments and how different function work on a document? Or for that matter any info on XSLT in general? I'd really appreciate it. Just trying to understand it all better :) – DashaLuna Feb 17 '10 at 09:15
-1

Guess (don't have dev env to hand):

Add <xsl:value-of select="." />

Before the closing </xsl:for-each>

Murph
  • 9,985
  • 2
  • 26
  • 41