I'm fairly new to XSL, XPATH etc. Some of this code is mine, some are someone else's.
Problem: When the template below gets called with the templates I've outlined further below, all the xsl:text nodes after the if test is output as a string instead of an HTML node, and thus the icon is not rendered.
This question has to do with understanding the why? of what's going on. My exact question is at the bottom of this post.
So, I have a template that I call that generates SVG elements with a <use> element for use with an SVG sprite.
<xsl:template name="svg-link">
<xsl:param name="svg-id"/>
<xsl:param name="svg-class"/>
<xsl:param name="svg-title"/>
<span class="{$svg-class} svgstore svgstore--{$svg-loc}">
<svg>
<xsl:if test="$svg-title != ''">
<title><xsl:value-of select="$svg-title"/></title>
</xsl:if>
<xsl:text disable-output-escaping="yes"><use xlink:href="</xsl:text>
<xsl:value-of select="concat('#', $svg-loc)" />
<xsl:text disable-output-escaping="yes">"></use></xsl:text>
</svg>
</span>
</xsl:template>
All sorts of templates call/apply this template. There is one in particular that I'm having an issue with. We have two snippets implemented by the CMS that output the same markup, but the configurations for the snippets are implemented differently, i.e. Page Template A vs Page Template B. The snippet in question is made of multiple XSL templates. The templates are organized like so:
Snippet Template: entry point for snippet for all callers. Accepts a couple of params related to CSS classes. Creates a few wrapper elements for the snippet. Calls the following template.
"Model" Template: is a template that needs to be defined by each page template. As mentioned above, each page template uses a different approach to implementing configuration options for the snippet. The idea is to make the following template agnostic about how the snippet was configured in the first place because this template is responsible for knowing those details and passing it on to the following template.
Snippet Item Template: renders most of the markup for the snippet based on the info passed to it by the "Model" Template.
Here's some simplified pseudo-code demonstrating above:
<xsl:template name="snippet">
<xsl:param name="outer-classes"/>
<xsl:param name="inner-classes"/>
<xsl:variable name="items">
<xsl:call-template name="snippet-model"/>
</xsl:variable>
<!-- Render Snippet if it has content. -->
<xsl:if test="count( $items )">
<div class="{ $outer-classes }">
<div class="{ $inner-classes }">
<xsl:copy-of select="$items">
</div>
</div>
</xsl:if>
</xsl:template>
<!-- Placeholder. Defined by each page template. -->
<xsl:template name="snippet-model"/>
<xsl:template name="snippet-item">
<xsl:param name="a"/>
<xsl:param name="b"/>
<xsl:param name="b"/>
<div class="snippet-item {$a}">
<xsl:apply-templates select="$b"/>
<xsl:call-template name="svg-link">
<xsl:with-param name="svg-id">alpha</xsl:with-param>
<xsl:with-param name="svg-class">alpha</xsl:with-param>
<xsl:with-param name="svg-title">The Title</xsl:with-param>
</xsl:call-template>
</div>
</xsl:template>
And an example of how a page template uses the above:
<xsl:template match="table[#class = 'snippet-alpha']">
<xsl:call-template="snippet">
<xsl:with-param name="outer-classes">page-template-a other</xsl:with-param>
<xsl:with-param name="inner-classes">some-template-modifier</xsl:with-param>
</xsl:call-template>
</xsl:template>
<!-- Template definition of `snippet-model` template. -->
<xsl:template name="snippet-model">
<!-- Another page template might not use `tbody/tr` to loop over. -->
<xsl:for-each select="tbody/tr">
<xsl:call-template="snippet-item">
<xsl:with-param name="a" select="td[1]"/>
<xsl:with-param name="b" select="td[2]"/>
<xsl:with-param name="c" select="td[3]"/>
</xsl:call-template>
</xsl:for-each>
</xsl:template>
I've narrowed down my issue to likely be the xsl:variable capturing the results of xsl:call-template in the snippet template. And/Or the referencing of that variable later with xsl:copy-of.
What Have I Tried?
Below I have working and non-working solutions, all of which I do not fully grokk why they may or may not work.
Works: Adding xmlns:xlink="http://www.w3.org/1999/xlink" to xsl:stylesheet for the file that contains the svg-link template and then modding svg-link template, see code below this list.
Works: Instead of outputting the value of the xsl:variable that captures the results of xsl:call-template with xsl:copy-of. I replace xsl:copy-of with a second xsl:call-template that is identical to that of the call that was done inside the variable.
Does Not Work: Used xsl:sequence instead of xsl:copy-of.
Does Not Work: Tried data typing(?) the xsl:variable that captures the results of xsl:call-template with the as attribute. I.e. as="node()*".
<xsl:template name="svg-link">
<xsl:param name="svg-id"/>
<xsl:param name="svg-class"/>
<xsl:param name="svg-title"/>
<span class="{$svg-class} svgstore svgstore--{$svg-loc}">
<svg>
<xsl:if test="$svg-title != ''">
<title><xsl:value-of select="$svg-title"/></title>
</xsl:if>
<use xlink:href="{concat( '#', $svg-loc )}"></use>
</svg>
</span>
</xsl:template>
Question: Why are some of the contents of the svg-link template being output as a string (instead of HTML) based on how the result of a call to xsl:call-template is captured/called? As you can see, I have working and non-working solutions - I would like to know why. Thanks!
First of all, disable-output-escaping is an optional serialization feature. Additionally, the XSLT 2 or 3 specs spell out when it doesn't work at all, see https://www.w3.org/TR/xslt-30/#disable-output-escaping
If output escaping is disabled for an xsl:value-of or xsl:text
instruction evaluated when temporary output state is in effect, the
request to disable output escaping is ignored.
and https://www.w3.org/TR/xslt-30/#dt-temporary-output-state
xsl:variable, xsl:param, xsl:with-param, xsl:function, xsl:key,
xsl:sort, xsl:accumulator-rule, and xsl:merge-key always evaluate the
instructions in their contained sequence constructor in temporary
output state
So inside your xsl:variable any disable-output-escaping can't work.
The whole attempt to use it to try to construct an SVG use element is completely unnecessary, you can create any result elements as literal result elements e.g. <use xlink:href="{concat( '#', $svg-loc )}"></use> (with an appropriate XLink namespace declaration in scope for the attribute from that namespace), or, if you need to compute part of the name or namespace, using xsl:element.
Related
In a rather complex xslt file some elements are to be processed twice. This is done by a template with a paramater.
<xsl:template macht="x">
<xsl:param name="modus"/>
<!-- comon things to do for both cases -->
<xsl:choose>
<xsl:when test="$modus='case1'"> <!-- things to do in case 1 --> </xsl:when>
<xsl:when test="$modus='case2'"> <!-- things to do in case 2 --> </xsl:when>
</xsl:choose>
</xsl:template>
The problem is: I cannot simply apply or call this template directly. The element x (for which this template with these two cases is for) is often at a quite low level of the xml input. Almost all ancestor elements have to be processed (in both cases) before it actually comes to x.
The call for the two cases is at the allmost top level.
In html it would be like this
<body>
<h1>Case 1</h1>
<xsl:apply-templates><xsl:with-parameter name="modus" select="case1"/>
<h1>Case 2</h1>
<xsl:apply-templates><xsl:with-parameter name="modus" select="case2"/>
</body>
So. How can I make sure, that the parameter reaches the template for x?
Of course, I could replace all
<xsl:apply-templates/>
calls within the templates for every single ancestor element of x by
<xsl:param name="modus">
<!-- What ever content here -->
<xsl:apply-templates><xsl:with-parameter name="modus" select="$modus"/></apply-templates>
But that would mean a lot of effort. Is there a better way to do this?
XSLT 2.0 has tunnel parameters e.g. with
<xsl:apply-templates>
<xsl:with-param name="modus" tunnel="yes" select="'foo'"/>
</xsl:apply-templates>
and
<xsl:template match="bar">
<xsl:param name="modus" tunnel="yes"/>
...
</xsl:template>
you don't have to pass on the parameter explicitly in the templates for ancestors of bar. So using an XSLT 2.0 processor like Saxon 9 you can do that.
I am very new to XSLT. I was wondering if there is any way to get the name of calling template from within a template.
I currently got the following with a little complex structure. One template is included once directly and once via another template. I need to add a new tag to this template only if it is called from a specific template.
<xsl:element name="parent">
<xsl:choose>
<xsl:when test="$myVariable = 'process1'">
<xsl:call-template name="templateA"/>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="templateB"/>
</xsl:otherwise>
</xsl:choose>
</xsl:element>
<xsl:template name="templateA">
<!-- Some Other Tags Here -->
<xsl:call-template name="templateB />"
</xsl:template>
<xsl:template name="templateb"> <!-- very big template -->
<!-- existing tags here -->
<!-- Add a new tag here only if called via templateA -->
</xsl:template>
To be clear,
As you can see, templateB is included either way, but templateA adds some more tags then includes templateB.
I want to add a new tag to templateB only if it is called from templateA. Is it possible to do?
You could use parameter
<xsl:template name="templateB"> <!-- very big template -->
<xsl:param name="calledFrom" select="" />
<!-- existing tags here -->
<xsl:if test="$calledFrom = 'templateA">
<!-- Add a new tag here only if called via templateA -->
</xsl:if>
</xsl:template>
And then called it in this way
<xsl:call-template name="templateB">
<xsl:with-param name="calledFrom" select="'templateA'" />
</xsl:call-template>
If a function/template needs to know where it was called from, then there's something wrong with the design. Passing a parameter is of course the immediate way to fix the code, but piling on parameters and adding conditional logic based on the parameter values leads to unmaintainable spaghetti.
There's not enough of your code here to assess the design, but I would ask why it's not making more use of template rules rather than named templates. It might well be that judicious use of apply-templates would solve the problem much more naturally.
Passing the parameter is the solution, I was not aware of if they are passed in nested templates.
The solution that proper suits my scenario is tunnel-params.
Parameters are tunneled(passed on ) to template called by default in xslt 2.0, but in xslt 1.0 we need to specify tunnel="yes". With tunelling myVariable can be accessible to the template called.
Is it possible to specify a parameter of a named template as the match pattern in another template?
Here, if I try to call the 'excerpt' template and pass in an XPath as the 'path' param, I get an error:
<xsl:template name="excerpt">
<xsl:param name="path" select="''" />
<xsl:apply-templates select="$path" />
</xsl:template>
<xsl:template match="$path">
<article class="newsarticle">
<h2><xsl:value-of select="title" /></h2>
<xsl:copy-of select="excerpt/node()" />
</article>
</xsl:template>
I can accomplish it with <xsl:for-each>, but I wondered if there was a good solution using something similar to the approach above.
Edit: here's what I'm trying to accomplish, working with a <xsl:for-each>:
<xsl:template name="excerpt">
<xsl:param name="path" select="''" />
<xsl:for-each select="$path">
<article class="newsarticle">
<h2><xsl:value-of select="title" /></h2>
<xsl:copy-of select="excerpt/node()" />
</article>
</xsl:for-each>
</xsl:template>
Edit: an example of calling the template:
<xsl:call-template name="excerpt">
<xsl:with-param name="path" select="path/to/nodeset" />
</xsl:call-template>
Thanks for the additional information. One clarification to make here is that in that call-template there, you are passing a node-set, not a path. String values of paths are pretty much worthless in XSLT 1.0 without convoluted parsing logic or extension functions.
There is a way to do what you're trying to do, just in a slightly different way from what you envisioned. You just need to use a template with a generic match value and a mode value, like this.
<xsl:template name="excerpt">
<xsl:param name="items" select="''" />
<xsl:apply-templates select="$items" mode="excerptItem" />
</xsl:template>
<xsl:template match="node() | #*" mode="excerptItem">
<article class="newsarticle">
<h2>
<a href="{$root}/news/view/{title/#handle}">
<xsl:value-of select="title" />
</a>
</h2>
<xsl:copy-of select="excerpt/node()" />
</article>
</xsl:template>
But if the named template is only serving to invoke the match template, then you don't need the named template at all. You can just use the match template directly:
<xsl:apply-templates select="path/to/nodeset" mode="excerptItem" />
The purpose of the mode attribute is that when you specify mode in an apply-templates, the XSLT will only consider templates that also have that same mode value. So you could define two different templates that handled the same element in different ways:
<xsl:template match="Item" mode="header">
Item in header: <xsl:value-of select="." />
</xsl:template>
<xsl:template match="Item" mode="body">
Item in body: <xsl:value-of select="." />
</xsl:template>
Then you can specify which one you want to use at different times:
<div id="header">
<xsl:apply-templates match="/root/Items/Item" mode="header" />
</div>
<div id="body">
<xsl:apply-templates match="/root/Items/Item" mode="body" />
</div>
and the appropriate one would be used in each case. You can read more about modes here.
node() | #* is a generic XPath that matches any node or attribute, so if you use it in the match attribute of a template, you can make a template that will match almost anything you use in an apply-templates (as long as there isn't another template with higher precedence). Using that in combination with a mode allows you to make a template that you can invoke on any node and only at specific times when you want to. In your example, it looks like the element you will use with this template will always be the same, so it would probably be better practice to explicitly specify it:
<xsl:template match="ExportItem" mode="excerptItem">
Is it possible to get a parameter of a named template to act as the
match path in another template?
No, in XSLT 2.0 the match pattern of a template can only contain a variable reference as an argument to an id() function.
See the XSLT 2.0 W3C Specificification for the complete grammar of a Pattern
http://www.w3.org/TR/xslt20/#pattern-syntax
In XSLT 1.0 it is an error to have a variable reference anywhere within a match pattern.
I am trying to re-use a XSL template, and place other templates within this template, multiple times.
Here's an example of my code:
<xsl:template name="wrapper">
<div>
<xsl:apply-templates/>
</div>
</xsl:template>
<xsl:template name="template1"></xsl:template>
<xsl:template name="template2"></xsl:template>
So, now i want to apply both template 1 and template 2 inside template 'wrapper', something like this (I know this isn't the right code, but the idea is there):
<xsl:template name="template1">
<xsl:template match="wrapper">
<!--code here-->
</xsl:template>
</xsl:template>
<xsl:template name="template2">
<xsl:template match="wrapper">
<!--code here-->
</xsl:template>
</xsl:template>
Any help on this would be grealty appreciated.
It is syntactically illegal to nest a template definition into another.
As per the W3C XSLT (both 1.0 and 2.0) specification, an xsl:template must be a child of the top element xsl:stylesheet.
This means that all templates in a stylesheet module must be siblings.
The way to invoke a named template is to use the xsl:call-template instruction like this:
<xsl:call-template name="someTemplateName">
<!-- Possibly place one or more `xsl:with-param` elements here -->
</xsl:call-template>
However, beaware that:
It is a good style and more in the spirit of XSLT to use unnamed templates (that have a match attribute) and to select the best matching template with an xsl:apply-templates instruction.
Most of the answers to SO XSLT questions demonstrate the use of xsl:apply-templates.
So, now i want to apply both template 1 and template 2 inside template 'wrapper',
If I take this literally:
<xsl:template name="wrapper">
<xsl:call-template name="template1" />
<xsl:call-template name="template2" />
</xsl:template>
But I have a strong gut feeling that you're somehow shooting yourself in the foot here.
Apart from rewriting a lot of XSLT code (which I'm not going to do), is there a way to find the position of an element within its parent, when the context is arbitrarily set to something else? Here's an example:
<!-- Here are my records-->
<xsl:for-each select="/path/to/record">
<xsl:variable name="record" select="."/>
<!-- At this point, I could use position() -->
<!-- Set the context to the current record -->
<xsl:for-each select="$record">
<!-- At this point, position() is meaningless because it's always 1 -->
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
</xsl:for-each>
<!-- This template expects the current context being set to a record -->
<xsl:template name="SomeTemplate">
<!-- it does stuff with the record's fields -->
<xsl:value-of select="SomeRecordField"/>
<!-- How to access the record's position in /path/to or in any other path? -->
</xsl:template>
NOTE: This is a simplified example. I have several constraints keeping me from implementing obvious solutions, such as passing new parameters to SomeTemplate, etc. I can really only modify the internals of SomeTemplate.
NOTE: I'm using Xalan 2.7.1 with EXSLT. So those tricks are available
Any ideas?
You could use
<xsl:value-of select="count(preceding-sibling::record)" />
or even, generically,
<xsl:value-of select="count(preceding-sibling::*[name() = name(current())])" />
Of course this approach will not work if you process a list of nodes that is not uniform, i.e.:
<xsl:apply-templates select="here/foo|/somewhere/else/bar" />
Position information is lost in such a case, unless you store it in a variable and pass that to the called template:
<xsl:variable name="pos" select="position()" />
<xsl:for-each select="$record">
<xsl:call-template name="SomeTemplate">
<xsl:with-param name="pos" select="$pos" />
</xsl:call-template>
</xsl:for-each>
but obviously that would mean some code rewriting, which I realize you want to avoid.
Final hint: position() does not tell you the position of the node within its parent. It tells you the position of the current node relative to the list of nodes you are processing right now.
If you only process (i.e. "apply templates to" or "loop over") nodes within one parent, this happens to be the same thing. If you don't, it's not.
Final hint #2: This
<xsl:for-each select="/path/to/record">
<xsl:variable name="record" select="."/>
<xsl:for-each select="$record">
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
</xsl:for-each>
is is equivalent to this:
<xsl:for-each select="/path/to/record">
<xsl:call-template name="SomeTemplate"/>
</xsl:for-each>
but the latter works without destroying the meaning of position(). Calling a template does not change context, so . will refer to the correct node withing the called template.