How should I pass a parameter from one XSLT template to another? - xslt

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>

Related

XSLT - How to refer to a current node value using xsl:choose?

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"/>

XSL pass attributes to child elements if child elements do not already have the same attribute

How can I pass attributes to child elements only if the child elements do not already have the same attribute?
XML:
<section>
<container attribute1="container1" attribute2="container2">
<p attribute1="test3"/>
<ol attribute2="test4"/>
<container>
<section/>
Output should look like this:
<section>
<p attribute1="test3" attribute2="test2"/>
<ol attribute1="container1" attribute2="test4"/>
</section>
This is what i tried:
<xsl:template match="container">
<xsl:apply-templates mode="passAttributeToChild"/>
</xsl:template>
<xsl:template match="*" mode="passAttributeToChildren">
<xsl:element name="{name()}">
<xsl:for-each select="#*">
<xsl:choose>
<xsl:when test="name() = name(../#*)"/>
<xsl:otherwise>
<xsl:copy-of select="."/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<xsl:apply-templates select="*|text()"/>
</xsl:element>
</xsl:template>
Any help would be greatly appreciated ;) Thank you in advance!
Attributes declared more than once overwrite each other, so this is easy.
<xsl:template match="container/*">
<xsl:copy>
<xsl:copy-of select="../#*" /> <!-- take default from parent -->
<xsl:copy-of select="#*" /> <!-- overwrite if applicable -->
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
This assumes you want all parent attributes, as your sample seems to indicate. Of course you can decide which attributes you want to inherit:
<xsl:copy-of select="../#attribute1 | ../#attribute2" />
<xsl:copy-of select="#attribute1 | #attribute2">
Try this.
<!-- root and static content - container -->
<xsl:template match="/">
<section>
<xsl:apply-templates select='section/container/*' />
</section>
</xsl:template>
<!-- iteration content - child nodes -->
<xsl:template match='*'>
<xsl:element name='{name()}'>
<xsl:apply-templates select='#*|parent::*/#*' />
</xsl:element>
</xsl:template>
<!-- iteration content - attributes -->
<xsl:template match='#*'>
<xsl:attribute name='{name()}'><xsl:value-of select='.' /></xsl:attribute>
</xsl:template>
On outputting each child node, we iteratively transfer across its attributes and those of the parent.
<xsl:apply-templates select='#*|parent::*/#*' />
Templates are applied to nodes in the order they appear in the XML. So the parent (container) node appears before the child nodes (of course), so it's the parent's attributes that are handled first by the attributes template.
This is handy because it means the template will always show preference to the child nodes' own attributes if they already exist, because they are handled last and thus take precedence over any attributes with the same name from the parent. Thus, the parent cannot overrule them.
Working demo at this XMLPlayground.

How to mimic calling template-match with a parameter value?

I'm looking for a workaround on passing parameters to a template-match. I'm aware this isn't allowed within XPath, and therefore I'm looking for a 'plan B' solution.
This is what I wished would work :
Part 1 of xslt (2.0) :
<xsl:template match="/">
<xsl:for-each select="//Main/PageList/Page">
<xsl:result-document href="{#ID}.xml">
<Page ID="{#ID}">
<xsl:apply-templates select="node()">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
</Page>
</xsl:result-document>
</xsl:for-each>
</xsl:template>
So fairly straightforward, don't bother about the tags used, what it does in essence is go through a node, and for each node it creates a single XML file. Each node starts with an ID, and it's this ID I'd like to make available for other templates. Unfortunately this works fine for named templates, but it doesn't work for matched ones (if I understood the theory correctly at least)
So below is what I'd like to see working :
<!-- identity template -->
<xsl:template match="node()|#*">
<xsl:param name="theID"/>
<xsl:copy>
<xsl:apply-templates select="node()|#*">
<xsl:with-param name="theID" select="$theID"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<!-- lots of templates here doing what I want them to do -->
<!-- one template creating an issue -->
<xsl:template match="#src">
<!-- would be nice to know the current ID, but unfortunately this one stays empty... -->
<xsl:param name="theID"/>
<!-- clean current attribute a bit -->
<xsl:variable name="S1" select="replace(.,'\.\.\/','')"/>
<xsl:attribute name="src">
<xsl:choose>
<xsl:when test="contains($S1,'common')">
<!-- just use current value, don't bother about current ID -->
<xsl:value-of select="$S1"/>
</xsl:when>
<xsl:otherwise>
<!-- use ID parameter -->
<xsl:value-of select="concat($theID,'_',$S1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
</xsl:template>
Don't look at the code as such, it's just a smaller part of the whole file, but the essence is that I want to use the ID parameter from my first part (match="/") inside the other template (match="#src"), but this seems to be rather complex.
Am I missing something ? If I'm just having bad luck and it's not possible indeed, would anyone have an advice how I could proceed ?
Thanks in advance !
Your problem is here:
<xsl:apply-templates select="node()">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
the select attribute specifies that the processing should continue on any children nodes of the current node.
However node() only selects children nodes (elements, text nodes, processing instructions and comments) -- not attributes.
Solution:
In order to directly cause processing of (all) attributes of the current node, use:
--
<xsl:apply-templates select="#*">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
.2. In order to cause the processing only of the src attribute of the current node use:
--
<xsl:apply-templates select="#src">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
.3. In order to process indirectly attributes tof the descendants of the current node, do:
--
<xsl:apply-templates select="node()|#*">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
You also must ensure that any template that processes node() must have an xsl:param named theID and that it must pass this param in any xsl:apply-templates instruction.
In practice this means that you mus override all XSLT built-in templates, because they aren't aware of your xsl:param.
I think instead of
<xsl:apply-templates select="node()">
<xsl:with-param name="theID" select="#ID"/>
</xsl:apply-templates>
you rather want
<xsl:apply-templates select="node()">
<xsl:with-param name="theID" select="parent::Page/#ID"/>
</xsl:apply-templates>
assuming you want to pass on the ID attribute of the parent Page element.
On the other hand the select="node()" select any child nodes like child elements, child comment nodes, child text nodes, child processing instruction nodes, so I don't see why you later show a template matching an attribute node.

