I have declared a variable in my XSLT as given below :
<xsl:variable name="inline-array">
<Item>A</Item>
<Item>B</Item>
<Item>C</Item>
</xsl:variable>
I am accessing this variable as given below :
<xsl:param name="array"
select="document('')/*/xsl:variable[#name='inline-array']/*" />
<xsl:value-of select="$array[1]" />
This is working fine as long as my inline array has static contents. But my requirement is to dynamically assign values in the XSLT to the tag "Item" ie. Something like :
<xsl:variable name="inline-array">
<Item>$item1</Item>
<Item>$item2</Item>
<Item>$item3</Item>
</xsl:variable>
But, I tried all possible options without any luck. Any suggestions will be greatly appreciated. Any other options to fulfill my requirement is also welcome. Thanks.
One way to achieve this is to make use of an extension function, namely the node-set function, which returns a set of nodes from a result tree fragment.
First you would need to define the namespace for the extension functions like so
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
In this case, I am using the Microsoft extension functions, but others are available depending on which platform you are using. (http://exslt.org/common is another common one for non-Microsoft platforms).
Next, you define your "array" parameter (or variable, you wanted), like so.
<xsl:param name="array" select="msxsl:node-set($inline-array)"/>
Finally, you can then access this array like so
<xsl:value-of select="$array/Item[1]"/>
Putting this altogether in a simple example gives you this
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt">
<xsl:output method="text" />
<xsl:variable name="inline-array">
<Item>
<xsl:value-of select="$Item1"/>
</Item>
<Item>
<xsl:value-of select="$Item2"/>
</Item>
<Item>
<xsl:value-of select="$Item3"/>
</Item>
</xsl:variable>
<xsl:param name="Item1">1</xsl:param>
<xsl:param name="Item2">2</xsl:param>
<xsl:param name="Item3">3</xsl:param>
<xsl:param name="array" select="msxsl:node-set($inline-array)"/>
<xsl:template match="/">
<xsl:value-of select="$array/Item[1]"/>
</xsl:template>
</xsl:stylesheet>
When run, this simply outputs the following result:
1
Firstly, are you stuck with XSLT 1.0? Workarounds like accessing the stylesheet source code using document('') are very rarely needed if only you can move to XSLT 2.0.
Secondly, I think we need to look at the design of the stylesheet, and we can't really do that without a description of the problem you are trying to solve (as distinct from your attempts at a solution.)
Related
I have the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<XmlTest>
<Pictures attr="Pic1">Picture 1</Pictures>
<Pictures attr="Pic2">Picture 2</Pictures>
<Pictures attr="Pic3">Picture 3</Pictures>
</XmlTest>
While this XSL does what is expected (output the attr of the first picture):
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPicture" select="Pictures[1]">
</xsl:variable>
<xsl:value-of select="$FirstPicture/#attr"/>
</xsl:template>
</xsl:stylesheet>
It seems to be not possible to do the same inside the variable declaration using xsl:copy-of:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPicture">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
<xsl:value-of select="$FirstPicture/#attr"/>
</xsl:template>
</xsl:stylesheet>
Curious:
If I just select "$FirstPicture" instead of "$FirstPicture/#attr" in the second example, it outputs the text node of Picture 1 as expected...
Before you all suggest me to rewrite the code:
This is just a simplified test, my real aim is to use a named template to select a node into the variable FirstPicture and reuse it for further selections.
I hope someone could help me to understand the behavior or could suggest me a proper way to select a node with code which could be easily reused (the decission which node is the first one is complex in my real application). Thanks.
Edit (thanks to Martin Honnen):
This is my working solution example (which additionally uses a seperate template to select the requested picture node), using the MS XSLT processor:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:msxsl="urn:schemas-microsoft-com:xslt"
version="1.0">
<xsl:template match="/XmlTest">
<xsl:variable name="FirstPictureResultTreeFragment">
<xsl:call-template name="SelectFirstPicture">
<xsl:with-param name="Pictures" select="Pictures" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="FirstPicture" select="msxsl:node-set($FirstPictureResultTreeFragment)/*"/>
<xsl:value-of select="$FirstPicture/#attr"/>
<!-- further operations on the $FirstPicture node -->
</xsl:template>
<xsl:template name="SelectFirstPicture">
<xsl:param name="Pictures"/>
<xsl:copy-of select="$Pictures[1]"/>
</xsl:template>
</xsl:stylesheet>
Not nice, that it is in XSLT 1.0 not possible to output a node directly from a template, but with the extra variable it is at least not impossible.
Well with an XSLT 1.0 processor if you do
<xsl:variable name="FirstPicture">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
the variable is a result tree fragment and all you can do with that in pure XSLT 1.0 is output it with copy-of (or value-of). If you want to apply XPath you first need to convert the result tree fragment into a node set, most XSLT 1.0 processors support an extension function for that so try
<xsl:variable name="FirstPictureRtf">
<xsl:copy-of select="Pictures[1]"/>
</xsl:variable>
<xsl:variable name="FirstPicture" select="exsl:node-set(FirstPictureRtf)/Pictures/#attr">
where you define xmlns:exsl="http://exslt.org/common" in your stylesheet.
Note that you will need to check whether your XSLT 1.0 processor supports the EXSLT extension function or a similar one in another namespace (as for instance the various MSXML versions do).
I have an XSLT like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xalan="http://xml.apache.org/xalan">
<xsl:variable name="fooDocument" select="document('fooDocument.xml')"/>
<xsl:template match="/">
<xsl:apply-templates select="$fooDocument//*"/>
</xsl:template>
<xsl:template match="nodeInFooDocument">
<xsl:variable name="valueFromSource" select="//someSourceElement"/>
</xsl:template>
</xsl:transform>
In the second template, which matches nodes in the fooDocument.xml which is loaded with document(), I want to access nodes in the XML source the transformation is executed upon. This does not work with //someSourceElement, because apparently, XPath executes this path in the context of fooDocument.
A first workaround that comes to mind is this:
...
<!-- global variable -->
<xsl:variable name="root" select="/"/>
...
<!-- in the template -->
<xsl:variable name="valueFromSource" select="$root//someSourceElement"/>
...
But I cannot use this workaround, because actually, my variable is selected like this:
<xsl:variable name="valueFromSource" select="xalan:evaluate($someXPathString)"/>
$someXPathString is not crafted in the XSLT file, but loaded from fooDocument (and contains an absolute path like the one used above). Still, I need to somehow change the XPath context back to the XML source. A very hacky workaround I found is this:
<xsl:for-each select="$root[1]">
<xsl:variable name="valueFromSource" select="xalan:evaluate($someXPathString)"/>
</xsl:for-each>
The (useless) for-each loop changes the context back to the main XML source, thus the XPath evaluates correctly. But obviously, this is not an acceptable solution.
Is there a way to do this right, or can someone suggest a better workaround?
Even if you think your attempt with a for-each select="$root" to change the context document is not acceptable this is the right approach. So use that, there is no other way.
Have you considered doing all the computation that constructs $someXPathString using a series of global variables?
<xsl:variable name="fooDocument" select="document('fooDocument.xml')"/>
<xsl:variable name="temp1"
.. some computation using fooDocument ..
</xsl:variable>
<xsl:variable name="temp2"
.. some computation using temp1 ..
</xsl:variable>
<xsl:variable name="someXPathString"
.. some computation using temp2 ..
</xsl:variable>
<xsl:variable name="root" select="xalan:evaluate($someXPathString)"/>
my first post here - sure hope someone will know the answer!
I have been able to find solutions for many issues I had, but not for this one.
The questions and answers on this site on the same subject did not solve my issue...
I have an xml containing Format specifications like
<Format>
<TagNr>92</TagNr>
<Option>A</Option>
<Format>//[N]15d</Format>
</Format>
<Format>
<TagNr>92</TagNr>
<Option>B</Option>
<Format>//3!a/3!a/15d</Format>
</Format>
TagNr + option is a unique combination within this nodeset.
I defined a key to make using the set of formats easier:
<xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
I can indeed use this key and get the correct value, but only in non-special elements; I get an error "Error in XPath 2.0 expression Not a node item" when using this key within for-each or other constructs like the one below.
What I try to achieve is the following: In other nodes processed there is a string of options for which I wish to retrieve the format for each character.
For example:
<Tag>
<TagNr>92</TagNr>
<Options>AB</Options>
</Tag>
I have been trying lots of variants of the below but no luck:
<xsl:variable name="TN"><xsl:value-of select="TagNr"/></xsl:variable>
<xsl:variable name="optList">
<xsl:analyze-string select="./Options" regex="[A-Z]">
<xsl:matching-substring>
<xsl:variable name="TNO" select="concat($TN, .)"/>
<opt>
<tag><xsl:value-of select="$TNO"/></tag>
<fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
</opt>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:variable>
Splitting into individual characters using the regex goes fine and when retrieving (only) the value for opt/tag that goes fine too.
But when I add opt/fmt, I run into the mentioned error message for the Xpath expression select="key('xx', $TNO)".
I tried defining a variable based on the key function as suggested in another thread on this site, but did not succeed.
Can anyone help me?
The key() function (with two arguments) searches the document containing the context node. If the context item is not a node - for example, within analyze-string - then you will get this error, because it doesn't know which document to search. The answer is to use the third argument of key() to supply this information.
The problem is that the context changes in your analyze-string element. Maybe the following solution will help you.
For an XML file like that :
<a>
<Format>
<TagNr>92</TagNr>
<Option>A</Option>
<Format>//[N]15d</Format>
</Format>
<Format>
<TagNr>92</TagNr>
<Option>B</Option>
<Format>//3!a/3!a/15d</Format>
</Format>
<Tag>
<TagNr>92</TagNr>
<Options>AB</Options>
</Tag>
</a>
Consider the following XSLT :
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" exclude-result-prefixes="xs" version="2.0">
<xsl:output indent="yes"/>
<xsl:key name="xx" match="//Format/Format" use="concat(../TagNr, ../Option)"/>
<xsl:template match="/">
<result>
<xsl:apply-templates select="//Tag"/>
</result>
</xsl:template>
<xsl:template match="Tag">
<xsl:call-template name="createOPT">
<xsl:with-param name="str" as="xs:string" select="Options"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="createOPT">
<xsl:param name="str"/>
<xsl:if test="string-length($str) > 0">
<xsl:variable name="firstChar" select="substring($str,1,1)"/>
<xsl:variable name="TNO" select="concat(TagNr,$firstChar)"/>
<opt>
<tag><xsl:value-of select="$TNO"/></tag>
<fmt><xsl:value-of select="key('xx', $TNO)"/></fmt>
</opt>
<xsl:call-template name="createOPT">
<xsl:with-param name="str" select="substring($str,2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
The result is :
<?xml version="1.0" encoding="UTF-8"?>
<result>
<opt>
<tag>92A</tag>
<fmt>//[N]15d</fmt>
</opt>
<opt>
<tag>92B</tag>
<fmt>//3!a/3!a/15d</fmt>
</opt>
</result>
The easiest XSLT 2.0 way to process a string character by character is the following:
<xsl:for-each select="string-to-codepoints($vStr)">
<xsl:variable name="$vChar" select=
"codepoints-to-string(.)"/>
<!-- Process $vChar here: -->
</xsl:for-each/>
You can combine this with saving the original document context into a variable (say $vDoc) and using this variable as the 3rd argument of the key() function -- which is again an XSLT 2.0 - only feature.
So, you'll have something like:
key('xx', concat($TN, $vChar), $vDoc)
Summary:
Use the string-to-codepoints() and codepoints-to-string() functions for char-by-char processing.
Use the 3-rd argument of the key() function to specify context different from the current.
I'm trying to keep my xsl DRY and as a result I wanted to call the same template for 2 sections of an XML document which happen to be the same complex type (ContactDetails and AltContactDetails). Given the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<RootNode>
<Name>Bob</Name>
<ContactDetails>
<Address>
<Line1>1 High Street</Line1>
<Town>TownName</Town>
<Postcode>AB1 1CD</Postcode>
</Address>
<Email>test#test.com</Email>
</ContactDetails>
<AltContactDetails>
<Address>
<Line1>3 Market Square</Line1>
<Town>TownName</Town>
<Postcode>EF2 2GH</Postcode>
</Address>
<Email>bob#bob.com</Email>
</AltContactDetails>
</RootNode>
I wrote an XSL Stylesheet as follows:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/">
<PersonsName>
<xsl:value-of select="RootNode/Name"/>
</PersonsName>
<xsl:call-template name="ContactDetails">
<xsl:with-param name="data"><xsl:value-of select="RootNode/ContactDetails"/></xsl:with-param>
<xsl:with-param name="elementName"><xsl:value-of select="'FirstAddress'"/></xsl:with-param>
</xsl:call-template>
<xsl:call-template name="ContactDetails">
<xsl:with-param name="data"><xsl:value-of select="RootNode/AltContactDetails"/></xsl:with-param>
<xsl:with-param name="elementName"><xsl:value-of select="'SecondAddress'"/></xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="ContactDetails">
<xsl:param name="data"></xsl:param>
<xsl:param name="elementName"></xsl:param>
<xsl:element name="{$elementName}">
<FirstLine>
<xsl:value-of select="$data/Address/Line1"/>
</FirstLine>
<Town>
<xsl:value-of select="$data/Address/Town"/>
</Town>
<PostalCode>
<xsl:value-of select="$data/Address/Postcode"/>
</PostalCode>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
When i try to run the style sheet it's complaining to me that I need to:
To use a result tree fragment in a path expression, either use exsl:node-set() or specify version 1.1
I don't want to go to version 1.1.. So does anyone know how to get the exsl:node-set() working for the above example?
Or if someone knows of a better way to apply the same template to 2 different sections then that would also really help me out?
Thanks
Dave
You are rolling this up from the wrong end (the wrong end being nearly always: trying to apply the imperative programming paradigm to XSLT).
This is really easy to do via template matching.
<xsl:template match="RootNode">
<PersonsName>
<xsl:value-of select="Name"/>
</PersonsName>
<xsl:apply-templates select="ContactDetails|AltContactDetails" />
</xsl:template>
<xsl:template match="ContactDetails|AltContactDetails">
<xsl:copy>
<FirstLine>
<xsl:value-of select="Address/Line1"/>
</FirstLine>
<Town>
<xsl:value-of select="Address/Town"/>
</Town>
<PostalCode>
<xsl:value-of select="Address/Postcode"/>
</PostalCode>
</xsl:copy>
</xsl:template>
Let go of the notion that you have to tell the XSLT processor what to do (through making named templates and calling them, "imperative style").
The XSLT processor chooses what templates to call. Starting at the root (/) it recursively checks for matching templates for every node it visits. It traverses your input XML all on its own - your only job is it to supply it with matching templates for those nodes you want to have processed in a special way.
You can drop in a custom template for those nodes that need special treatment and trust your XSLT processor with calling it once they come up. All you need to make sure in your templates is that traversal goes on by declaring an appropriate <xsl:apply-templates />.
Why not make the template
<xsl:template match="ContactDetails|AltContactDetails">
and do the test to determine the output element name inside the template instead?
Please help me out guys. I'm just trying to declare a simple result tree fragment and iterate over it.
...
<xsl:variable name="rtf">
<item-list>
<item id="1">one</item>
<item id="2">two</item>
<item id="3">three</item>
<item id="4">four</item>
</item-list>
</xsl:variable>
<xsl:for-each select="msxsl:node-set($rtf)/item-list/item">
<xsl:value-of select="#id"/>
</xsl:for-each>
...
Am I completely mistaken about how this works?
Edit:
I'm using .NET XslCompiledTransform and have the correct msxsl namespace declarations - xmlns:msxsl="urn:schemas-microsoft-com:xslt"
The transformating executes fine - the problem is that nothing is output
My suspicion is that you have a default namespace declared in your stylesheet. That would effectively place the <item-list> and <item> elements into a namespace. To select namespace-qualified elements using XPath 1.0, you must always use a prefix in the expression.
So if you have something like this at the top of your stylesheet:
<xsl:stylesheet xmlns="http://example.com"...>
Then you'll need to also add this:
<xsl:stylesheet xmlns="http://example.com" xmlns:x="http://example.com"...>
And then use the "x" prefix in your XPath expression:
<xsl:for-each select="msxsl:node-set($rtf)/x:item-list/x:item">
<xsl:value-of select="#id"/>
</xsl:for-each>
Let me know if that did the trick. I'm only speculating here.
Unlike MSXSL, XslCompiledTransform provides node-set() function where it is supposed to be - in EXSLT Common namespace:
<xsl:stylesheet xmlns:exslt="http://exslt.org/common">
...
<xsl:for-each select="exslt:node-set($rtf)/item-list/item">
...
</xsl:stylesheet>
This looks OK to me.
Have you correctly declared the msxsl namespace for the extension functions though? Something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
I am assuming you are using the Microsoft XSLT processor here