tags:

views:

66

answers:

2

Hi, I want to have a key value map in xsl and so defined a variable which has an xml fragment, but later when i try to access the xml nodes in the variable i get an error that type of the xpath xpression cannot be resolved. Below is the xsl-

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:template match="/">
    <xsl:variable name="map">
        <map>
            <entry key="key-1">value1</entry>
            <entry key="key-2">value2</entry>
            <entry key="key-3">value3</entry>
        </map>
    </xsl:variable>
    <output>
        <xsl:vlaue-of select="$map/entry[@key='key-1']">
        </xsl:vlaue-of>
    </output>
</xsl:template>

Best Regards, Keshav

+8  A: 

You cannot use a result tree fragment in a XPath expression in XSLT 1.0. You need to (1) either use XSLT 2.0 or (2) use fn:document() to be able to get to the map values. I've answered a similar question recently which will work in your case aswell.

The XSLT 1.0 solution:

<xsl:value-of select="document('')//xsl:variable[@name='map']/map/entry[@key='key-1']"/>

As described in the XSLT 1.0 specification:

document("") refers to the root node of the stylesheet; the tree representation of the stylesheet is exactly the same as if the XML document containing the stylesheet was the initial source document.

However, you don't need to use xsl:variable for this. You could specify your map node directly under xsl:stylesheet, but you must remember that a top level elements must have a non null namespace URI:

<xsl:stylesheet 
  version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:my="some.uri" exclude-result-prefixes="my">

  <my:map>
    <entry key="key-1">value1</entry>
    <entry key="key-2">value2</entry>
    <entry key="key-3">value3</entry>
  </my:map>

  <xsl:template match="/">
    <output>
      <xsl:value-of select="document('')/*/my:map/entry[@key='key-1']"/>
    </output>
  </xsl:template>
</xsl:stylesheet>

In XSLT 2.0 you could've done it the way you wanted to:

  <xsl:variable name="map">
    <entry key="key-1">value1</entry>
    <entry key="key-2">value2</entry>
    <entry key="key-3">value3</entry>
  </xsl:variable>

  <xsl:template match="/">
    <output>
      <xsl:value-of select="$map/entry[@key='key-1']"/>
    </output>
  </xsl:template>
Per T
Hi,But then when i did copy-of the result tree fragment was printed properly and not only that even value-of only printed the text nodes in the result tree.BR,Keshav
keshav.veerapaneni
Good answer (+1).
Dimitre Novatchev
@Keshav: If you only use `copy-of`/`value-of` on the variable `$map` without trying to use it as a node set, it will work. But once you try to make your way down the result tree fragment with a XPath expression it will fail.
Per T
@Per T: +1 Good answer. The first statement should be: "You can't use RTF as `/` operator left hand". As the OP has commented, you can copy a RTF or use it in expression (works as its string value, except for boolean conversion)
Alejandro
@Alejandro: You're ofcourse right! Thanks!
Per T
Thanks Per T :)
keshav.veerapaneni
+3  A: 

You can sort of work around XSLT 1.0 missing support for using the variable's contents as a node set. You'll have to rely on extensions added by the parser's maker. For example, Microsoft has offered a function to work around this: node-set()

Your XSL will look like this:

<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:msxsl="urn:schemas-microsoft-com:xslt">
    <xsl:template match="/">
        <xsl:variable name="map">
            <map>
                <entry key="key-1">value1</entry>
                <entry key="key-2">value2</entry>
                <entry key="key-3">value3</entry>
            </map>
        </xsl:variable>
        <output>
            <xsl:value-of select="msxsl:node-set($map)/map/entry[@key='key-1']"/>
        </output>
    </xsl:template>
</xsl:stylesheet>

Notice the namespace and the msxsl-prefix here. This will only work in applications based on Microsoft's parser (for example: Internet Explorer utilizes it, as well as .NET). Other parsers may or may not have such an extension (Saxxon does, for example, but it's named a bit differently). But, it eliminates depending on XSLT 2.0, as this will work fine in XSLT 1.0 and Microsoft has yet to support XSLT 2.0 in their XML library (unless they've added it recently).

Depending on the parser you're using, the above may work fine for you, otherwise Per T's answer is better for you.

Rob
+1 Good addition to my answer. Usally I try to avoid vendor specific extensions, but it's definetly one way to solve it!
Per T
@Rob: @Per_T's answer is better in all cases, because it is a completely *portable* solution. THere are problems that are very difficult to solve without the `xxx:node-set()` extension, but the current problem isn't one of these. Also, in advocating an `xxx:node-set()` solution one should always refer to the `exslt:node-set()`, which is quite portable because many vendors provide EXSLT implementations.
Dimitre Novatchev
@Dimitre I must admit Per T's answer has taught me some things about XSL that I didn't know (I'm not an expert), so I didn't know much better and I definitely upvoted his answer for being great. ;) I do slightly argue that, while portability is a very good thing, its importance is context dependent so I wouldn't want to consider my answer quite terrible: it might do fine. ;) It doesn't really pollute your XSL much as it's not too invasive. This is why we're happy about this solution, even if I agree with you that it's not perfect. :) Thanks for your input!
Rob