[xsl] Cross-referencing accross multiple documents and sorting: Bug in MSXML4 SP1 when using current() in an xsl:sort/@select value

Subject: [xsl] Cross-referencing accross multiple documents and sorting: Bug in MSXML4 SP1 when using current() in an xsl:sort/@select value
From: David Chryst <dchryst@xxxxxxxxx>
Date: Mon, 29 Apr 2002 11:20:46 -0700 (PDT)
Subject: Cross-referencing accross multiple documents
and sorting: Bug in MSXML4 SP1 when using current() in
an xsl:sort/@select value
Date: 29 Apr, 2002

FYI:
There is a bug in MSXML4 when using current() in an
xsl:sort/@select value.  In that context, current()
resolves to the wrong node.  Microsoft will release a
fix with MSXML 4.0 Serivce Pack 2.  No date has been
set for that release. 

Normally, you can use a key() function when doing a
cross-reference sort.  However, sometimes you have to
cross-reference data in 2 separate XML documents and
do a sort.  For example, suppose you get 1 XML
document from some SOAP service that includes month
names in the data.  You could define the order of
months in some static XML document and cross-refence
that month-order XML document in your XSLT script
using the document() function.  In this case, you
can't use a key element with the data in the static
XML file!  

Using the current() function: rather than performing 2
separate transforms, I was able to use a single
transform, but I had to use the current() function
call in the xsl:sort/@select value.  This worked great
in production for several XSLT scripts when I used
MSXML3, but my sort returned data in document order
once I upgraded to MSXML4.

Here's an example.  This example is simpler than what
I'm using in production, but it demonstrates the bug:

Here's my dynamically-generated XML:  Execute the XSLT
against this document.
<large-collection>
	<collection name = "groupA">
		<ref refid="1"/>
		<ref refid="3"/>
	</collection>
	<collection name = "groupB">
		<ref refid="3"/>
		<ref refid="4"/>
	</collection>
	<collection name = "groupC">
		<ref refid="1"/>
		<ref refid="2"/>
		<ref refid="3"/>
		<ref refid="4"/>
		<ref refid="5"/>
		<ref refid="6"/>
		<ref refid="7"/>
		<ref refid="8"/>
		<ref refid="9"/>
	</collection>
</large-collection>


Here's my static XML document that I am
cross-referencing.  I saved it to c:\temp\parts.xml 
Do not execute the XSLT against this XML document. 
This file is cross_refenced in the xsl.  If you save
this XML as something other than c:\temp\parts.xml,
you must alter the XSLT script.
<parts>
	<part id="1" type="type-a" name="name-a"/>
	<part id="2" type="type-a" name="name-b"/>
	<part id="3" type="type-b" name="name-c"/>
	<part id="4" type="type-d" name="name-d"/>
	<part id="5" type="type-b" name="name-e"/>
	<part id="6" type="type-c" name="name-f"/>
	<part id="7" type="type-d" name="name-g"/>
	<part id="8" type="type-b" name="name-h"/>
	<part id="9" type="type-c" name="name-i"/>
</parts>


Here's my XSLT that takes the parts referenced in
groupC of the dynamically-generated XML, and sorts all
the parts in that group by type, then by name:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
version="1.0">
	<xsl:output method="html" />
	<xsl:variable
name="MyGroup">groupC</xsl:variable><!--normally this
would be a param-->
	<xsl:variable name="MyParts"
select="document('c:\temp\parts.xml')/parts"/>

	<xsl:template match="/large-collection">
		<xsl:apply-templates select="collection[@name =
$MyGroup]" />
	</xsl:template>

	<xsl:template match="/large-collection/collection">
		<xsl:for-each select="ref">
			<xsl:sort select="$MyParts/part[@id =
(current())/@refid]/@type" order="ascending"/>
			<xsl:sort select="$MyParts/part[@id =
(current())/@refid]/@name" order="ascending"/>
			type: <xsl:value-of
select="concat($MyParts/part[@id =
(current())/@refid]/@type, '  name: ',
$MyParts/part[@id = (current())/@refid]/@name, '  id:
', $MyParts/part[@id = (current())/@refid]/@id)"/>
		</xsl:for-each>
	</xsl:template>

</xsl:stylesheet>


Here's the output I get when I execute the XSLT using
MSXML3 DOM and processor:
type: type-a  name: name-a  id: 1
type: type-a  name: name-b  id: 2
type: type-b  name: name-c  id: 3
type: type-b  name: name-e  id: 5
type: type-b  name: name-h  id: 8
type: type-c  name: name-f  id: 6
type: type-c  name: name-i  id: 9
type: type-d  name: name-d  id: 4
type: type-d  name: name-g  id: 7


Here's the output I get when I execute the same XSLT
using MSXML4 DOM and processor:
type: type-a  name: name-a  id: 1
type: type-a  name: name-b  id: 2
type: type-b  name: name-c  id: 3
type: type-d  name: name-d  id: 4
type: type-b  name: name-e  id: 5
type: type-c  name: name-f  id: 6
type: type-d  name: name-g  id: 7
type: type-b  name: name-h  id: 8
type: type-c  name: name-i  id: 9


Possible workarounds:
1) Normally, one can avoid using the current function
call by simply using a key.  That is not possible in
this case because XSLT does not allow you to form a
key from an outside document XML tree.

2) You could duplicate the data kept in
c:\temp\parts.xml in every dynamically-generated XML
document (by modifying the SOAP service that generates
the XML or by running an extra transform) but that
seems a little in-elegant!

3) You can revert back to MSXML3 or some other XSLT
processor that allows use of current() in an
xsl:sort/@select value.

4) You can wait for MSXML4 SP2

David Chryst


__________________________________________________
Do You Yahoo!?
Yahoo! Health - your guide to health and wellness
http://health.yahoo.com

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list


Current Thread