XSLT can not query input XML from within function - xslt

I am trying to create a function that takes one pram and then queries the input XML in several ways based on the input. My problem is that when I try to query the input xml and store a value in a variable while in the function I get the error:
'/' cannot select the root node of the tree containing the context item: the context item is absent
How can I query the XML from within the function? Below is the XSLT
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lang="info:lc/xmlns/codelist-v1"
xmlns:foo="http://whatever">
<xsl:output indent="yes" />
<xsl:function name="foo:get-prefered">
<xsl:param name="field-name"/>
<xsl:variable name="var1" select="sources/source[#type='A']/name" />
</xsl:function>
<xsl:template match="/">
<xsl:value-of select="foo:get-prefered(10)"></xsl:value-of>
</xsl:template>
</xsl:stylesheet>

This is as per the W3C specification where it states...
Within the body of a stylesheet function, the focus is initially
undefined; this means that any attempt to reference the context item,
context position, or context size is a non-recoverable dynamic error.
The solution is to pass in a node (such as the root node) as a parameter
<xsl:function name="foo:get-prefered">
<xsl:param name="root"/>
<xsl:param name="field-name"/>
<xsl:variable name="var1" select="$root/sources/source[#type='A']/name"></xsl:variable>
<xsl:value-of select="$var1" />
</xsl:function>
<xsl:template match="/">
<xsl:value-of select="foo:get-prefered(/, 10)"></xsl:value-of>
</xsl:template>
Alternatively, you could consider using a named-template instead of a function, perhaps:
<xsl:template name="get-prefered">
<xsl:param name="field-name"/>
<xsl:variable name="var1" select="sources/source[#type='A']/name"></xsl:variable>
<xsl:value-of select="$var1" />
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="get-prefered">
<xsl:with-param name="field-name">10</xsl:with-param>
</xsl:call-template>
</xsl:template>

Related

Applying template more than once on an element

Im struggling a bit with the following. Given that items may contain several part and spec pairs, i want to process each pair, or apply the template to the item more than once.
Currently, each item is processed once and I'm missing the second part.
<figure>
<list>
<item>
<part>
<p>74174</p>
</part>
<spec>
<u>a1</u>
</spec>
<part>
<p>75375</p>
</part>
<spec>
<u>a4</u>
</spec>
</item>
</list>
</figure>
Stylesheet:
<xsl:if test="$a = 'abc'">
<xsl:apply-templates mode="pt" select="/figure/list/item" />
</xsl:if>
<xsl:template mode="pt" match="item[./part]">
<xsl:call-template name="ptt">
<xsl:with-param name="p"><xsl:value-of select="part/p"/>
</xsl:with-param>
<xsl:with-param name="pr">
<xsl:if test="spec/u">
<xsl:element name="pr">
<xsl:element name="rpn">
<xsl:value-of select="spec/u"/>
</xsl:element>
<xsl:element name="rtn">Alt</xsl:element>
</xsl:element>
</xsl:if>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
I simplified and cropped the code a bit since it goes on and on and on..
Edit: This next one is generating my new elements based solely on the input params
<xsl:template name="ptt">
<xsl:param name="p"/>
<xsl:param name=u"/>
</xsl:template>
It seems that you are unwilling to show a self-contained and complete example (for your future questions, do not make it quite so hard for people to help you), so I am not sure whether the following is helpful to you.
Assuming the XML input you have shown, the stylesheet below demonstrates a way to call a named template for each pair of spec and part elements, as you requested.
It uses xsl:for-each-group (which is exclusive to XSLT 2.0, but you did not tell which version of XSLT you use) to define groups that start with a part element, resulting in the said pairs. Then, for each group, the named template ptt is invoked with p and u as parameters.
Stylesheet
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:strip-space elements="*"/>
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<xsl:template match="item">
<xsl:for-each-group select="*" group-starting-with="part">
<xsl:call-template name="ptt">
<xsl:with-param name="p" select="current-group()[1]/p"/>
<xsl:with-param name="u" select="current-group()[2]/u"/>
</xsl:call-template>
</xsl:for-each-group>
</xsl:template>
<xsl:template name="ptt">
<xsl:param name="p"/>
<xsl:param name="u"/>
<result>
<part><xsl:value-of select="$p"/></part>
<spec><xsl:value-of select="$u"/></spec>
</result>
</xsl:template>
</xsl:transform>
XML Output
<?xml version="1.0" encoding="UTF-8"?>
<result>
<part>74174</part>
<spec>a1</spec>
</result>
<result>
<part>75375</part>
<spec>a4</spec>
</result>
Well with call-template and a parameter <xsl:with-param name="p"><xsl:value-of select="part/p"/></xsl:with-param> you are making it harder than it needs to be, assuming XSLT 1.0 the <xsl:with-param name="p"><xsl:value-of select="part/p"/></xsl:with-param> fills the parameter p with a text node of the string value of the first element selected by part/p. So at least use simply <xsl:with-param name="p-elements" select="part/p"/>, then the parameter value is a node-set with all p elements.
Better yet, simply use template matching and apply-templates consistently, then you don't have to struggle with call-template and with-param.
Based on your comments you could just use
<xsl:template mode="pt" match="item[part]">
<xsl:apply-templates select="part" mode="pt"/>
</xsl:template>
<xsl:template match="part" mode="pt">
<xsl:variable name="spec" select="following-sibling::*[1][self::spec]"/>
...
</xsl:template>

