Tuesday 26 June 2007

Sort by date greater than other date

Often XML contains dates in all sorts of formats, and either XSL Developers have to go through a great deal of diplomatic negotiations with Developers responsible for the XML, or worse there is no one to talk to.

Ultimately the job needs to get done, and as XSL 1.0 at least does not recognise the Date data type natively, it’s back to hacking away the xsl:sort element so the sorting can take place.

As XSL 1.0 Processors understand number sorting, dates can easily and meaningfully be sorted in the format yyyymmdd, which represents a meaningful numeric value for any given date, maintaining the correct order once sorted. This means that no matter what format you get dates in xml as soon as you manage to convert the date to yyyymmdd you can sort by Date.

Lets say you have some date in the traditional US format mm-dd-yyyy (10-05-1971). To format this date you know that all the characters before the first occurrence of the hyphen character represent the month, so you can easily get those with substring-before([dateNode], ‘-‘). To get the day, you can use the same technique twice, but this time using substring-before and substring-after combined, substring-before(substring-after([dateNode], ‘-‘), ‘-‘) .

To get year, you guessed . . . substring-after(substring-after([dateNode], ‘-‘), ‘-‘)

This technique works for the following formats:
mm-dd-yyyy
dd-mm-yyyy
d-m-yyyy
d-m-yy

If you knew the exact character length of your date you could use the substring() function instead:
Month – substring([dateNode], 1, 2)
Day – substring([dateNode], 4, 2)
Year – substring([dateNode], 7, 4)

Whatever technique you use, all you need to do is to concatenate the string fragmentation results of your date string to yyyymmdd, or equivalent, like:
concat(
substring-after(substring-after([dateNode], ‘-‘), ‘-‘),
substring-before([dateNode],
substring-before(substring-after([dateNode], ‘-‘)
)

You can now use the concatenated string inside an XPath predicate.

The following example creates a list of news where the date is greater than the expiry date and sorts the results by date descending.


XML


<PICNews>
<news>
<Date>6/31/2007</Date>
<Expires>6/30/2007</Expires>
<Author>Dan Jurden</Author>
<Type>Memo</Type>
<Text>This is a memo.</Text>
</news>
<news>
<Date>7/21/2007</Date>
<Expires>6/30/2007</Expires>
<Author>Dan Jurden</Author>
<Type>News</Type>
<Text>This is some news.</Text>
</news>
<news>
<Date>6/22/2007</Date>
<Expires>6/23/2007</Expires>
<Author>Dan Jurden</Author>
<Type>Alert</Type>
<Text>This is an alert.</Text>
</news>
</PICNews>



XSL

<xsl:template match="/">
<body>
<h2>PIC Information</h2>
<div id="news">
<xsl:apply-templates select="
PICNews/news[
concat(
substring(Date, (string-length(Date) - 3), string-length(Date)),
substring-before(Date, '/'),
substring-before(substring-after(Date, '/'), '/')
)

>

concat(
substring(Expires, (string-length(Expires) - 3), string-length(Expires)),
substring-before(Expires, '/'),
substring-before(substring-after(Expires, '/'), '/')
)

]
">

<xsl:sort select="
Date[
concat(
substring(Date, (string-length(Date) - 3), string-length(Date)),
substring-before(Date, '/'),
substring-before(substring-after(Date, '/'), '/')
)
]
"
order="descending"/>
</xsl:apply-templates>
</div>
</body>
</xsl:template>


<xsl:template match="news">
<div class="newsItem">
<img src="images/{translate(
Type,
'QWERTYUIOPASDFGHJKLZXCVBNM',
'qwertyuiopasdfghjklzxcvbnm'
)
}.jpg"
width="32px"/>
<p class="date">
<xsl:text>Date: </xsl:text>
<xsl:value-of select="Date"/>
</p>
<p class="news-text">
<xsl:value-of select="Text"/>
</p>
</div>
</xsl:template>

Good use of xsl:key

This is a perfect example of how xsl:key can not only optimize the syntax of your xsl, but also its performance significantly.
In the following XML there are three separate nodesets, children of the same parent, that reference each other via different ids. The purpose of this exercise is to get a list of incidents for each car.

XML


<?xml version="1.0" encoding="UTF-8"?>
<root>
<car>
<CarInfo vin="4FL200212345">
<Owner>J. P. Morgan</Owner>
</CarInfo>
</car>
<car>
<CarInfo vin="3CL200212345">
<Owner>J.P. Thompson</Owner>
</CarInfo>
</car>
<carSpecifics vin="4FL200212345">
<CarMake xrefId="XREF12345">
<Color>Red</Color>
<Style>Sedan</Style>
</CarMake>
</carSpecifics>
<carSpecifics vin="3CL200212345">
<CarMake xrefId="XREF67890">
<Color>Red</Color>
<Style>Sedan</Style>
</CarMake>
</carSpecifics>
<CarXref xrefId="XREF12345">
<Incident>Friday Crash</Incident>
</CarXref>
<CarXref xrefId="XREF67890">
<Incident>Sat Crash</Incident>
</CarXref>
</root>


XSL

<xsl:key name="incidents"
match="/root//CarXref/Incident" use="../@xrefId"/>
<xsl:key name="carSpecifics"
match="/root/carSpecifics/CarMake/@xrefId" use="../../@vin"/>

