Do I have a bug, or am I pushing the limits of XSLT engines?

  • Thread starter Mike Whittemore
  • Start date
M

Mike Whittemore

I am trying to convert an HTML table into a list of name-value pairs,
one pair per field in the table. I believe my XSLT is correct, but
I've tried both Xalan and Saxon, which both fail with different
results. Below I've listed my HTML input, my expected XML output, my
XSLT, and the actual output from both Saxon and Xalan. Thanks in
advance.

HTML Input
----------
<html>
<head/>
<body>
<table>
<tr>
<th>Field 1</th>
<th>Field 2</th>
</tr>
<tr>
<td>A</td>
<td>1</td>
</tr>
<tr>
<td>B</td>
<td>2</td>
</tr>
</table>
</body>
</html>

Expected XML Output
-------------------
<?xml version="1.0" encoding="UTF-8" ?>
<records>
<record>
<field>
<name>Field 1</name>
<value>A</value>
</field>
<field>
<name>Field 2</name>
<value>1</value>
</field>
</record>
<record>
<field>
<name>Field 1</name>
<value>B</value>
</field>
<field>
<name>Field 2</name>
<value>2</value>
</field>
</record>
</records>

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

<xsl:variable name="fields" select="//th" />

<xsl:template name="lookup-field">
<xsl:param name="at-index"/>
<xsl:value-of select="$fields[$at-index]" />
</xsl:template>

<xsl:template match="/html/body/table">
<records>
<xsl:apply-templates select="tr[td]"/>
</records>
</xsl:template>

<xsl:template match="tr[td]">
<record>
<xsl:for-each select="td">
<field>
<name>
<xsl:call-template name="lookup-field">
<xsl:with-param name="at-index">
<xsl:value-of select="position()"/>
</xsl:with-param>
</xsl:call-template>
</name>
<value>
<xsl:value-of select="."/>
</value>
</field>
</xsl:for-each>
</record>
</xsl:template>

</xsl:transform>

Actual Output From Saxon:
-------------------------
<?xml version="1.0" encoding="UTF-8" ?>
<records>
<record>
<field>
<name>Field 1</name>
<value>A</value>
</field>
<field>
<name>Field 1</name>
<value>1</value>
</field>
</record>
<record>
<field>
<name>Field 1</name>
<value>B</value>
</field>
<field>
<name>Field 1</name>
<value>2</value>
</field>
</record>
</records>

Actual Output From Xalan:
 
A

A. Bolmarcich

I am trying to convert an HTML table into a list of name-value pairs,
one pair per field in the table. I believe my XSLT is correct, but
I've tried both Xalan and Saxon, which both fail with different
results. Below I've listed my HTML input, my expected XML output, my
XSLT, and the actual output from both Saxon and Xalan. Thanks in
advance.

[snipped most of helpful example]
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:variable name="fields" select="//th" />

<xsl:template name="lookup-field">
<xsl:param name="at-index"/>
<xsl:value-of select="$fields[$at-index]" />
</xsl:template>

<xsl:template match="/html/body/table">
<records>
<xsl:apply-templates select="tr[td]"/>
</records>
</xsl:template>

<xsl:template match="tr[td]">
<record>
<xsl:for-each select="td">
<field>
<name>
<xsl:call-template name="lookup-field">
<xsl:with-param name="at-index">
<xsl:value-of select="position()"/>
</xsl:with-param>

Replace the above three lines with

<xsl:with-param name="at-index" select="position()"/>

What you have sets the param to a result tree fragment. You need to set the param
to a number, so that in the called template $at-index evaluates to a number.
 
M

Mike Whittemore

I am trying to convert an HTML table into a list of name-value pairs,
one pair per field in the table. I believe my XSLT is correct, but
I've tried both Xalan and Saxon, which both fail with different
results. Below I've listed my HTML input, my expected XML output, my
XSLT, and the actual output from both Saxon and Xalan. Thanks in
advance.

[snipped most of helpful example]
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:variable name="fields" select="//th" />

<xsl:template name="lookup-field">
<xsl:param name="at-index"/>
<xsl:value-of select="$fields[$at-index]" />
</xsl:template>

<xsl:template match="/html/body/table">
<records>
<xsl:apply-templates select="tr[td]"/>
</records>
</xsl:template>

