Generic way to pass XSL param to templates - xslt

I have several stylesheets and templates, and I'd like to add some behaviour to all of them.
Let's say something like that :
<xsl:template match="*" priority='10'>
<xsl:apply-templates select="." mode="someFunStuffsToDo"/>
<xsl:next-match/>
</xsl:template>
But, as i need some generic templates, i have issues with params because I don't know all the different types of param I could have.
Is there any 'easy' way to say something like that :
<xsl:template match="*" priority='10'>
<xsl:param select="All the params you get"/>
<xsl:apply-templates select="." mode="someFunStuffsToDo">
<xsl:with-param select="All the params you got"/>
</xsl:apply-templates>
<xsl:next-match/>
</xsl:template>
I could imagine some solution based on generic param which would contain nodes of params but I'd need to rewrite most of my actual templates to switch different specifics params declaration for the generic one...
EDIT :
Ok, I think I just find a solution just before posting my question :
Tunnel parameters.
Is that working for my purpose as I understand it, i mean
<xsl:template match="*" priority='10'>
<xsl:apply-templates select="." mode="someFunStuffsToDo"/>
<xsl:next-match/>
</xsl:template>
will work if i just set my parameters before and after with the attribute tunnel='yes' ?
(I haven't tested yet and shame on me, but i assume that next-match will preserve the current mode)

Yes, tunnel parameters help, you however need to make sure your passing code and your receiving templates have the <xsl:with-param name="foo" tunnel="yes" select="bar"/> respectively <xsl:param name="foo" tunnel="yes"/>. But any templates in between, like the one you have, will then not need a with-param.

Related

How best to re-use XSLT code when different groups of documents have different optional tag attributes for the same tags?

Here is the scenario. I have XML documents with tags that look like this:
<para a="A" b="B" c="C">
appearing in different classes of XML documents. The a and b attributes are completely generic and are handled exactly the same way in all documents. The optional c attribute is document class dependent, and will require different transforms, depending on the document class. I would like to write a stylesheet to be included or imported into document class-specific stylesheets which take care of doing the transform for and attributes a and b, which attribute c handled by the parent stylesheet. I can think of at least a couple of ways to do this, but am wondering if there is some canonical best way.
Let's call the stylesheet to be shared st-generic.xsl. Each of the templates in st-generic.xsl would be named:
<xsl:template match="para" name="generic-para">...</xsl:template>
The document class specific stylesheets would then import st-generic.xsl (rather than include, to set precedence), and would include templates that look like this:
<xsl:template match="para">
<xsl:call-template name="generic-para"/>
{other stuff}
<xsl:apply-templates/>
</xsl:template>
This probably works, but seems a bit inelegant. For example, in most cases the generic-para template is all that is needed, so this template will need to similarly include an
<xsl:apply-templates/>
node in the template body. I'm wondering if there is a better way to do this?
Without seeing more of your code and input, I don't see why you should use named templates.
Let's consider two hypothetical elements in your input:
<para a="A" b="B" c="C">paracontent</para>
<div a="A" b="B" c="C">divcontent</div>
Now, let us assume both attribute a and b are generic attributes that can be handled in the same way, no matter what element they occur on. c is processed in more than one way, depending on the parent element.
There is of course a template matching para elements
<xsl:template match="para">
and I don't see why you need a named template to process the attributes of this element. Why not simply apply-templates to all attributes?
<xsl:template match="para">
<xsl:apply-templates select="#*"/>
<!--Do stuff other than processing attributes...-->
</xsl:template>
Then, other templates (not named ones) would match the two generic attributes:
<xsl:template match="#a">
<!--Process attribute a, no matter the parent element-->
</xsl:template>
and
<xsl:template match="#b">
<!--Process attribute b, no matter the parent element-->
</xsl:template>
or perhaps even
<xsl:template match="#a|#b">
<!--Process attributes a or b, no matter the parent element-->
</xsl:template>
whereas you would write separate templates for attribute c:
<xsl:template match="para/#c">
<!--Process attribute c, if para is the parent-->
</xsl:template>
and
<xsl:template match="div/#c">
<!--Process attribute c, if div is the parent-->
</xsl:template>
All of this code is still in separate templates and can be modularized and imported or included ad libitum.
The best I can think of at the moment is:
Include this in your st-generic.xsl file:
<xsl:template match="para">
{ do para processing }
<xsl:apply-templates select="para" mode="custom" />
<xsl:apply-templates />
</xsl:template>
<xsl:template match="para" mode="custom" priority="-5" />
Then when you need custom behavior, you can put this in your main template:
<xsl:template match="para" mode="custom">
{ do custom para processing }
</xsl:template>
this will be invoked between the { do para processing } and the <xsl:apply-templates /> in the generic file, so you can have the custom template focus on custom behavior.
XSL provides a canonical way of solving this problem using the <xsl:apply-imports/> element.
In this particular example, you would have the st-generic.xsl stylesheet import a customizations.xsl stylesheet which would include optional customizations:
st-generic.xsl:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:import href="customizations.xsl"/>
<xsl:template match="para">
<xsl:if test="#a and string(#a)">
{do stuff}
</xsl:if>
<xsl:if test="#b and string(#b)">
{do other stuff}
</xsl:if>
<xsl:apply-imports/>
<xsl:apply-templates/>
</xsl:template>
/xsl:stylesheet>
customizations.xsl:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="para">
<xsl:if test="#c and string(#c)">
{do special stuff}
</xsl:if>
</xsl:template>
/xsl:stylesheet>
A completely flexible solution is provided if you also name the templates in the st-generic.xsl stylesheet, which then allows you to import st-generic.xsl into yet another stylesheet, calling the named templates as needed or using <xsl:apply-imports/> as used in the previous example.
So, my original answer proved not to work because of the unfortunate way that <xsl:apply-imports/> works when there is no matching template. See the answer to this question for details. The bottom line is it resorts to default templates, which ends up messing the output when you use it the way I had it in my original answer.
In pondering what to do about this I came up with an alternative solution I like better anyway, namely use attribute sets defined in an included stylesheet. For example, to handle the case proposed in the original question, you could do something like this:
The main stylesheet would look like this:
<xsl:include href="generic-attributes.xsl"/>
<xsl:template match="para">
<fo:block xsl:use-attribute-sets="para_default-attrs">
<xsl:if test="#a and string(#a)">
{do stuff}
</xsl:if>
<xsl:if test="#b and string(#b)">
{do other stuff}
</xsl:if>
~~ other stuff ~~
<xsl:apply-templates/>
</fo:block>
</xsl:template>
The generic-attributes.xsl file would then contain this:
<xsl:attribute-set name="para_default-attrs">
<xsl:attribute name="a">A</xsl:attribute>
<xsl:attribute name="b">B</xsl:attribute>
</xsl:attribute-set>

choosing to have calls to different templates in xsl

I have the below template that is xsl:apply template
<xsl:apply-templates
select="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference[starts-with(#href, 'PO')]" />
as shown above it is working fine for 'PO' now i want to make it for CPTY too so I have developed it as shown..
<xsl:apply-templates
select="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference[starts-with(#href, 'CPTY')]" />
but the problem is that there can't be two seprate templates with the same name payerPartyReference can you please advise what is the best approach to deal with this ..
what approach i am thinking is ..
<xsl:if test="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference[starts-with(#href, 'PO')]">
</xsl:if>
<xsl:if test="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference[starts-with(#href, 'CPTY')]">
</xsl:if>
You're right that you can't have two templates with exactly the same matching pattern, but you can have
<xsl:template match="fpml:payerPartyReference[starts-with(#href, 'PO')]">
<!-- ... -->
</xsl:template>
<xsl:template match="fpml:payerPartyReference[starts-with(#href, 'CPTY')]">
<!-- ... -->
</xsl:template>
With these separate templates in place you may find you don't need to split out the apply-templates. Depending on the precise details of your problem you might find that you can just do one
<xsl:apply-templates
select="fpml:dataDocument/fpml:trade/fpml:swap/fpml:swapStream/fpml:payerPartyReference" />
and let the template matcher handle the conditional behaviour by picking the appropriate matching template for each target node.

XSLT: apply a template to a tree fragment

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.

passing parameter to parsing of imported file

In parsing a WSDL, I come across many wsdl:import and xsd:import elements. I would like to parse the imports and pass the #location or #schemaLocation to the parser.
The intent is to have the file list grow when an imported file imports a file for example filea.wsdl;filez.xsd;filev.xsd. This way I can eliminate a previously imported file.
I would think something along along these lines:
<xsl:param name="file-list"/>
<xsl:template match="/">
<xsl:param name="file-list"/>
<xsl:apply-templates />
</xsl:template>
<xsl:template match="wsdl:import">
<xsl:apply-templates select="document(#location)">
<xsl:with-param name="file-list" select="concat($file-list, ';', #location)`"/>
</xsl:apply-templates>
</xsl:template>
Your basic idea seems to be fine. You just need to pass the the file-list parameter along when applying templates, so:
add a <xsl:with-param name="file-list" value="$file-list"/> to the xsl:apply-templates in your first template to actually pass the parameter, and
add a <xsl:param name="file-list"/> to your second template to introduce the parameter there.

Can you simulate a boolean flag in XSLT?

I'd like to simulate a flag in an xslt script. The idea is for template foo to set a flag (or a counter variable, or anything), so that it can be accessed from template bar. Bar isn't called from foo, but from a common parent template (otherwise I would pass a parameter to it). The structure is like this:
<xsl:template match="bla">
<xsl:apply-templates select="foo"/> <!-- depending on the contents of foo... -->
<xsl:apply-templates select="bar"/> <!-- ... different things should happen in bar -->
</xsl:template>
Any tricks are much appreciated.
Not really... At least not in the sense that you are trying to do it. Variables in XSLT are immutable, once you assign them a value, you can't change them, so trying to call foo multiple times to change the value of the flag wouldn't work. There are a couple of patterns you could try, which might accomplish what you are trying to do, for example:
<xsl:variable name="myFlag"><xsl:apply-templates select="foo" /></xsl:variable>
<xsl:template match="bla">
<xsl:apply-templates select="bar" /> <!-- Can use the value of $myFlag --.
</xsl:template>
Which will work if template foo is built to return the value of the flag, however, if the value of the flag is meant to change over time, the only real way you can accomplish this is to incorporate the call to foo into the bar template.
<xsl:template match="bla">
<xsl:apply-templates select="bar"> />
</xsl:template>
<xsl:template match="bar">
<xsl:variable name="flag"><xsl:apply-templates name="foo" /></xsl:variable>
<xsl:if test="$flag=1">
</xsl:if>
</xsl:template>
There are plenty ways of doing this. For instance:
You can use conditional structures like xsl:if/xsl:choose.
You can use variables to store whatever is calculated from foo and pass it as parameter to the apply-templates on bar.
The true XSLT way whould be to define different templates for bar - which match different foo cases:
<xsl:template match="bar[../foo[#a='x']]">
...
</xsl:template>
<xsl:template match="bar[../foo[#a='y']]">
...
</xsl:template>
If template foo shall produce output, any solution using the output as flag won't work. In this case, if you are using a Java based XSLT processor (e.g. Saxon or Xalan), you can use mutable Java objects.
But note that this has its own difficulties. The transform given below uses a global flag, which might not suffice for all use cases. I would like to instantiate the flag in the bla template and pass it as parameter to foo and bar, but I could not get that working in Xalan. Also note that I invoke the Java setter in an xsl:value-of, because otherwise the call might get optimized away (see Cannot access updated Java object from Saxon XSLT processor).
<xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:myflag="java:mypackage.MyFlag">
<xsl:variable name="foo-flag" select="myflag:new()" />
<xsl:template match="bla">
<xsl:apply-templates select="foo"/> <!-- depending on the contents of foo... -->
<xsl:apply-templates select="bar"/> <!-- ... different things should happen in bar -->
</xsl:template>
<xsl:template match="foo">
<xsl:choose>
<xsl:when ...>
<xsl:value-of select="myflag:set($foo-flag, true())" />
...
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="myflag:set($foo-flag, false())" />
...
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="bar">
<xsl:choose>
<xsl:when test="myflag:get($foo-flag)">
...
</xsl:when>
<xsl:otherwise>
...
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:transform>
The class MyFlag in its most basic version is merely a mutable boolean wrapper.
public class MyFlag {
private boolean flag;
public void set(boolean flag){
this.flag = flag;
}
public boolean get(){ return flag; }
}
This uses the approach you mentioned in your question: Pass a parameter, from foo to bar. NOTE: This assumes that there is exactly one foo below each bla, or else the bar template will be invoked never or more than once per bar element.
<xsl:template match="bla">
<xsl:apply-templates select="foo" />
</xsl:template>
<xsl:template match="foo">
...
<xsl:apply-templates select="../bar">
<xsl:with-param name="flag" select="..." />
</xsl:apply-templates />
</xsl:template>