Slow running XSLT: Any help appreciated


J

JimLad

Hi,

We're using XSLT to transform XML data into an HTML table. With 200
rows of data, this is taking 10s which I consider to be excessive to
say the least. I'm afraid I use XSLT so rarely that I have to relearn
everything each time I do. Any help in sorting this out would be
appreciated. I'm sure the same advice would apply to all our
transforms so it would be very helpful.

XML sample:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<member_salaries member_salary_id="791628" member_id="150230"
salary_type_id="25" start_date="2005-06-01" salary="103.73" />
<member_salaries member_salary_id="791629" member_id="150230"
salary_type_id="25" start_date="2006-05-01" end_date="2006-05-31"
salary="32.12" />
</root>

XSLT (I have removed some of the HTML markup):
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/
Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:custom="/">
<msxsl:script language="vbscript" implements-prefix="custom">
<![CDATA[

Function dateFormat(x)
if x="" then
dateFormat = ""
exit function
end if
dateFormat = numberFormat(day(x)) & "/" & numberFormat(month(x)) &
"/" & year(x)
End Function

Function numberFormat(x)
if x="" then exit function
if x < 10 then numberFormat = "0" & x else numberFormat = x
End Function
]]>
</msxsl:script>

<xsl:template match="/">
<xsl:choose>
<xsl:when test="//member_salaries">
<FORM ID="Form1">
<table >
<thead>
</thead>
<tbody >
<xsl:apply-templates select="//member_salaries">
<xsl:sort select="@salary_type_order"
order="ascending" data-type="text" />
<xsl:sort select="@start_date" order="descending" data-
type="text" />
</xsl:apply-templates>
<tr >
</td>
</tr>
</tbody>
</table>
</FORM>
</xsl:when>
<xsl:eek:therwise>
There are no entries which match your selection.
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>

<xsl:template match="//member_salaries">
<tr >
<td>
<input id="member_salary_id" name="member_salary_id"
type="hidden">
<xsl:attribute name="value">
<xsl:value-of select="@member_salary_id"/>
</xsl:attribute>
</input>
</td>
<td>
<input id="member_id" name="member_id" type="hidden">
<xsl:attribute name="value">
<xsl:value-of select="@member_id"/>
</xsl:attribute>
</input>
</td>
<td>
<input id="status_change_date" name="status_change_date"
type="hidden">
<xsl:attribute name="value"></xsl:attribute>
</input>
</td>
<xsl:variable name="salaryTypeId" select="@salary_type_id"/>
<td>
<SELECT id="salary_type_id" NAME="salary_type_id">
<xsl:for-each select="//salary_types">
<xsl:sort select="@salary_type" order="ascending" data-
type="text" />
<option>
<xsl:if test="@salary_type_id=$salaryTypeId">
<xsl:attribute name="selected">selected</
xsl:attribute>
</xsl:if>
<xsl:attribute name="value">
<xsl:value-of select="@salary_type_id"/>
</xsl:attribute>
<xsl:value-of select="@salary_type"/>
</option>
</xsl:for-each>
</SELECT>
</td>
<td>
<input size="7" id="start_date" name="start_date" >
<xsl:attribute name="value">
<xsl:apply-templates select="@start_date"/>
</xsl:attribute>
</input>
</td>
<td>
<input size="7" id="end_date" name="end_date" >
<xsl:attribute name="value">
<xsl:apply-templates select="@end_date"/>
</xsl:attribute>
</input>
</td>
<td>
<input size="10" id="salary" name="salary" >
<xsl:attribute name="value">
<xsl:value-of select="@salary"/>
</xsl:attribute>
</input>
</td>
</tr>
</xsl:template>

<xsl:template match="@start_date | @end_date">
<xsl:value-of select="custom:dateformat(string(.))"/>
</xsl:template>

<xsl:template match="//row/@Start_date">
<xsl:value-of select="custom:dateformat(string(.))"/>
</xsl:template>

</xsl:stylesheet>

Cheers,

James
 
Ad

Advertisements

J

JimLad

Hi,

We're using XSLT to transform XML data into an HTML table. With 200
rows of data, this is taking 10s which I consider to be excessive to
say the least. I'm afraid I use XSLT so rarely that I have to relearn
everything each time I do. Any help in sorting this out would be
appreciated. I'm sure the same advice would apply to all our
transforms so it would be very helpful.

XML sample:
<?xml version="1.0" encoding="utf-8" ?>
<root>
<member_salaries member_salary_id="791628" member_id="150230"
salary_type_id="25" start_date="2005-06-01" salary="103.73" />
<member_salaries member_salary_id="791629" member_id="150230"
salary_type_id="25" start_date="2006-05-01" end_date="2006-05-31"
salary="32.12" />
</root>

XSLT (I have removed some of the HTML markup):
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/
Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt"
xmlns:custom="/">
<msxsl:script language="vbscript" implements-prefix="custom">
<![CDATA[

Function dateFormat(x)
if x="" then
dateFormat = ""
exit function
end if
dateFormat = numberFormat(day(x)) & "/" & numberFormat(month(x)) &
"/" & year(x)
End Function

Function numberFormat(x)
if x="" then exit function
if x < 10 then numberFormat = "0" & x else numberFormat = x
End Function
]]>
</msxsl:script>