Testing whether a node with particular content exists using xslt

I am trying to merge the elements from two separate web.xml files using XSLT. For example, if web-1.xml and web-2.xml are being merged, and I'm processing web-1.xml, I want all elements in web-2.xml to be added into the result, except any that already exist in web-1.xml.
In the XSLT sheet, I have loaded the document whose servlet's are to be merged into the other document using:
<xsl:variable name="jandy" select="document('web-2.xml')"/>
I then have the following rule:
<xsl:template match="webapp:web-app">
<xsl:copy>
<!-- Copy all of the existing content from the document being processed -->
<xsl:apply-templates/>
<!-- Merge any <servlet> elements that don't already exist into the result -->
<xsl:for-each select="$jandy/webapp:web-app/webapp:servlet">
<xsl:variable name="servlet-name"><xsl:value-of select="webapp:servlet-name"/></xsl:variable>
<xsl:if test="not(/webapp:web-app/webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
<xsl:copy-of select="."/>
</xsl:if>
</xsl:for-each>
</xsl:copy>
</xsl:template>
The problem I'm having is getting the test in the if correct. With the above code, the test always evaluates to false, whether a servlet-name element with the given node exists or not. I have tried all kinds of different tests but with no luck.
The relevant files are available at http://www.cs.hope.edu/~mcfall/stackoverflow/web-1.xml, and http://www.cs.hope.edu/~mcfall/stackoverflow/transform.xslt (the second web-2.xml is there as well, but StackOverflow won't let me post three links).
provide an anchor for the first document, just before the for-each loop:
<xsl:variable name="var" select="."/>
then, use it in your if:
<xsl:if test="not($var/webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
Your template matches XPATH webapp:webapp from web-1.xml and
you are refencing absolute XPATH if your xsl:if condition: /webapp:web-app/webapp:servlet/webapp:servlet-name[text() = $servlet-name]. Try to do it using relative XPATH:
<xsl:if test="not(webapp:servlet/webapp:servlet-name[text() = $servlet-name])">
<xsl:copy-of select="."/>
</xsl:if>
I haven't checked it, so you have to give it a try.
Also, it would be easier if you could provide web-1.xml and web-2.xml files.
EDIT
The following XSLT merges two files - the only problem appears when there are sections of the same type (like listener) in two places of the input XML.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:webapp="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xpath-default-namespace="http://java.sun.com/xml/ns/javaee">
<xsl:output indent="yes"/>
<xsl:variable name="jandy" select="document('web-2.xml')"/>
<xsl:template match="/">
<xsl:element name="web-app">
<xsl:for-each select="webapp:web-app/*[(name() != preceding-sibling::node()[1]/name()) or (position() = 1)]">
<xsl:variable name="nodeName" select="./name()"/>
<xsl:variable name="web1" as="node()*">
<xsl:sequence select="/webapp:web-app/*[name()=$nodeName]"/>
</xsl:variable>
<xsl:variable name="web2" as="node()*">
<xsl:sequence select="$jandy/webapp:web-app/*[name() = $nodeName]"/>
</xsl:variable>
<xsl:copy-of select="$web1" copy-namespaces="no"/>
<xsl:for-each select="$web2">
<xsl:variable name="text" select="./*[1]/text()"/>
<xsl:if test="count($web1[*[1]/text() = $text]) = 0">
<xsl:copy-of select="." copy-namespaces="no"/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>