<xsl:template match="tr[td]">
<record>
<xsl:for-each select="td">
<field>
<name>
<xsl:call-template name="lookup-field">
<xsl:with-param name="at-index">
<xsl:value-of select="position()"/>
</xsl:with-param>

Replace the above three lines with

<xsl:with-param name="at-index" select="position()"/>

What you have sets the param to a result tree fragment. You need to set the param
to a number, so that in the called template $at-index evaluates to a number.
</xsl:call-template>
</name>
<value>
<xsl:value-of select="."/>
</value>
</field>
</xsl:for-each>
</record>
</xsl:template>

</xsl:transform>

Thanks for your response. That worked perfectly for the Saxon and
Microsoft engines, but I still get a null pointer exception from the
Xalan engine. I suppose this show's it is a good idea to test your XSL
transformations on multiple engines if you want them to increase the
chances of them being portable. Below I've posted a different solution
that works for all 3 engines mentioned. I'm still new at this, so if
you spot a more elegant solution I'd appreciate seeing it. Thanks
again.

"Portable" XSLT Solution:
-------------------------
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/html/body/table">
<records>
<xsl:apply-templates select="tr[td]"/>
</records>
</xsl:template>

<xsl:template match="tr[td]">
<record>
<xsl:for-each select="td">
<field>
<name>
<xsl:variable name="pos" select="position()"
/>
<xsl:value-of select="/descendant::th[$pos]"/>
</name>
<value>
<xsl:value-of select="."/>
</value>
</field>
</xsl:for-each>
</record>
</xsl:template>

</xsl:transform>

PS. I'm not sure why I need to assign position() to a variable first,
but without doing it this way it was always equating to "1".
 
M

Mike Whittemore

I am trying to convert an HTML table into a list of name-value pairs,
one pair per field in the table. I believe my XSLT is correct, but
I've tried both Xalan and Saxon, which both fail with different
results. Below I've listed my HTML input, my expected XML output, my
XSLT, and the actual output from both Saxon and Xalan. Thanks in
advance.

[snipped most of helpful example]
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:variable name="fields" select="//th" />

<xsl:template name="lookup-field">
<xsl:param name="at-index"/>
<xsl:value-of select="$fields[$at-index]" />
</xsl:template>

<xsl:template match="/html/body/table">
<records>
<xsl:apply-templates select="tr[td]"/>
</records>
</xsl:template>

<xsl:template match="tr[td]">
<record>
<xsl:for-each select="td">
<field>
<name>
<xsl:call-template name="lookup-field">
<xsl:with-param name="at-index">
<xsl:value-of select="position()"/>
</xsl:with-param>

Replace the above three lines with

<xsl:with-param name="at-index" select="position()"/>

What you have sets the param to a result tree fragment. You need to set the param
to a number, so that in the called template $at-index evaluates to a number.
</xsl:call-template>
</name>
<value>
<xsl:value-of select="."/>
</value>
</field>
</xsl:for-each>
</record>
</xsl:template>

</xsl:transform>

Thanks for your response. That worked perfectly for the Saxon and
Microsoft engines, but I still get a null pointer exception from the
Xalan engine. I suppose this show's it is a good idea to test your XSL
transformations on multiple engines if you want them to increase the
chances of them being portable. Below I've posted a different solution
that works for all 3 engines mentioned. I'm still new at this, so if
you spot a more elegant solution I'd appreciate seeing it. Thanks
again.

"Portable" XSLT Solution:
-------------------------
<xsl:transform version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:template match="/html/body/table">
<records>
<xsl:apply-templates select="tr[td]"/>
</records>
</xsl:template>

<xsl:template match="tr[td]">
<record>
<xsl:for-each select="td">
<field>
<name>
<xsl:variable name="pos" select="position()"
/>
<xsl:value-of select="/descendant::th[$pos]"/>
</name>
<value>
<xsl:value-of select="."/>
</value>
</field>
</xsl:for-each>
</record>
</xsl:template>

</xsl:transform>

PS. I'm not sure why I need to assign position() to a variable first,
but without doing it this way it was always equating to "1".

My last post on this. I switched from using the version of Xalan (2.2)
that shipped with my JVM, to the latest 2.6. After reading the Xalan
FAQ to figure out how to override the default version of Xalan, I ran
my test and it succeeded. Thanks again for the help.
 

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

Members online

No members online now.

Forum statistics

Threads
473,954
Messages
2,570,116
Members
46,704
Latest member
BernadineF

Latest Threads

Top