<xsl:template match="/">
<xsl:choose>
<xsl:when test="//member_salaries">
<FORM ID="Form1">
<table >
<thead>
</thead>
<tbody >
<xsl:apply-templates select="//member_salaries">
<xsl:sort select="@salary_type_order"
order="ascending" data-type="text" />
<xsl:sort select="@start_date" order="descending" data-
type="text" />
</xsl:apply-templates>
<tr >
</td>
</tr>
</tbody>
</table>
</FORM>
</xsl:when>
<xsl:eek:therwise>
There are no entries which match your selection.
</xsl:eek:therwise>
</xsl:choose>
</xsl:template>

<xsl:template match="//member_salaries">
<tr >
<td>
<input id="member_salary_id" name="member_salary_id"
type="hidden">
<xsl:attribute name="value">
<xsl:value-of select="@member_salary_id"/>
</xsl:attribute>
</input>
</td>
<td>
<input id="member_id" name="member_id" type="hidden">
<xsl:attribute name="value">
<xsl:value-of select="@member_id"/>
</xsl:attribute>
</input>
</td>
<td>
<input id="status_change_date" name="status_change_date"
type="hidden">
<xsl:attribute name="value"></xsl:attribute>
</input>
</td>
<xsl:variable name="salaryTypeId" select="@salary_type_id"/>
<td>
<SELECT id="salary_type_id" NAME="salary_type_id">
<xsl:for-each select="//salary_types">
<xsl:sort select="@salary_type" order="ascending" data-
type="text" />
<option>
<xsl:if test="@salary_type_id=$salaryTypeId">
<xsl:attribute name="selected">selected</
xsl:attribute>
</xsl:if>
<xsl:attribute name="value">
<xsl:value-of select="@salary_type_id"/>
</xsl:attribute>
<xsl:value-of select="@salary_type"/>
</option>
</xsl:for-each>
</SELECT>
</td>
<td>
<input size="7" id="start_date" name="start_date" >
<xsl:attribute name="value">
<xsl:apply-templates select="@start_date"/>
</xsl:attribute>
</input>
</td>
<td>
<input size="7" id="end_date" name="end_date" >
<xsl:attribute name="value">
<xsl:apply-templates select="@end_date"/>
</xsl:attribute>
</input>
</td>
<td>
<input size="10" id="salary" name="salary" >
<xsl:attribute name="value">
<xsl:value-of select="@salary"/>
</xsl:attribute>
</input>
</td>
</tr>
</xsl:template>

<xsl:template match="@start_date | @end_date">
<xsl:value-of select="custom:dateformat(string(.))"/>
</xsl:template>

<xsl:template match="//row/@Start_date">
<xsl:value-of select="custom:dateformat(string(.))"/>
</xsl:template>

</xsl:stylesheet>

Cheers,

James

Sorry: You will also want this:

XML:
<?xml version="1.0" encoding="utf-8" ?>
<ROOT xmlns:sql="urn:schemas-microsoft-com:xml-sql">
<salary_types salary_type_id="11" salary_type="Bank Account
Value" />
<salary_types salary_type_id="27" salary_type="Weekly Basic" />
</ROOT>
 
J

Joseph Kesselman

Haven't had time to look at this in any detail, but a minor clean-up
point: Match patterns never need to start with //. Rather than writing
match="//member_salaries"
just write
match="member_salaries"

That may not make a performance difference (depending on how the
processor is implemented), but it's more idiomatic and easier for others
to read.

In general, beware of using // (exhaustive search of the whole document)
when you can do a more specific search relative to where you currently are.

The script is a nonportable solution -- essentially, it's a flavor of
extension function. You may want to consider switching to a pure XSLT
solution, or at the very least to the standardized-nonstandard EXSLT
library functions.
 
P

Pavel Lepin

JimLad said:
We're using XSLT to transform XML data into an HTML table.
With 200 rows of data, this is taking 10s which I consider
to be excessive to say the least. I'm afraid I use XSLT so
rarely that I have to relearn everything each time I do.
Any help in sorting this out would be appreciated. I'm
sure the same advice would apply to all our transforms so
it would be very helpful.

[...]
<msxsl:script language="vbscript"
implements-prefix="custom">
<![CDATA[

Function dateFormat(x)
if x="" then
dateFormat = ""
exit function
end if
dateFormat = numberFormat(day(x)) & "/" &
numberFormat(month(x)) & "/" & year(x)
End Function

Function numberFormat(x)
if x="" then exit function
if x < 10 then numberFormat = "0" & x else numberFormat =
x End Function
]]>
</msxsl:script>

BAD idea. All of this is easily done in pure XSLT1.

[the rest of stylesheet]

First of all, I wouldn't use "//element" selects (or
matches, for that matter--Joseph Kesselman already pointed
out why). Template-based processing probably wouldn't
affect performance much, but it sure helps a lot with
maintenance.