<xsl:template match="/">
<incidents>
<xsl:apply-templates select="root/car/CarInfo/@vin"/>
</incidents>
</xsl:template>

<xsl:template match="@vin">
<xsl:apply-templates select="key('carSpecifics',.)"/>
</xsl:template>

<xsl:template match="@xrefId">
<xsl:apply-templates select="key('incidents', .)"/>
</xsl:template>

<xsl:template match="Incident">
<incident>
<xsl:value-of select="."/>
</incident>
</xsl:template>

Monday 25 June 2007

Copying nodes depending on children values

The input XML needs to be refined to contain only nodes where the value element of a characteristics element where the name attibute is weight is greater than 15.

XML


<root>
<quality>
<batch id="1">
<characteristic name="weight">
<value>12</value>
</characteristic>
<characteristic name="length">
<value>45</value>
</characteristic>
<characteristic name="volume">
<value>67</value>
</characteristic>
</batch>
<batch id="2">
<characteristic name="weight">
<value>12</value>
</characteristic>
<characteristic name="weight">
<value>17</value>
</characteristic>
</batch>
<batch id="3">
<characteristic name="weight">
<value>56</value>
</characteristic>
<characteristic name="volume">
<value>65</value>
</characteristic>
</batch>
</quality>
</root>


XSL

<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/root">
<quality>
<xsl:copy-of select="
quality/batch[characteristic[@name= 'weight' and value &gt; 15]]
"/>
</quality>
</xsl:template>
</xsl:transform>

Sunday 24 June 2007

Horizontally scalable HTML table

Build an horizontally scalable HTML table using XPath and XSLT


XML


<?xml version="1.0" encoding="UTF-8"?>
<ProducsList>
<Product id="1">
<SpecList>
<Spec>
<SpecLabel>Height</SpecLabel>
<SpecValue>10</SpecValue>
<SpecCat>Dimension</SpecCat>
</Spec>
<Spec>
<SpecLabel>Width</SpecLabel>
<SpecValue>6</SpecValue>
<SpecCat>Dimension</SpecCat>
</Spec>
<Spec>
<SpecLabel>Weight</SpecLabel>
<SpecValue>20.5</SpecValue>
<SpecCat>Weigth</SpecCat>
</Spec>
</SpecList>
</Product>
<Product id="2">
<SpecList>
<Spec>
<SpecLabel>Height</SpecLabel>
<SpecValue>8</SpecValue>
<SpecCat>Dimension</SpecCat>
</Spec>
<Spec>
<SpecLabel>Width</SpecLabel>
<SpecValue>5</SpecValue>
<SpecCat>Dimension</SpecCat>
</Spec>
<Spec>
<SpecLabel>Weight</SpecLabel>
<SpecValue>18</SpecValue>
<SpecCat>Weigth</SpecCat>
</Spec>
</SpecList>
</Product>
<Product id="3">
<SpecList>
<Spec>
<SpecLabel>Height</SpecLabel>
<SpecValue>5</SpecValue>
<SpecCat>Dimension</SpecCat>
</Spec>
<Spec>
<SpecLabel>Width</SpecLabel>
<SpecValue>2</SpecValue>
<SpecCat>Dimension</SpecCat>
</Spec>
<Spec>
<SpecLabel>Weight</SpecLabel>
<SpecValue>10</SpecValue>
<SpecCat>Weigth</SpecCat>
</Spec>
</SpecList>
</Product>
</ProducsList>



XSL

<xsl:template match="ProducsList">
<table>
<tr>
<th>Label</th>
<xsl:apply-templates select="Product" mode="header"/>
</tr>
<xsl:apply-templates select="
/ProducsList/Product/SpecList/Spec/SpecLabel[
not(
text()
=
ancestor::Product/preceding-sibling::Product//SpecLabel/text()
)
]
"
/>
</table>
</xsl:template>

<xsl:template match="Product" mode="header">
<th>
<xsl:text>Product </xsl:text>
<xsl:value-of select="@id"/>
</th>
</xsl:template>

<xsl:template match="SpecLabel">
<xsl:variable name="text" select="text()"/>
<tr>
<th>
<xsl:value-of select="$text"/>
</th>
<xsl:apply-templates select="/ProducsList/Product">
<xsl:with-param name="label" select="$text"/>
</xsl:apply-templates>
</tr>
</xsl:template>

<xsl:template match="Product">
<xsl:param name="label"/>
<td>
<xsl:value-of select="SpecList/Spec[SpecLabel=
$label]/SpecValue"/>
</td>
</xsl:template>



HTML

<table>
<tr>
<th>Label</th>
<th>Product 1</th>
<th>Product 2</th>
<th>Product 3</th>
</tr>
<tr>
<th>Height</th>
<td>10</td>
<td>8</td>
<td>5</td>
</tr>
<tr>
<th>Width</th>
<td>6</td>
<td>5</td>
<td>2</td>
</tr>
<tr>
<th>Weight</th>
<td>20.5</td>
<td>18</td>
<td>10</td>
</tr>
</table>

Friday 22 June 2007

top id from xml


Clever little XPath 1.0, gets the node with the highest id value. Xpath 2.0 has the max() function that ultimately does the same thing.

/rt/item[@id[not(. &lt; ../../item/@id)]]


<rt>
<item id="1">txt</item>
<item id="2">txt</item>
<item id="5">txt</item>
<item id="4">txt</item>
</rt>


returns

<item id="5">txt</item>