In XSLT 1.0, what is the shortest/cleanest/recommended way to pass the current context node to a called template and have that node become the context node inside the called template?
It would be nice (it would, right?) if a template with no xsl:param and called by an empty call-template would simply pick up the caller's context node, but the best I can think of is this:
<xsl:call-template name="sub">
<xsl:with-param name="context" select="." />
</xsl:call-template>
with
<xsl:template name="sub">
<xsl:param name="context" />
<xsl:for-each select="$context">
</xsl:for-each>
</xsl:template>
It would be nice (it would, right?) if a template with no xsl:param
and called by an empty call-template would simply pick up the
caller's context node.
This is exactly how xsl:call-template is defined in the W3C XSLT 1.0 (and 2.0) specification, and implemented by any compliant XSLT processor.
Here is a small example:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="a">
<xsl:call-template name="currentName"/>
</xsl:template>
<xsl:template name="currentName">
Name: <xsl:value-of select="name(.)"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the following XML document:
<t>
<a/>
</t>
the wanted, correct result is produced:
Name: a
Just otherway of explaining what Dimitre said.
When you call a template from a node, you are already there in that node,
example:
assume this code:
<xsl:template match="MyElement">
<xsl:call-template name="XYZ"/>
</xsl:template>
<xsl:template name="XYZ>
<xsl:value-of select="."/>
</xsl>
The above code is as good as writing:
<xsl:template match="MyElement">
<xsl:value-of select="."/>
</xsl:template>
You can use for-each loop in the called template as well. :)
But just be sure where you exactly are ..
Related
So I'm trying to create a variable and then later on use this in my template.
This is the code I have now:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="items">
<xsl:call-template name="item" />
</xsl:template>
<xsl:template name="item">
<xsl:for-each select="item">
<xsl:variable name="currentItemTheme">
test variable
</xsl:variable>
<xsl:if test="title = name">
Showvariable: {$currentItemTheme} <br/>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
I have no idea why it doesn't work. My file does output the "Showvariable: ", but it just doesn't show the test values. So the for-each loops and template aren't the problem.
Is there something I'm missing? Does someone know how I get this to work?
I think you want <xsl:value-of select="$currentItemTheme"/> instead of {$currentItemTheme}, unless you are mixing the XSLT code with some other language that provides the {$currentItemTheme} syntax.
<xsl:copy-of select="$currentItemTheme"/> is another option if you build nodes in your variable and want to output them instead of just the string value as a text node.
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>
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 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?
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.