In a stylesheet I would like to choose a template based on an attribute in the source xml.
Unfortunately it looks as if one cannot use the mode attribute of apply-templates as this must be a qname literal.
Is there any other similar way to do this?
Example:
source xml:
...
<document type="1">
<item>...</item>
</document>
...
stylesheet:
...
<xsl:template match="document">
<xsl:apply-templates select="item" mode="{#type}" />
</xsl:template>
<xsl:template match="item" mode="1">
...
</xsl:template>
<xsl:template match="item" mode="2">
...
</xsl:template>
Easy answer: pattern matching.
<xsl:template match="item[../#type = 'whatever']"/>
Second easy answer: when you need variable or param references (You can't use them in patterns), use xsl:choose instruction.
<xsl:template match="item">
<xsl:param name="pType"/>
<xsl:choose>
<xsl:when test="$pType = 'whatever'">
</xsl:when>
<xsl:when test="$pType = 'otherthing'">
</xsl:when>
</xsl:choose>
</xsl:template>
Complex answer: use named template reference.
<xsl:variavle name="vTemplate" select="document('')/xsl:template/#name"/>
<xsl:template match="xsl:template/#name[.='typeA']" name="typeA">
<xsl:param name="pContext"/>
</xsl:template>
<xsl:template match="xsl:template/#name[.='typeB']" name="typeB">
<xsl:param name="pContext"/>
</xsl:template>
<xsl:template match="document">
<xsl:apply-templates select="$vTemplate[.='typeA']">
<xsl:with-param name="pContext" select="item"/>
</xsl:apply-templates>
</xsl:template>
Or look at Dimitre's FXSL.
Related
I am passing an xml file to my fo file which looks like:
<?xml version="1.0"?>
<activityExport>
<resourceKey>
<key>monthName</key>
<value>January</value>
</resourceKey>
So if I directly use:
<xsl:value-of select="activityExport/resourceKey[key='monthName']/value"/>
I can see "January" in my PDF file just fine.
However, if I use it like this in a template I have:
<xsl:template name="format-month">
<xsl:param name="date"/>
<xsl:param name="month" select="format-number(substring($date,6,2), '##')"/>
<xsl:param name="format" select="'m'"/>
<xsl:param name="month-word">
<xsl:choose>
<xsl:when test="$month = 1"><xsl:value-of select="activityExport/resourceKey[key='monthName']/value"/>
</xsl:when>
Then I do not see "January" when I call:
<xsl:variable name="monthName">
<xsl:call-template name="format-month">
<xsl:with-param name="format" select="'M'"/>
<xsl:with-param name="month" select="#monthValue"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($monthName,' ',#yearValue)"/>
I know my template works because if I have a static string in:
<xsl:choose>
<xsl:when test="$month = 1">Januaryyy</xsl:when>
Then I can see Januaryyyy fine.
So the template works, the resource exists, but the value-of-select does not work inside of call-template or xsl:choose or xsl:when test
Any help?
Regards!
Your template is probably fine, except that you are calling it from an unsuitable position in your XML. Therefore, the XPath you are using to set month-word does not find anything - it is a path to nothing.
For example, the following XSLT stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="#* | node()">
<xsl:copy>
<xsl:apply-templates select="#* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template name="format-month">
<xsl:param name="date"/>
<xsl:param name="month" select="format-number(substring($date,6,2), '##')"/>
<xsl:param name="format" select="'m'"/>
<xsl:param name="month-word">
<xsl:choose>
<xsl:when test="$month = 1">
<xsl:value-of select="activityExport/resourceKey[key='monthName']/value"/>
</xsl:when>
</xsl:choose>
</xsl:param>
<xsl:value-of select="$month-word"/>
</xsl:template>
<xsl:template match="/">
<xsl:variable name="monthName">
<xsl:call-template name="format-month">
<xsl:with-param name="month" select=" '1' "/>
<xsl:with-param name="format" select="'M'"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($monthName,' ',#yearValue)"/>
</xsl:template>
</xsl:stylesheet>
applied to this XML:
<activityExport>
<resourceKey>
<key>monthName</key>
<value>January</value>
</resourceKey>
</activityExport>
produces this output:
January
Note that I have replaced the month parameter to your template with the value 1. No elements in this input XML have a #monthValue attribute (which is what lead me to believe you are calling the template from an unsuitable location) and so month-word would not get set either because of the xsl:choose.
To make things work with your real input XML, you could try replacing the XPath with "//activityExport/resourceKey[key='monthName']/value" where the double-slash defines a path to anywhere within the XML document. This should be fine if there is only one activityExport node. Otherwise, you will need to work out the suitable XPath.
If I use <xsl:param> without specifying a value, the transformer assumes that the value is an empty string.
In other words, if I forgot to specify a value (e.g. <xsl:param name="N"/>), the compiler doesn't signal an error. This may cause my program to fail silently, which is a bad thing.
How can I specify that my <xsl:param> must have an explicit value? For example, this code should give me an error because there is no explicit value specified:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:call-template name="F1"></xsl:call-template>
<html>
<head>
<title></title>
</head>
<body>stuff</body>
</html>
</xsl:template>
<xsl:template name="F1">
<xsl:param name="N"/> <!-- I Should Get An Error Here! -->
</xsl:template>
</xsl:stylesheet>
Am looking for a solution in both XSLT 1.0 and XSLT 2.0.
In XSLT 2.0, of course, you can say <xsl:param required="yes">, so the problem goes away.
You could actually do this with a bit of meta-XSLT:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="xsl:call-template">
<xsl:variable name="template" select="/xsl:stylesheet/xsl:template[#name=current()/#name]"/>
<xsl:variable name="call" select="." />
<xsl:variable name="desc">
<xsl:value-of select="concat('call to named template "',$template/#name,'" in ')"/>
<xsl:choose>
<xsl:when test="ancestor::xsl:template/#name">
<xsl:value-of select="concat('named template "',ancestor::xsl:template/#name,'"')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="concat('template matching "',ancestor::xsl:template/#match,'"')" />
</xsl:otherwise>
</xsl:choose>
<xsl:text>
</xsl:text>
</xsl:variable>
<xsl:for-each select="$template/xsl:param[not(#select)]">
<xsl:if test="not($call/xsl:with-param[#name=current()/#name])">
<xsl:value-of select="concat('Missing parameter "',#name,'" in ',$desc)" />
</xsl:if>
</xsl:for-each>
<xsl:for-each select="xsl:with-param">
<xsl:if test="not($template/xsl:with-param[#name=current()/#name])">
<xsl:value-of select="concat('Unrecognised parameter "',#name,'" in ',$desc)" />
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
This stylesheet takes any stylesheet as an input, and checks that all call-template's have the right parameters, outputting a message if there's any errors.
This obviously isn't going to put the error checking in the transformer itself, but it will list ALL errors in one go, and can potentially be extended to check for other issues as well.
EDIT: I've adapted it to handle optional parameters, and added in a means of describing where the error is; it's actually a bit of a redesign, with optional parameters simply counting them was going to be tricky, so I removed that bit. Every error is itemized anyway, so the count wasn't really necessary.
<xsl:param name="foo" select="false" />
<xsl:if test="not($foo)">
<xsl:message terminate="yes">You called me with improper params</xsl:message>
</xsl:if>
A simple way is checking the input parameter to be not an empty string (specific case mentioned in your comment):
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="test">
<xsl:param name="nodefault"/>
<xsl:choose>
<xsl:when test="boolean($nodefault)">
<xsl:message>do your stuff</xsl:message>
</xsl:when>
<xsl:otherwise>
<xsl:message terminate="yes">Your stuff can't be done</xsl:message>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/">
<xsl:call-template name="test"/>
</xsl:template>
</xsl:stylesheet>
or much simpler:
<xsl:template name="test">
<xsl:param name="nodefault"/>
<xsl:if test="not($nodefault)">
<xsl:message terminate="yes">Your stuff can't be done</xsl:message>
</xsl:if>
<!-- do your stuff -->
</xsl:template>
There's a similar option, where you can use a variable that make a choose for the parameter in the called template, for example:
<xsl:template match="/">
<!-- call 1 -->
<xsl:apply-templates select="//encabezado/usuario" mode="forma1">
<xsl:with-param name="nombre" select="'wwww1'"/>
</xsl:apply-templates>
<!-- call 2 -->
<xsl:apply-templates select="//encabezado/usuario" mode="forma1">
</xsl:apply-templates>
<xsl:template match="node()" mode="forma1">
<xsl:param name="nombre"/>
<xsl:param name="valor"/>
<xsl:variable name="nombreLocal">
<xsl:choose>
<xsl:when test="normalize-space($nombre)">
<xsl:value-of select="$nombre"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="local-name()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:value-of select ="$nombreLocal"/>
</xsl:template>
Given the following XML fragment:
<foo>
<bar>1</bar>
<bar>2</bar>
<bar>3</bar>
</foo>
The following XSL should work:
<xsl:template match="/">
<xsl:apply-templates
mode="items"
select="bar" />
</xsl:template>
<xsl:template mode="items" match="bar">
<xsl:value-of select="." />
</xsl:template>
Is there a way I can use a similar format to this to apply a template when there are no <bar/> entities? For example:
<xsl:template match="/">
<xsl:apply-templates
mode="items"
select="bar" />
</xsl:template>
<xsl:template mode="items" match="bar">
<xsl:value-of select="." />
</xsl:template>
<xsl:template mode="items" match="none()">
There are no items.
</xsl:template>
Yes.
But the logic should be:
<xsl:template match="foo">
<xsl:apply-templates select="bar"/>
</xsl:template>
<xsl:template match="foo[not(bar)]">
There are no items.
</xsl:template>
Note: It's foo element which is having or not having bar children.
One could also use this pattern to avoid extra chooses:
<xsl:template match="/*">
<xsl:apply-templates select="bar" mode="items"/>
<xsl:apply-templates select="(.)[not(bar)]" mode="show-absence-message"/>
</xsl:template>
<xsl:template match="bar" mode="items">
<xsl:value-of select="."/>
</xsl:template>
<xsl:template match="/*" mode="show-absence-message">
There are no items.
</xsl:template>
No, when you have apply-templates select="bar" and the context node does not have any bar child elements then no nodes are processed and therefore no templates are applied. You could however change your code in the template doing the apply-templates to e.g.
<xsl:choose>
<xsl:when test="bar">
<xsl:apply-templates select="bar"/>
</xsl:when>
<xsl:otherwise>There are not items.</xsl:otherwise>
</xsl:choose>
Given the following XML fragment:
<foo>
<bar>1</bar>
<bar>2</bar>
<bar>3</bar>
</foo>
The following XSL should work:
<xsl:template match="/">
<xsl:apply-templates mode="items" select="bar" />
</xsl:template>
<xsl:template mode="items" match="bar">
<xsl:value-of select="." />
</xsl:template>
No, the <xsl:apply-templates> above doesn't select any node at all.
Is there a way I can use a similar
format to this to apply a template
when there are no entities?
Yes:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*[not(bar)]">
No <bar/> s.
</xsl:template>
<xsl:template match="/*[bar]">
<xsl:value-of select="count(bar)"/> <bar/> s.
</xsl:template>
</xsl:stylesheet>
when applied to the provided XML document:
<foo>
<bar>1</bar>
<bar>2</bar>
<bar>3</bar>
</foo>
the result is:
3<bar/> s.
When applied to this XML document:
<foo>
<baz>1</baz>
<baz>2</baz>
<baz>3</baz>
</foo>
the result is:
No <bar/> s.
I have a pretty flat XML structure that I need to reorder into categorised sections and, for the life of me, I can't figure out how to do it in XSLT (not that I'm by any means an expert.)
Basically, the original XML looks kinda like:
<things>
<thing>
<value>one</value>
<type>a</type>
</thing>
<thing>
<value>two</value>
<type>b</type>
</thing>
<thing>
<value>thee</value>
<type>b</type>
</thing>
<thing>
<value>four</value>
<type>a</type>
</thing>
<thing>
<value>five</value>
<type>d</type>
</thing>
</things>
And I need to output something like:
<data>
<a-things>
<a>one</a>
<a>four</a>
</a-things>
<b-things>
<b>two</b>
<b>three</b>
</b-things>
<d-things>
<d>five</d>
</d-things>
</data>
Note that I can't output <c-things> if there aren't any <c> elements, but I do know ahead of time what the complete list of types is, and it's fairly short so handcoding templates for each type is definitely possible. It feels like I could probably hack something together using <xsl:if> and <xsl:for-each> but it also feels like there must be a more ... 'templatey' way to do it. Can anyone help?
Cheers.
As you are using Saxon, use the native XSLT 2.0 grouping.
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="things">
<data>
<xsl:for-each-group select="thing" group-by="type">
<xsl:element name="{concat(current-grouping-key(),'-things')}">
<xsl:for-each select="current-group()">
<xsl:element name="{current-grouping-key()}">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each-group>
</data>
</xsl:template>
</xsl:stylesheet>
In XSLT 1.0 you can group with keys. This approach is called Muenchian Grouping.
The xsl:key defines an index containing thing elements, grouped by the string value of their type element. Function key() returns all nodes from the key with the specified value.
The outer xsl:for-each selects the thing elements that are the first returned by key() for their value.
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:key name="thing" match="thing" use="type" />
<xsl:template match="things">
<data>
<xsl:for-each select="thing[generate-id(.)=generate-id(key('thing',type)[1])]">
<xsl:element name="{concat(type,'-things')}">
<xsl:for-each select="key('thing',type)">
<xsl:element name="{type}">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>
The generic solution is to use an XSL key:
<xsl:key name="kThingByType" match="thing" use="type" />
<xsl:template match="things">
<xsl:copy>
<xsl:apply-templates select="thing" mode="group">
<xsl:sort select="type" />
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="thing" mode="group">
<xsl:variable name="wholeGroup" select="key('kThingByType', type)" />
<xsl:if test="generate-id() = generate-id($wholeGroup[1])">
<xsl:element name="{type}-thing">
<xsl:copy-of select="$wholeGroup/value" />
</xsl:element>
</xsl:if>
</xsl:template>
The above yields:
<things>
<a-thing>
<value>one</value>
<value>four</value>
</a-thing>
<b-thing>
<value>two</value>
<value>thee</value>
</b-thing>
<d-thing>
<value>five</value>
</d-thing>
</things>
In XSLT 2, you can do this very elegantly. Say you have a template for formatting each thing before it is wrapped in an <a> element:
<xsl:template match="thing" mode="format-thing">
<xsl:value-of select="value/text()"/>
</xsl:template>
Then you can apply that to each thing of some $type to build the <a-things> elements via a function:
<xsl:function name="my:things-group" as="element()">
<xsl:param name="type" as="xs:string"/>
<xsl:param name="things" as="element(thing)*"/>
<xsl:element name="{ concat($type, '-things') }">
<xsl:for-each select="$things[type/text() eq $type]">
<xsl:element name="{ $type }">
<xsl:apply-templates select="." mode="format-thing"/>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:function>
Then you can call that function for each unique type (a, b, d in your sample input) to build the entire output and you're done:
<xsl:template match="/">
<data>
<xsl:sequence select="
for $type in distinct-values(things/thing/type/text())
return my:things-group($type, /things/thing)
"/>
</data>
</xsl:template>
Of course, asking the question made it obvious...
My solution does use an <xsl:if>, but I can't see how it couldn't now I think about it. My solution looks basically like:
<xsl:if test="/things/thing/type = 'a'">
<a-things>
<xsl:apply-templates select="/things/thing[type='a']" mode="a" />
</a-things>
</if>
<xsl:template match="/things/thing[type='a']" mode="a">
<a><xsl:value-of select="value"/>
</xsl:template>
And repeat for the other types. I've coded it up, and it seems to work just fine.
<a-things>
<xsl:for-each select="thing[type = 'a']">
<a><xsl:value-of select="./value" /></a>
</xsl:for-each>
</a-things>
If you want to get really snazzy, replace the <a-things> and the predicate with parameters and use attribute value templates:
<xsl:param name="type" />
<xsl:element name="{$type}-things">
<xsl:for-each select="thing[type = $type]">
<xsl:element name="{$type}"><xsl:value-of select="./value" /></xsl:element>
</xsl:for-each>
</xsl:element>
And using grouping, you can do it without the if:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="things">
<data>
<xsl:for-each select="thing[not(type=preceding-sibling::thing/type)]">
<xsl:variable name="type"><xsl:value-of select="type" /></xsl:variable>
<xsl:element name="concat($type, '-things')">
<xsl:for-each select="../thing[type=$type]">
<xsl:element name="$type">
<xsl:value-of select="value" />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</data>
</xsl:template>
</xsl:stylesheet>
I am trying to implement a text filter that adds a parent node to each text node.
<xsl:template match="text()">
<aNewTag><xsl:value-of select="."/></aNewTag>
</xsl:template>
This works fine until when I call it indirectly by:
<xsl:apply-templates/>
But if I call the template directly using
<xsl:apply-templates select="text()"/>
the new tag disappears.
Can anyone explain me why?
Cheers
Jan
I was a bit confused by my own code. The complete example looks like this:
<xsl:template match="/">
<xsl:call-template name="a">
<xsl:with-param name="b">
<xsl:apply-templates select="text()"/>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="a">
<xsl:param name="b"/>
<xsl:value-of select="$b"/> <!-- here is my error -->
</xsl:template>
<xsl:template match="text()">
<aNewTag>
<xsl:value-of select="."/>
</aNewTag>
</xsl:template>
My error was, that I have not seen the value-of in the calling template. If I change the value-of to a apply-templates, everything works fine.
Thanks
Jan
If you use the xal:apply-templates element without a select attribute, the value of select is implicitly set to node() i.e. all the child nodes and hence your text() template is matched.
I think the issue is that in template "a" that the parameter "b" is a node set. To access this, you may have to use an "node-set" extension function in the XSL. It is not part of standard XSLT, so you need to specify an extension.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:ext="urn:schemas-microsoft-com:xslt">
<xsl:template match="/">
<xsl:call-template name="a">
<xsl:with-param name="b">
<xsl:apply-templates select="text()"/>
</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="a">
<xsl:param name="b"/>
<xsl:for-each select="ext:node-set($b)">
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:template>
<xsl:template match="text()">
<aNewTag>
<xsl:value-of select="."/>
</aNewTag>
</xsl:template>
</xsl:stylesheet>
This one only works for Microsoft's XML parser (MSXML) only. For other XML processors, such as xsltproc, the namespace "http://exslt.org/common" should be used.
This then allows you to access the node, or nodes, that make up the "b" parameter, although in my example above I have used to iterate over them.
Here is an article which explains about the node-set
XML.Com Article