XSLT: apply a template to a tree fragment - xslt

I have defined a variable $NodeVariable,
for instance:
<xsl:variable name="NodeVariable">
<aT>
<aT2>foo</aT2>
<aT3>bar</aT3>
</aT>
</xsl:variable>
and in different parts of the code I want to "apply"
different templates to myVariable.
Unfortunately,
I don't know what's the syntax for this.
I've tried the following:
<xsl:for-each select="$NodeVariable">
<xsl:call-template name="ns:ExtractInfo1"/>
</xsl:for-each>
<xsl:copy-of select="$NodeVariable">
<xsl:call-template name="ns:ExtractInfo2"/>
</xsl:for-each>
<xsl:copy-of select="$NodeVariable">
<xsl:call-template name="ns:ExtractInfo3"/>
</xsl:for-each>
which doesn't work.
How to apply a template to a tree fragment?

Assuming you use an XSLT 1.0 processor, you need to convert the result tree fragment to a node set first:
<xsl:variable name="NodeVariable">
<aT>
<aT2>foo</aT2>
<aT3>bar</aT3>
</aT>
</xsl:variable>
<xsl:variable name="NodeSet" select="exsl:node-set($NodeVariable)"/>
(where the stylesheet declares xmlns:exsl="http://exslt.org/common"), then you can apply-templates in different modes as needed e.g.
<xsl:apply-templates select="$NodeSet/aT" mode="m1"/>
and write templates for that mode e.g.
<xsl:template match="aT" mode="m1">
<xsl:value-of select="aT2"/>
</xsl:template>
Of course if you really want to call named templates you could do that as well, but using apply-templates and modes for different processing steps is the preferred way in XSLT in my view.

Related

My XSLT Variable doesn't load in template

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.

XSLT passing for each result to another template

