result tree fragment to node-set: generic approach

2019-01-26 16:32发布

问题:

Answering another thread (see stackoverflow: generate css color schemes) I bumped into the issue below, where different xsl engines seem to need different approaches in transforming result tree fragments into node-sets.

Simplifying the issue (but see link above for the full story behind this), I wish to have an inline tree containing a list of color values. As this has to be used in Xpath expressions, I had to create a node-set from it specifically for MSXML x.x xsl engine (XML Spy built-in had less trouble interpreting Xpath expressions contaning variables constructed as rtf's).
Yet another thread stackoverflow: automating-exsltnode-set helped me there. The resulting node-set is used in creating a new variable rtf from the input XML.
Again, MSXML complains when the new variable is used in Xpath expressions, so I used the node-set function to create a node-set from it.
So far so good, and MSXML x.x does not error any more.
But when I run the same in XML Spy built-in or Saxon 9he, I get another error: it seems that the node-set function is unknown:

Cannot find a matching 1-argument function named {urn:schemas-microsoft-com:xslt}node-set() in variable colorList

Note that this two-step approach is not needed in this particular example, but as I said I simplified things; I just wish to know how to write an XSLT 1.0 transformation that will work in all xsl engines.

The XSLT I used:

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet
    version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:std="http://whatever"
    xmlns:exslt="urn:schemas-microsoft-com:xslt"
    exclude-result-prefixes="std exslt">

    <xsl:output method="xml" indent="yes"/>

    <std:colors>
        <color>#0000FF</color>
        <color>#FF0000</color>
    </std:colors>

    <xsl:variable name="colors" select="document('')/*/std:colors"/>

    <xsl:variable name="std:colorList">
        <xsl:for-each select="//testid">
            <xsl:variable name="pos" select="position() mod 2"/>
            <xsl:element name="color">
                <xsl:attribute name="testid"><xsl:value-of select="."/></xsl:attribute>
                <xsl:value-of select="$colors/color[$pos + 1]"/>
            </xsl:element>
        </xsl:for-each>
    </xsl:variable>

    <xsl:variable name="colorList" select="exslt:node-set($std:colorList)"/>

    <xsl:template match="/">
        <output>
            <xsl:copy-of select="$colorList/color"/>
        </output>
   </xsl:template>

</xsl:stylesheet>

Input file:

<?xml version="1.0" standalone="yes"?>
<NewDataSet>
  <defects>
    <testid>111</testid>
  </defects>
  <defects>
    <testid>999</testid>
  </defects>
</NewDataSet>

Result in MSXML 3.0/4.0/6.0:

<?xml version="1.0" encoding="UTF-16"?>
<output>
<color testid="111">#FF0000</color>
<color testid="999">#0000FF</color>
</output>

Result in Saxon9he:

Cannot find a matching 1-argument function named {urn:schemas-microsoft-com:xslt}node-set()
in variable colorList

result in XML Spy built-in xsl engine:

Error in XPath expression
Unknown function - Name and number of arguments do not match any function signature in the static context - 'urn:schemas-microsoft-com:xslt:node-set'

回答1:

For processors other than MSXML, use the exslt:node-set() function. (http://www.exslt.org/). (It's a little odd to bind the exslt prefix to the Microsoft version of the function - that had me confused for a while!)

You can test which functions are available using function-available():

<xsl:choose>
  <xsl:when test="function-available('exslt:node-set')"...
  <xsl:when test="function-available('msxsl:node-set')"...

For Saxon-HE and other XSLT 2.0 processors you don't need any of these functions, so use

<xsl:when test="xsl:version='2.0'">


回答2:

To avoid having to do

 <xsl:choose>
 <xsl:when test="function-available('exslt:node-set')"...

every time you need this, you can define exslt node set for xslt2 engines (using xsl:function) and for msxml (using msxsl:script) and then just use exslt:node-set function in the rest of your document.

http://dpcarlisle.blogspot.co.uk/2007/05/exslt-node-set-function.html

shows you how to define it for msxml and for xslt2 engines you can xsl:include a stylesheet

<xsl:stylesheet version="2.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:exslt="http://exslt.org/common"
        >

<xsl:function name="exslt:node-set">
  <xsl:param name="rtf"/>
  <xsl:sequence select="$rtf"/>
</xsl:function>

<xsl:stylesheet>

XSLT1's forward compatibility rules mean that it is safe to include this, an XSLT1 engine will just ignore it.



回答3:

Well Saxon 9 is an XSLT 2.0 processor and with XSLT 2.0 one of the major improvements is that the distinction between node-sets and result tree fragments has gone and you don't need any extension function at all as you don't need to force any coercion anymore. So with stylesheets targetting any XSLT 2.0 processor you should simply drop any attempt to use an extension function like that, then the stylesheet will work. If you want to run the same stylesheet with both XSLT 1.0 and 2.0 processors then I see a problem but I don't think there is an easy solution. You will need to make use of http://www.w3.org/TR/xslt#function-function-available and system-property to distinguish processors and availability of extension functions.



回答4:

Microsoft's .NET XsltCompiledTransform XSLT processor supports exslt:node-set().

For MSXML one can use my own implementation of a subset of EXSLT functions -- for MSXML. You can find detailed description and a link to the download here:

http://www.xml.com/pub/a/2003/08/06/exslt.html



回答5:

Well, you can easily do it without any checks. Just follow the pattern described in here: http://dpcarlisle.blogspot.co.uk/2007/05/exslt-node-set-function.html

<xsl:stylesheet
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:exslt="http://exslt.org/common"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  exclude-result-prefixes="exslt msxsl">


<msxsl:script language="JScript" implements-prefix="exslt">
 this['node-set'] =  function (x) {
  return x;
  }
</msxsl:script>


<xsl:variable name="x">
  <y/>
</xsl:variable>

<xsl:template match="x">
  <html>
    <head><title>test exslt node set</title></head>
    <body>
      <xsl:apply-templates select="exslt:node-set($x)/*"/>
    </body>
  </html>
</xsl:template>

<xsl:template match="y">
  <p>node set!</p>
</xsl:template>

</xsl:stylesheet>

It definitely works in FF, Chrome and IE7+