Find the position of an element within its parent with XSLT / XPath

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.

How do I check if a tag exists in XSLT?

I have the following template
<h2>one</h2>
<xsl:apply-templates select="one"/>
<h2>two</h2>
<xsl:apply-templates select="two"/>
<h2>three</h2>
<xsl:apply-templates select="three"/>
I would like to only display the headers (one,two,three) if there is at least one member of the corresponding template. How do I check for this?
<xsl:if test="one">
<h2>one</h2>
<xsl:apply-templates select="one"/>
</xsl:if>
<!-- etc -->
Alternatively, you could create a named template,
<xsl:template name="WriteWithHeader">
<xsl:param name="header"/>
<xsl:param name="data"/>
<xsl:if test="$data">
<h2><xsl:value-of select="$header"/></h2>
<xsl:apply-templates select="$data"/>
</xsl:if>
</xsl:template>
and then call as:
<xsl:call-template name="WriteWithHeader">
<xsl:with-param name="header" select="'one'"/>
<xsl:with-param name="data" select="one"/>
</xsl:call-template>
But to be honest, that looks like more work to me... only useful if drawing a header is complex... for a simple <h2>...</h2> I'd be tempted to leave it inline.
If the header title is always the node name, you could simplifiy the template by removing the "$header" arg, and use instead:
<xsl:value-of select="name($header[1])"/>
I like to exercise the functional aspects of XSL which lead me to the following implementation:
<?xml version="1.0" encoding="UTF-8"?>
<!-- test data inlined -->
<test>
<one>Content 1</one>
<two>Content 2</two>
<three>Content 3</three>
<four/>
<special>I'm special!</special>
</test>
<!-- any root since take test content from stylesheet -->
<xsl:template match="/">
<html>
<head>
<title>Header/Content Widget</title>
</head>
<body>
<xsl:apply-templates select="document('')//test/*" mode="header-content-widget"/>
</body>
</html>
</xsl:template>
<!-- default action for header-content -widget is apply header then content views -->
<xsl:template match="*" mode="header-content-widget">
<xsl:apply-templates select="." mode="header-view"/>
<xsl:apply-templates select="." mode="content-view"/>
</xsl:template>
<!-- default header-view places element name in <h2> tag -->
<xsl:template match="*" mode="header-view">
<h2><xsl:value-of select="name()"/></h2>
</xsl:template>
<!-- default header-view when no text content is no-op -->
<xsl:template match="*[not(text())]" mode="header-view"/>
<!-- default content-view is to apply-templates -->
<xsl:template match="*" mode="content-view">
<xsl:apply-templates/>
</xsl:template>
<!-- special content handling -->
<xsl:template match="special" mode="content-view">
<strong><xsl:apply-templates/></strong>
</xsl:template>
Once in the body all elements contained in the test element have header-content-widget applied (in document order).
The default header-content-widget template (matching "*") first applies a header-view then applies a content-view to the current element.
The default header-view template places the current element's name in the h2 tag. The default content-view applies generic processing rules.
When there is no content as judged by the [not(text())] predicate no output for the element occurs.
One off special cases are easily handled.