Just for the heck of it, here's how I would've done it:

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:eek:utput indent="yes"/>
<xsl:template match="node()|@*"/>
<xsl:template match="text()"><xsl:copy/></xsl:template>
<xsl:template match="/">
<result><xsl:apply-templates/></result>
</xsl:template>
<xsl:template match="root">
<xsl:text>No entries found.</xsl:text>
</xsl:template>
<xsl:template match="root[member_salaries]">
<data>
<xsl:apply-templates select="member_salaries">
<xsl:sort select="@salary_type_order"/>
<xsl:sort select="@start_date" order="descending"/>
</xsl:apply-templates>
</data>
</xsl:template>
<xsl:template match="member_salaries">
<row><xsl:apply-templates select="@*"/></row>
</xsl:template>
<xsl:template
match=
"
@member_salary_id|@member_id|@status_change_date|
@salary
">
<elt id="{local-name()}" value="{.}"/>
</xsl:template>
<xsl:template match="@start_date|@end_date">
<elt id="{local-name()}">
<xsl:attribute name="value">
<xsl:call-template name="date-format"/>
</xsl:attribute>
</elt>
</xsl:template>
<xsl:template match="member_salaries/@salary_type_id">
<list>
<xsl:apply-templates select="/root/salary_types">
<xsl:sort select="@salary_type"/>
<xsl:with-param name="selected" select="."/>
</xsl:apply-templates>
</list>
</xsl:template>
<xsl:template match="salary_types">
<xsl:param name="selected"/>
<item>
<xsl:apply-templates select="@*">
<xsl:with-param name="selected"
select="$selected"/>
</xsl:apply-templates>
</item>
</xsl:template>
<xsl:template match="salary_types/@salary_type_id">
<xsl:param name="selected"/>
<xsl:attribute name="value">
<xsl:value-of select="."/>
</xsl:attribute>
<xsl:if test=". = $selected">
<xsl:attribute name="selected">
<xsl:text>1</xsl:text>
</xsl:attribute>
</xsl:if>
</xsl:template>
<xsl:template match="salary_types/@salary_type">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template name="date-format">
<xsl:param name="date" select="."/>
<xsl:if test="$date != ''">
<xsl:variable name="y"
select="substring-before($date,'-')"/>
<xsl:variable name="md"
select="substring-after($date,'-')"/>
<xsl:variable name="m"
select="substring-before($md,'-')"/>
<xsl:variable name="d"
select="substring-after($md,'-')"/>
<xsl:call-template name="number-format">
<xsl:with-param name="n" select="$d"/>
</xsl:call-template>
<xsl:text>/</xsl:text>
<xsl:call-template name="number-format">
<xsl:with-param name="n" select="$m"/>
</xsl:call-template>
<xsl:text>/</xsl:text>
<xsl:call-template name="number-format">
<xsl:with-param name="n" select="$y"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="number-format">
<xsl:param name="n"/>
<xsl:value-of select="format-number($n,'00')"/>
</xsl:template>
</xsl:stylesheet>

Note that it doesn't output HTML (well, my nntp server does
not allow HTML elements in outgoing messages anyway), and
its behaviour might differ a bit from the one you've
posted. Since I couldn't run your transformation (and
wouldn't even if I could :), I can't say how it compares
in performance, but here are the results of my test run:

[email protected]:~/dev/xslt$ time xsltproc ms.xsl ms.xml
ms_out.xml

real 0m0.498s
user 0m0.484s
sys 0m0.004s
[email protected]:~/dev/xslt$ time saxon ms.xml ms.xsl
ms_out2.xml
Warning: at xsl:stylesheet on line 2 of
file:/var/www/dev/xslt/ms.xsl:
Running an XSLT 1.0 stylesheet with an XSLT 2.0 processor

real 0m1.946s
user 0m1.788s
sys 0m0.068s
[email protected]:~/dev/xslt$ l ms*
-rw-r--r-- 1 pavel pavel 908830 2007-07-11 11:55 ms_out2.xml
-rw-r--r-- 1 pavel pavel 838313 2007-07-11 11:55 ms_out.xml
-rw-r--r-- 1 pavel pavel 65040 2007-07-11 11:44 ms.xml
-rw-r--r-- 1 pavel pavel 3129 2007-07-11 11:42 ms.xsl
[email protected]:~/dev/xslt$

Xalan doesn't seem to grok format-number(), so I skipped it.
 
J

Joe Kesselman

Pavel said:
Xalan doesn't seem to grok format-number(), so I skipped it.

Surprising. If you can confirm that, please submit a bug report...

(There are reports that Xalan and Saxon have differed slightly in how
this function behaves, due to drift in how Java 1.0/1.1 and XSLT 1.0/2.0
define it and some slightly lazy implementation. But I haven't seen a
report that it flat-out doesn't work...)
 
Ad

Advertisements

P

Pavel Lepin

Joe Kesselman said:
Surprising. If you can confirm that, please submit a bug
report...

Xalan-C++ FAQ indicates it's not really a bug, at least not
in Xalan-C++. It just has to be built with ICU support for
format-number() implementation to work, and I slurped in a
binary from Debian packages collection which seemingly
doesn't have that.
 
Ad

Advertisements


Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top