XSLT
Extensions
In this page, I am compiling some
examples for XSLT extensions. Extensions 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: May 26, 2007