I am trying to covert an xml to html using xslt.
Now I got struck with the use of variables in xslt.
Is it possible to pass variables from parent node to some other template other than child node?
I have amy xslt code like this
`
<xsl:template match="xpath"name="a">
<xsl:variable name="object" select="Hello">
<xsl:call-template="b">
<xsl:with-param name="object" select="$object"/>
</xsl:call-template>
</xsl:variable>
</xsl:template>
<xsl:template match="xpath" name="b">
<xsl:param name="object"/>
</xsl:template>
`
I am getting an following error
unexpected xslt element 'param'
Help me to resolve this issue.
There are multiple issues with the code that has been shared.
XSL template cannot have both match and name attributes specified. You can either have a match attribute or name attribute. The match attribute can go with the mode attribute if you are looking to match with same element from input XML.
XSL variable object has been declared and a value of Hello assigned to it. However you are also calling a template b from inside the <xsl:variable> which is not correct. XSL variables cannot have select and content both.
You may want to modify the code as below if you are looking for passing a parameter from one template to another.
<!-- template matching with input XML -->
<xsl:template match="xpath">
<!-- declare variable "object" and assign value as "Hello" -->
<xsl:variable name="object" select="'Hello'" />
<!-- call template "b" and pass the value of variable "object" -->
<xsl:call-template name="b">
<xsl:with-param name="object" select="$object" />
</xsl:call-template>
</xsl:template>
<!-- create template "b" using #name attribute -->
<xsl:template name="b">
<!-- declare parameter -->
<xsl:param name="object" />
<!-- print the value of parameter -->
<xsl:value-of select="$object" />
</xsl:template>
Related
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.
I try to create a variable, which I can use in a later template:
<xsl:variable name="fc">
<xsl:choose>
<xsl:when test="self::node()='element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
Unfortunately it does not work.
<xsl:template match="element1">
<h1><font color="{$fc}"><xsl:value-of select="self::node()"/></font></h1>
</xsl:template>
What am I doing wrong?
Here is the extensive code:
XML:
<root
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.test.com scheme.xsd" xmlns="http://www.test.com" xmlns:tst="http://www.test.com">
<elementA>
<elementB tst:name="name">
<elementC tst:name="name">
<element1> Test1 </element1>
<element2> Test2 </element2>
</elementC >
</elementB>
</elementA>
</root>
All the elements are qualified and part of the namespace "http://www.test.com".
XSLT:
<xsl:template match="/">
<html>
<body><xsl:apply-templates select="tst:root/tst:elementA/tst:elementB/tst:elementC/tst:element1"/>
</body>
</html>
</xsl:template>
<xsl:variable name="var_fc">
<xsl:choose>
<xsl:when test="local-name(.)='tst:element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:template match="tst:element1">
<h2><font color="{$var_fc}"><xsl:value-of select="self::node()"/></font></h2>
</xsl:template>
As a result, element1 should turn gray, but it always turn red.
You can't use a variable for this, as the content of an xsl:variable is evaluated just once at definition time, whereas you want to evaluate some logic every time the variable is referenced, in the current context at the point of reference.
Instead you need a template, either a named one:
<xsl:template name="fc">
<xsl:choose>
<xsl:when test="local-name()='element1'">gray</xsl:when>
<xsl:otherwise>red</xsl:otherwise>
</xsl:choose>
</xsl:template>
or (better) a pair of matching templates with a mode, to let the template matcher do the work:
<!-- match any node whose local name is "element1" -->
<xsl:template mode="fc" match="node()[local-name() = 'element1']">gray</xsl:template>
<!-- match any other node -->
<xsl:template mode="fc" match="node()">red</xsl:template>
When you want to use this logic:
<h1>
<font>
<xsl:attribute name="color">
<xsl:apply-templates select="." mode="fc" />
</xsl:attribute>
Seeing as you have the tst prefix mapped in your stylesheet you could check the name directly instead of using the local-name() predicate:
<xsl:template mode="fc" match="tst:element1">gray</xsl:template>
<xsl:template mode="fc" match="node()">red</xsl:template>
XSLT variables are designed not to be changeable. Actually they could be named constants. If your variable fc is created global, it will use the root element for choose. You have to use choose in the actual template to be tested against the current element. If you want to have "red" and "gray" defined only once, create two variables with just that text content and use these instead the plain text in the choose.
Maybe it is a typo:
<xsl:when test=self::node()='element1'">gray</xsl:when>
should be:
<xsl:when test="self::node()='element1'">gray</xsl:when>
there is a missing quote.
I think instead of test="self::node()='element1'" you want test="self::element1" or test="local-name(.) = 'element1'".
A couple of other errors in your code:
(1) self::node() = 'element1'
tests whether the content of the element is "element1", not whether its name is "element1"
(2) local-name(.)='tst:element1'
will never be true because the local name of a node never contains a colon.
Experienced users would often write this code using template rules:
<xsl:template mode="var_fc" match="tst:element1">gray</xsl:template>
<xsl:template mode="var_fc" match="*">red</xsl:template>
and then
<xsl:apply-templates select="." mode="var_fc"/>
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'm having trouble passing a parameter to a template.
<!-- // Product / Instances -->
<xsl:template match="/data/products/instances">
<ul>
<xsl:apply-templates select="item">
<xsl:with-param name="idp" select="#id"/>
</xsl:apply-templates>
</ul>
</xsl:template>
<!-- // Product / Instances / Instance -->
<xsl:template match="/data/products/instances/item">
<xsl:param name="idp"/>
<p>$idp: <xsl:value-of select="$idp"/></p> <!-- $idp is empty -->
<xsl:for-each select="/data/instances/entry">
<xsl:if test="#id = $idp">
<p><xsl:value-of select="code"/></p>
</xsl:if>
</xsl:for-each>
</xsl:template>
/data/products/instances/item has an attribute named id, which has a value of an integer.
Although the second template and its for-each loop are being processed (I've tested them by outputting dummy output from within them), the value of the $idp parameter is not being passed to the second template.
Thanks.
The problem is that when you do the apply-templates, your current context is on the instances element, and so the attribute #id refers to the attribute id of the instances element, and not the attribute on the item elements you are going to select (which have not yet been selected at that point).
In this sample given, there is no actually need to pass in a parameter. Simply use a variable in the matching template instead. Insteaf of the xsl:param, do the following:
<xsl:variable name="idp" select="#id"/>
This will get the value of the id attribute for you, as you are positioned on the item element at that point.
You would need to show enough details to allow us to reproduce the issue, otherwise it is hard to tell what goes wrong.
I think you don't need any parameter, and you should use a key
<xsl:key name="k1" match="data/instances/entry" use="#id"/>
<!-- // Product / Instances -->
<xsl:template match="/data/products/instances">
<ul>
<xsl:apply-templates select="item"/>
</ul>
</xsl:template>
<!-- // Product / Instances / Instance -->
<xsl:template match="/data/products/instances/item">
<xsl:for-each select="key('k1', #id)">
<p><xsl:value-of select="code"/></p>
</xsl:for-each>
</xsl:template>
Is it possible to set a variable name for the match attribute value in xslt template tag.
Any help?
I'm not entirely sure about what you want to achieve, but here is an example:
<!-- a variable -->
<xsl:variable name="x" select="//some/path"/>
<!-- a template to match all elements with the name of the variable -->
<xsl:template match="*[name(.)=$x]">
Yes! <xsl:value-of select="."/>
</xsl:template>