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¶m2=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¶m2=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
Related
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>
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>
I have the following params defined in my xslt file:
<xsl:param name="language">E</xsl:param>
<xsl:param name="headerTitle-E">English Title</xsl:param>
<xsl:param name="headerTitle-F">French Title</xsl:param>
How do I display the appropriate header based on the language param?
This doesn't work:
<xsl:value-of select="concat('headerTitle','-',$language)" />
It outputs "headerTitle-E" as opposed to "English Title" (which is what I want).
I'm trying to find a clean solution for displaying the appropriate text based on the language parameter, without having to use a "choose" block for every bit of text.
Any ideas?
If you now where your parameters are, you can use a single XPath. For instance, try this:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:param name="language">F</xsl:param>
<xsl:param name="headerTitle-E">English Title</xsl:param>
<xsl:param name="headerTitle-F">French Title</xsl:param>
<xsl:template match="/">
<xsl:value-of select="document('')/*/
xsl:param[#name=concat('headerTitle-',$language)]"/>
</xsl:template>
</xsl:stylesheet>
However I think that this kind of task should be better accomplished making use of lookup tables than parameters.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:empo="lookup"
exclude-result-prefixes="empo"
version="1.0">
<xsl:param name="language">F</xsl:param>
<empo:header name="headerTitle-E">English Title</empo:header>
<empo:header name="headerTitle-F">French Title</empo:header>
<xsl:template match="/">
<xsl:value-of select="document('')/*/
empo:header[#name=concat('headerTitle-',$language)]"/>
</xsl:template>
</xsl:stylesheet>
You might also want use the current header as a variable, just use:
<xsl:variable name="Header" select="document('')/*/
empo:header[#name=concat('headerTitle-',$language)]"/>
You can use the full broadth of XSLT inside xsl:param and xsl:variable. So do it like this:
<xsl:variable name="headerTitle">
<xsl:choose>
<xsl:when test="$language = 'fr'">
French
</xsl:when>
<xsl:otherwise>
English
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select="$headerTitle" />
Actually, the choose block is the clean solution, compared to creating dozens of unneeded variables.
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.
I'm pulling what's left of my hair out trying to get a simple external lookup working using Saxon 9.1.0.7.
I have a simple source file dummy.xml:
<something>
<monkey>
<genrecode>AAA</genrecode>
</monkey>
<monkey>
<genrecode>BBB</genrecode>
</monkey>
<monkey>
<genrecode>ZZZ</genrecode>
</monkey>
<monkey>
<genrecode>ZER</genrecode>
</monkey>
</something>
Then the lookup file is GenreSet_124.xml:
<GetGenreMappingObjectsResponse>
<tuple>
<old>
<GenreMapping DepartmentCode="AAA"
DepartmentName="AND - NEWS AND CURRENT AFFAIRS"
Genre="10 - NEWS"/>
</old>
</tuple>
<tuple>
<old>
<GenreMapping DepartmentCode="BBB"
DepartmentName="AND - NEWS AND CURRENT AFFAIRS"
Genre="11 - NEWS"/>
</old>
</tuple>
... lots more
</GetGenreMappingObjectsResponse>
What I'm trying to achieve is simply to get hold of the "Genre" value based on the "DepartmentCode" value.
So my XSL looks like:
...
<!-- Set up the genre lookup key -->
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:variable name="lookupDoc" select="document('GenreSet_124.xml')"/>
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc">
<xsl:with-param name="curr-label" select="genrecode"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template>
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup', $curr-genrecode)/#Genre"/>
</xsl:template>
...
The issue that I have is that I get nothing back. I currently just get
<?xml version="1.0" encoding="UTF-8"?>
<stuff>
<Genre/>
<Genre/>
<Genre/>
<Genre/>
</stuff>
I have moved all the lookup data to be attributes of GenreMapping, previously as child elements of GenreMapping whenever I entered the template match="GetGenreMappingObjectsResponse" it would just print out all text from every GenreMapping (DepartmentCode, DepartmentName, Genre)!
I can't for the life of me figure out what I am doing wrong. Any helpo/suggestions would be greatly appreciated.
PLease find the current actual XSLT listing:
<?xml version="1.0"?>
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Define the global parameters -->
<xsl:param name="TransformationID"/>
<xsl:param name="TransformationType"/>
<!-- Specify that XML is the desired output type -->
<xsl:output method="xml" encoding="UTF-8" indent="yes"/>
<!-- Set up the genre matching capability -->
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:variable name="documentPath"><xsl:value-of select="concat('GenreSet_',$TransformationID,'.xml')"/></xsl:variable>
<xsl:variable name="lookupDoc" select="document($documentPath)"/>
<!-- Start the first match on the Root level -->
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template >
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup', $curr-genrecode, $lookupDoc)/#Genre"/>
</xsl:template>
</xsl:stylesheet>
The TransformationID is alway 124 (so the correct lookup file is opened. The Type is just a name that I am currently not using but intending to.
In XSLT 2.0 there are two ways you can do what you want:
One is the three-parameter version of the key function. The third parameter lets you specify the root node you want the key to work on (by default it's always the root of the main document):
<xsl:value-of select="key('genre-lookup', $curr-genrecode,$lookupDoc)/#Genre"/>
Another way is to use the key function under the $lookupDoc node:
<xsl:value-of select="$lookupDoc/key('genre-lookup', $curr-genrecode)/#Genre"/>
Both of these methods are documented in the XSLT 2.0 specification on keys, and won't work in XSLT 1.0.
For the sake of completeness, you'd have to rewrite this to not use keys if you're restricted to XSLT 1.0.
<xsl:value-of select="$lookupDoc//GenreMapping[#DepartmentCode = $curr-genrecode]/#Genre"/>
Aha! The problem is the select="$lookupDoc" in your apply-templates call is calling a default template rather than the one you expect, so the parameter is getting lost.
Change it to this:
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
That will call your template properly and the key should work.
So the final XSLT sheet should look something like this:
<xsl:variable name="lookupDoc" select="document('XMLFile2.xml')"/>
<xsl:key name="genre-lookup" match="GenreMapping" use="#DepartmentCode"/>
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<xsl:apply-templates select="$lookupDoc/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template>
<xsl:template match="GetGenreMappingObjectsResponse">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select="key('genre-lookup',$curr-genrecode,$lookupDoc)/#Genre"/>
</xsl:template>
OK, so this is a bit mental and I don't claim to understand it but it works (sounds like a career in software).
The issue I was having is that when I call the apply-templates and pass in the external document as a variable it never matched any templates even ones called "Genremapping".
So I used a wildcard to catch it, also when calling the apply-templates I narrowed down the node set to be the child I was interested in. This was pretty bonkers a I could print out the name of the node and see "GenreMapping" yet it never went into any template I had called "GenreMapping" choosing instead to only ever go to "*" template.
What this means is that my new template match gets called for every single GenreMapping node there is so it may be a little inefficient. What I realised then was all I needed to do was print sometihng out if a predicate matched.
So it looks like this now (no key used whatsoever):
...
<xsl:template match="/something">
<stuff>
<xsl:for-each select="monkey">
<Genre>
<key_value><xsl:value-of select="genrecode"/></key_value>
<xsl:variable name="key_val"><xsl:value-of select="genrecode"/></xsl:variable>
<code>
<xsl:apply-templates select="$lookupDoc/*/*/*/*">
<xsl:with-param name="curr-genrecode" select="string(genrecode)"/>
</xsl:apply-templates>
</code>
</Genre>
</xsl:for-each>
</stuff>
</xsl:template >
<xsl:template match="*">
<xsl:param name="curr-genrecode"/>
<xsl:value-of select=".[#DepartmentCode = $curr-genrecode]/#Genre"/>
</xsl:template>
...
All of which output:
Note, the last key_value correctly doesn't have a code entry as there is no match in the lookup document.
<?xml version="1.0" encoding="UTF-8"?>
<stuff xmlns:xs="http://www.w3.org/2001/XMLSchema">
<Genre>
<key_value>AAA</key_value>
<code>10 - NEWS</code>
</Genre>
<Genre>
<key_value>AAA</key_value>
<code>10 - NEWS</code>
</Genre>
<Genre>
<key_value>BBB</key_value>
<code>11 - NEWS</code>
</Genre>
<Genre>
<key_value>SVVS</key_value>
<code/>
</Genre>
</stuff>
Answer on a postcode. Thanks for the help Welbog.