XSLT: Passing URL querystring as a parameter

I know this is an old question that has been passed around SO several times but I was wondering whether anyone can expand on whether a URL that has a querystring attached to it can be stripped out via XSLT 1.0 and can be used as a parameter for later use of the XSLT transformation.
For example, I have a URL of http://www.mydomain.com/mypage.htm?param1=a&param2=b
via XSLT, I am looking for a result of something along the lines of:
<xsl:param name="param1">a</xsl:param> and <xsl:param name="param2">b</xsl:param>
where both parameter name (param1, param2) and it's value (a, b) has been extracted from the quesrystring to allow me to use $param1 and $param2 later on say in an if condition
e.g. <xsl:if test="$param1 = 'a'> comes out true but if we use <xsl:if test="$param1 = 'b'> comes out false.
I have seen a similar question here: Retrieve page URL params or page URL in XSLT which uses the str-split-to-words template but I have unsuccessfully got it working (possibly due to me implementing it the wrong way) so any working examples of how it can be done in practice would be massively beneficial.
XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="http://exslt.org/common">
<xsl:import href="http://fxsl.cvs.sourceforge.net/viewvc/fxsl/fxsl-xslt2/f/strSplit-to-Words.xsl"/>
<xsl:output indent="yes" method="html"/>
<xsl:template match="/">
<xsl:variable name="vwordNodes">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="$pQString"/>
<xsl:with-param name="pDelimiters" select="'?&'"/>
</xsl:call-template>
</xsl:variable>
<xsl:apply-templates select="ext:node-set($vwordNodes)/*"/>
</xsl:template>
<xsl:template match="word">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
There are a few problems in your code:
<xsl:import href="http://fxsl.cvs.sourceforge.net/viewvc/fxsl/fxsl-xslt2/f/strSplit-to-Words.xsl"/> I doubt that the wanted stylesheet can be imported directly from its SourceForge view page -- especially taking into account, that it itself imports other FXSL stylesheets. The correct way to use FXSL is to download it to the local computer and reference its stylesheets off the file location it resides in at the local computer.
...
.2. <xsl:with-param name="pStr" select="$pQString"/> This will produce a compilation error because you missed to define the $pQString global/external parameter. You need to define this parameter at global level. It can be given a default value (for example a particular URL) for easier testing. However, the idea of using this parameter is that the invoker of the transformation should pass this parameter to the transformation.
.3. The results of the transformation are written to the output. While this is good for demonstration purposes, you want to be able to use these results later in the transformation. The way to do this is to capture these results in a variable, make another variable from it, with a regular tree (from its RTF type) and then reference the nodes of this last variable.
Here is an example of the code you want (provided that you have downloaded FXSL, unzipped the distribution and saved this code in the same directory as the unzipped distribution of FXSL):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
>
<xsl:import href="strSplit-to-Words.xsl"/>
<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:param name="pUrl" select=
"'http://www.mydomain.com/mypage.htm?param1=a&param2=b'"/>
<xsl:param name="pQString" select=
"substring-after($pUrl, '?')"
/>
<xsl:template match="/">
<xsl:variable name="vwordNodes">
<xsl:call-template name="str-split-to-words">
<xsl:with-param name="pStr" select="$pQString"/>
<xsl:with-param name="pDelimiters"
select="'?&'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vrtfqueryParams">
<xsl:apply-templates select="ext:node-set($vwordNodes)/*"/>
</xsl:variable>
<xsl:variable name="vqueryParams" select="ext:node-set($vrtfqueryParams)/*"/>
<xsl:value-of select="$vqueryParams/#name[. ='param1']"/>
<xsl:text> : </xsl:text>
<xsl:value-of select="$vqueryParams[#name = 'param1']"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="$vqueryParams/#name[. ='param2']"/>
<xsl:text> : </xsl:text>
<xsl:value-of select="$vqueryParams[#name = 'param2']"/>
</xsl:template>
<xsl:template match="word">
<param name="{substring-before(.,'=')}">
<xsl:value-of select="substring-after(.,'=')"/>
</param>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used in this demo), the wanted, correct result -- the query-string parameters referenced of a results variable by name -- is produced:
param1 : a
param2 : b

