XSLT
Extensions
In this page, I'm compiling some
examples for XSLT extensions (which allows us to extend the XSLT language).
1. Image handling using XSLT
The following question was asked on XSL-List.
> Has anyone written an XSLT 1.0 extension function
to get the
> height and width of an image at a specified URL?
Following is an example of Java extension function (tested with JDK 1.6.0) for Xalan-J:
ImageHelper.java
------------------
import
java.net.URL;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
public class ImageHelper
{
public static int getHeight(String imgUrl) {
int height = -1;
try {
URL url = new
URL(imgUrl);
BufferedImage
buffImg = ImageIO.read(url);
height =
buffImg.getHeight();
}
catch(java.net.MalformedURLException
ex) {
ex.printStackTrace();
}
catch(java.io.IOException ex) {
ex.printStackTrace();
}
return height;
}
public static int getWidth(String imgUrl) {
int width = -1;
try {
URL url = new
URL(imgUrl);
BufferedImage
buffImg = ImageIO.read(url);
width =
buffImg.getWidth();
}
catch(java.net.MalformedURLException
ex) {
ex.printStackTrace();
}
catch(java.io.IOException ex) {
ex.printStackTrace();
}
return width;
}
}
Following is the test stylesheet (named, test.xsl):
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:java="http://xml.apache.org/xalan/java"
version="1.0">
<xsl:output method="text" />
<xsl:template match="/">
<xsl:variable name="imageUrl" select="'http://www.google.co.in/intl/en/images/about_logo.gif'"
/>
<xsl:variable name="imgHeight" select="java:ImageHelper.getHeight($imageUrl)"
/>
<xsl:variable name="imgWidth" select="java:ImageHelper.getWidth($imageUrl)"
/>
Height - <xsl:value-of select="$imgHeight" /><xsl:text>
</xsl:text>
Width - <xsl:value-of select="$imgWidth" />
</xsl:template>
</xsl:stylesheet>
When Xalan-J is run as following:
java org.apache.xalan.xslt.Process -in test.xsl -xsl test.xsl
The output produced is:
Height - 65
Width - 175
2. Grouping and numbering
The following question was asked on XSL-List.
I need to run through a large structure that
resembles:
<sample>
<result>
<details>
<group_id>250</group_id>
</details>
</result>
<result>
<details>
<group_id>300</group_id>
</details>
</result>
<result>
<details>
<group_id>250</group_id>
</details>
</result>
</sample>
Firstly, I need to sort the data into <group_id> order, so I use:
<xsl:apply-templates select="sample//result/details">
<xsl:sort select="group_id" data-type="number" />
</xsl:apply-templates>
Next, within the called template, I need to loop through all the <details> and
add row numbering information but add an extra row at the end of each group.
So hope to see:
<output row="1">250</output>
<output row="2">250</output>
<output row="3" />
<output row="4">300</output>
I tried to solve this problem as following:
Please consider the following example.
Java extension class:
public class Iterator {
private static int i = 0;
public static int next() {
i++;
return i;
}
}
XSLT stylesheet:
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:java="http://xml.apache.org/xalan/java"
exclude-result-prefixes="java"
version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:key name="by-group" match="result" use="details/group_id" />
<xsl:template match="sample">
<sample>
<xsl:for-each select="result[generate-id()
= generate-id(key('by-group', details/group_id)[1])]">
<xsl:sort
select="details/group_id" data-type="number" />
<xsl:variable
name="x" select="position()" />
<xsl:for-each
select="key('by-group', details/group_id)">
<output row="{java:Iterator.next()}"><xsl:value-of select="details/group_id"
/></output>
</xsl:for-each>
<xsl:if
test="$x != last()">
<output row="{java:Iterator.next()}" />
</xsl:if>
</xsl:for-each>
</sample>
</xsl:template>
</xsl:stylesheet>
Using Xalan-J (ver 2.7.0), when the above stylesheet is applied to the XML:
<sample>
<result>
<details>
<group_id>250</group_id>
</details>
</result>
<result>
<details>
<group_id>300</group_id>
</details>
</result>
<result>
<details>
<group_id>250</group_id>
</details>
</result>
</sample>
The output produced is:
<?xml version="1.0" encoding="UTF-8"?>
<sample>
<output row="1">250</output>
<output row="2">250</output>
<output row="3"/>
<output row="4">300</output>
</sample>
David Carlisle provided a
nice solution, written in pure XSLT 1.0:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="g" match="group_id" use="." />
<xsl:output indent="yes"/>
<xsl:variable name="g" select="/sample/result/details/group_id[generate-id()=generate-id(key('g',.))]"/>
<xsl:template match="sample">
<xsl:for-each select="$g">
<xsl:sort
select="."/>
<xsl:variable
name="p" select="position()-1"/>
<xsl:variable
name="c" select="count(key('g',$g[.<current()]))"/>
<xsl:for-each
select="key('g',.)">
<output row="{$c+$p+position()}"><xsl:value-of select="."/></output>
</xsl:for-each>
<xsl:if
test="position()!=last()">
<output row="{$c+count(key('g',.))+$p+1}"/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Last Updated: Apr 24, 2010