I would like to pass a for each loop as a parameter to another template
<xsl:template name="dataTableGroup">
<xsl:call-template name="test">
<xsl:with-param name="pContent1">
<xsl:for-each
select="NewDataSet/Table[generate-id() = generate-id(key('countryKey', concat(Unit, ReportingBusUnitDesc)))]">
<tr>
<td class="columnTextRight">
<xsl:value-of select="ReportingBusUnitDesc"/>
</td>
</tr>
</xsl:for-each>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
and use it here
<xsl:template name="test">
<xsl:param name="pContent1"/>
<xsl:for-each select="$pContent1">
</xsl:for-each>
</xsl:template>
but instead of nodes I get only values. Idealy I would like to have it work with xslt 1.0.
If you use an XSLT 1.0 processor then the parameter value is a result tree fragment so you need to use a processor specific extension function like exsl:node-set (http://exslt.org/exsl/functions/node-set/index.html) first to convert it into a node set e.g.
<xsl:template name="test">
<xsl:param name="pContent1"/>
<xsl:for-each select="exsl:node-set($pContent1)/tr" xmlns:exsl="http://exslt.org/common">
</xsl:for-each>
</xsl:template>
Node that doing a copy of a result tree fragment is possible without using any extension e.g. <xsl:copy-of select="$pContent1"/> should do.
Also for the completeness of the example I have declared the namespace for the extension function on the xsl:for-each element, normally you would put it on the stylesheet's root element xsl:stylesheet/transform and additionally use exclude-result-prefixes="exsl" to ensure the namespace doesn't occur on any result elements.
With an XSLT 2 or 3 processor you shouldn't have any problems to use the variable directly as a fragment node containing your tr elements.

Split large xslt into smaller ones

I have a large xslt file that is giving problems during deployment
com.sun.org.apache.bcel.internal.generic.ClassGenException: Branch target offset too large for short
at com.sun.org.apache.bcel.internal.generic.BranchInstruction.dump(BranchInstruction.java:99)
at com.sun.org.apache.bcel.internal.generic.InstructionList.getByteCode(InstructionList.java:980)
at com.sun.org.apache.bcel.internal.generic.MethodGen.getMethod(MethodGen.java:616)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Mode.compileNamedTemplate(Mode.java:556)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Mode.compileTemplates(Mode.java:566)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Mode.compileApplyTemplates(Mode.java:818)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Stylesheet.compileModes(Stylesheet.java:615)
at com.sun.org.apache.xalan.internal.xsltc.compiler.Stylesheet.translate(Stylesheet.java:730)
at com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC.compile(XSLTC.java:370)
at com.sun.org.apache.xalan.internal.xsltc.compiler.XSLTC.compile(XSLTC.java:445)
For this, I need to split this large xslt into smaller ones.
I've seen xsl:include tag, but seems like this works for seperate templates.
In my case, its a single parent tag with multiple assignments like this
<xsl:template match="/">
<ns5:taskListResponse>
<xsl:for-each select="/tns:taskListResponse/task:task">
<ns7:task>
<xsl:if test="task:title">
<ns7:title>
<xsl:value-of select="task:title"/>
</ns7:title>
</xsl:if>
<xsl:if test="task:taskDefinitionURI">
<ns7:taskDefinitionURI>
<xsl:value-of select="task:taskDefinitionURI"/>
</ns7:taskDefinitionURI>
</xsl:if>
<xsl:if test="task:creator">
<ns7:creator>
<xsl:value-of select="task:creator"/>
</ns7:creator>
</xsl:if>
........100 more tags like this.....
...................
</xsl:for-each>
</ns5:taskListResponse>
How can I split this xsl?
I want to put some tags in another file and include those inside the
Appreciate your help
Regards
Ravi
I would consider splitting this up into separate templates, for example each of the if tests could be replaced by apply-templates, and the following template to do the work:
<xsl:template match="task:*">
<xsl:element name="ns7:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
If you don't need to re-order the children then the entire stylesheet boils down to
<xsl:template match="/">
<ns5:taskListResponse>
<xsl:apply-templates select="/tns:taskListResponse/task:task" />
</ns5:taskListResponse>
</xsl:template>
<xsl:template match="task:task">
<ns7:task><xsl:apply-templates select="*" /></ns7:task>
</xsl:template>
<xsl:template match="task:*">
<xsl:element name="ns7:{local-name()}">
<xsl:value-of select="." />
</xsl:element>
</xsl:template>
It gets slightly more complex if you do need to re-order things, then you'll need 100 separate <xsl:apply-templates select="task:foo" /> in place of the <xsl:apply-templates select="*" />, but it's still smaller and more modular.

Calling the same xsl:template for different node names of the same complex type

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?

Using xsl:variable in a xsl:foreach select statement

I'm trying to iterate through an xml document using xsl:foreach but I need the select=" " to be dynamic so I'm using a variable as the source. Here's what I've tried:
...
<xsl:template name="SetDataPath">
<xsl:param name="Type" />
<xsl:variable name="Path_1">/Rating/Path1/*</xsl:variable>
<xsl:variable name="Path_2">/Rating/Path2/*</xsl:variable>
<xsl:if test="$Type='1'">
<xsl:value-of select="$Path_1"/>
</xsl:if>
<xsl:if test="$Type='2'">
<xsl:value-of select="$Path_2"/>
</xsl:if>
<xsl:template>
...
<!-- Set Data Path according to Type -->
<xsl:variable name="DataPath">
<xsl:call-template name="SetDataPath">
<xsl:with-param name="Type" select="/Rating/Type" />
</xsl:call-template>
</xsl:variable>
...
<xsl:for-each select="$DataPath">
...
The foreach threw an error stating: "XslTransformException - To use a result tree fragment in a path expression, first convert it to a node-set using the msxsl:node-set() function."
When I use the msxsl:node-set() function though, my results are blank.
I'm aware that I'm setting $DataPath to a string, but shouldn't the node-set() function be creating a node set from it? Am I missing something? When I don't use a variable:
<xsl:for-each select="/Rating/Path1/*">
I get the proper results.
Here's the XML data file I'm using:
<Rating>
<Type>1</Type>
<Path1>
<sarah>
<dob>1-3-86</dob>
<user>Sarah</user>
</sarah>
<joe>
<dob>11-12-85</dob>
<user>Joe</user>
</joe>
</Path1>
<Path2>
<jeff>
<dob>11-3-84</dob>
<user>Jeff</user>
</jeff>
<shawn>
<dob>3-5-81</dob>
<user>Shawn</user>
</shawn>
</Path2>
</Rating>
My question is simple, how do you run a foreach on 2 different paths?
Try this:
<xsl:for-each select="/Rating[Type='1']/Path1/*
|
/Rating[Type='2']/Path2/*">
Standard XSLT 1.0 does not support dynamic evaluation of xpaths. However, you can achieve your desired result by restructuring your solution to invoke a named template, passing the node set you want to process as a parameter:
<xsl:variable name="Type" select="/Rating/Type"/>
<xsl:choose>
<xsl:when test="$Type='1'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path1/*"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="$Type='2'">
<xsl:call-template name="DoStuff">
<xsl:with-param name="Input" select="/Rating/Path2/*"/>
</xsl:call-template>
</xsl:when>
</xsl:choose>
...
<xsl:template name="DoStuff">
<xsl:param name="Input"/>
<xsl:for-each select="$Input">
<!-- Do stuff with input -->
</xsl:for-each>
</xsl:template>
The node-set() function you mention can convert result tree fragments into node-sets, that's correct. But: Your XSLT does not produce a result tree fragment.
Your template SetDataPath produces a string, which is then stored into your variable $DataPath. When you do <xsl:for-each select="$DataPath">, the XSLT processor chokes on the fact that DataPath does not contain a node-set, but a string.
Your entire stylesheet seems to be revolve around the idea of dynamically selecting/evaluating XPath expressions. Drop that thought, it is neither possible nor necessary.
Show your XML input and specify the transformation your want to do and I can try to show you a way to do it.