xsl: passing variable into xsl file path

Is there a way pass a attribute, variable combinations directly into the path of the url with xsl?
Example:
http://something.xsl?asdf=12&attribute2=1234
I would like to use these attributes and values to enable certain flags inside the xsl file.
Use concat()
<xsl:variable name="url" select="concat($currURL, 'flag=true')" />
Yeah, you just need to escape the & as &
I think you mean can the stylesheet access its own URI to access the parameters. In XSLT2 you can use the static-base-uri() function to access the URI, and then you can split it up to extract the query parameters using the regex string functions. In XSLT1 it is not possible, you would need to pass the information in as stylesheet parameters, and XSLT1 stylesheet has no access to the URI of the source or itself.
Here is a complete XSLT 1.0 solution, assuming that the URL is passed as an external parameter to the transformation:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common" exclude-result-prefixes="ext">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pUrl" select=
"'http://something.xsl?asdf=12&attribute2=1234'"/>
<xsl:template match="/">
<xsl:variable name="vQuery" select="substring-after($pUrl, '?')"/>
<xsl:variable name="vrtfQueryItems">
<xsl:call-template name="buildQueryItems">
<xsl:with-param name="pQuery" select="$vQuery"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vQueryItems"
select="ext:node-set($vrtfQueryItems)/*"/>
<xsl:copy-of select="$vQueryItems"/>
</xsl:template>
<xsl:template name="buildQueryItems">
<xsl:param name="pQuery"/>
<xsl:if test=
"string-length($pQuery) > 0">
<xsl:variable name="vQuery" select="concat($pQuery, '&')"/>
<xsl:variable name="vItem" select="substring-before($vQuery, '&')"/>
<param name="{substring-before(concat($vItem, '='), '=' )}">
<xsl:value-of select="substring-after($vItem, '=')"/>
</param>
<xsl:call-template name="buildQueryItems">
<xsl:with-param name="pQuery" select="substring-after($pQuery, '&')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted result is produced:
<param name="asdf">12</param>
<param name="attribute2">1234</param>

How contruct in xslt a tree in a variable

I want to create a variable like that :
<xsl:variable name="mytree" >
<foos>
<foo>bar</foo>
<foo>bar</foo>
<foo>bar</foo>
<foo>bar</foo>
<foos>
</xsl:variable>
to use it like in :
<xsl:call-template name="myTemplate">
<xsl:with-param name='aTree' select='$mytree' />
</xsl:call-template>
<xsltemplate name="myTemplate">
<xsl:param name="aTree" />
[My code that treat $aTree like a Tree]
</xsl:template>
My question is : is it possible to create a Tree variable and How?
To achieve this, you probably need 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).
Then, you can access the elements in your variable like so (as an example)
<xsl:apply-templates select="msxsl:node-set($aTree)/foos/foo"/>
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" exclude-result-prefixes="msxsl">
<xsl:variable name="mytree">
<foos>
<foo>bar1</foo>
<foo>bar2</foo>
<foo>bar3</foo>
<foo>bar4</foo>
</foos>
</xsl:variable>
<xsl:template match="/">
<xsl:call-template name="myTemplate">
<xsl:with-param name="aTree" select="$mytree"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="myTemplate">
<xsl:param name="aTree"/>
<newfoos>
<xsl:apply-templates select="msxsl:node-set($aTree)/foos/foo"/>
</newfoos>
</xsl:template>
<xsl:template match="foo">
<newfoo>
<xsl:value-of select="text()"/>
</newfoo>
</xsl:template>
</xsl:stylesheet>
When run, this simply outputs the following result:
<newfoos>
<newfoo>bar1</newfoo>
<newfoo>bar2</newfoo>
<newfoo>bar3</newfoo>
<newfoo>bar4</newfoo>
</newfoos>
Given this example, there is no reason why you can't dynamically create your myTree variable first.