How to match this OR that in an xsl template? - xslt

In my Sharepoint fldtypes_custom.xsl file, I have this code, which works perfectly. However, I want to use the same code on three or four similar fields.
Is there a way I can match fields named status1 OR status2, OR status3 in the same template? Right now I have to have three copies of this block of code where the only difference is the fieldref name. I would like to consolodate the code.
<xsl:template match="FieldRef[#Name='status1']" mode="body">
<xsl:param name="thisNode" select="."/>
<xsl:variable name="currentValue" select="$thisNode/#status1" />
<xsl:variable name="statusRating1">(1)</xsl:variable>
<xsl:variable name="statusRating2">(2)</xsl:variable>
<xsl:variable name="statusRating3">(3)</xsl:variable>
<xsl:choose>
<xsl:when test="contains($currentValue, $statusRating1)">
<span class="statusRatingX statusRating1"></span>
</xsl:when>
<xsl:when test="contains($currentValue, $statusRating2)">
<span class="statusRatingX statusRating2"></span>
</xsl:when>
<xsl:when test="contains($currentValue, $statusRating3)">
<span class="statusRatingX statusRating3"></span>
</xsl:when>
<xsl:otherwise>
<span class="statusRatingN"></span>
</xsl:otherwise>
</xsl:choose>
</xsl:template>

Is there a way I can match fields named status1 OR status2, OR status3
in the same template?
Use:
<xsl:template match="status1 | status2 | status3">
<!-- Your processing here -->
</xsl:template>
However, I see from the provided code, that the strings "status1", "status2" and "status3" aren't element names -- they are just possible values of the Name attribute of the FieldRef element.
In this case, your template could be:
<xsl:template match="FieldRef
[#Name = 'status1' or #Name = 'status2' or #Name = 'status3']">
<!-- Your processing here -->
</xsl:template>
In case there are many possible values for the Name attribute, one can use the following abbreviation:
<xsl:template match="FieldRef
[contains('|status1|status2|staus3|', concat('|',#Name, '|'))]">
<!-- Your processing here -->
</xsl:template>

Related

How to conditionally output tags?

Tags can be outputted by either directly typing
<div>
<span>complex...</span>
</div>
or using <xsl:element>,
<xsl:element name="div">
<span>complex...</span>
</xsl:element>
My question is how to do this: when x, output <div>, when y, output <a>, when z, output no tag?
One of course can make three templates, or even write ugly code as
<xsl:when ...x >
<![CDATA[ <div> ]]>
</xsl:when>
<span>complex...</span>
<xsl:when ...x >
<![CDATA[ </div> ]]>
</xsl:when>
but is there a way to conditionally provide the value of the name attribute of xsl:element?
I tried this, failed:
<xsl:variable name="a" select="'div'"/>
<xsl:element name="$a">
...
[edited] Forgot to say, XSLT1.0 only
Here's another way to look at it:
<xsl:variable name="content">
<span>complex...</span>
</xsl:variable>
<xsl:choose>
<xsl:when ... x>
<div>
<xsl:copy-of select="$content"/>
</div>
</xsl:when>
<xsl:when ... y>
<a>
<xsl:copy-of select="$content"/>
</a>
</xsl:when>
<xsl:when ... z>
<xsl:copy-of select="$content"/>
</xsl:when>
</xsl:choose>
The name attribute is not expecting a full-fledge XPath expression but simply a string. So, instead of using name="$a" you only have to evaluate the Xpath expression into a string by bracing it with curly braces:
<xsl:element name="{$a}">
As for the conditional creation of the surrounding tag you could do something like this:
<xsl:variable name="tag_name">
<xsl:choose>
<xsl:when test="x">
<xsl:text>div</xsl:text>
</xsl:when>
<xsl:when test="y">
<xsl:text>a</xsl:text>
</xsl:when>
</xsl:choose>
<!-- possibly other checks for different tag names -->
<xsl:variable>
<xsl:choose>
<xsl:when test="$tag_name != ''">
<xsl:element name="$tag_name">
<!-- whatever has to be put into a tagged block (A) -->
</xsl:element>
</xsl:when>
<xsl:otherwise>
<!-- whatever has to be put into a untagged block (B) -->
</xsl:otherwise>
</xsl:choose>
If A and B are equal you could put that into a template.
XSLT doesn't output tags: it outputs nodes to a result tree. Your suggestion of using constructs like <![CDATA[ </div> ]]> is therefore way off the mark: you can't add half a node to a tree.
However, there's no difficulty with conditional generation of elements nodes. If you want to create an element but compute its name conditionally, then in XSLT 2.0 you can do
<xsl:element name="{if (test) then 'a' else 'b'}">
or if you're stuck with 1.0, the much more verbose
<xsl:variable name="elname">
<xsl:choose>
<xsl:when test="test">a</xsl:when>
<xsl:otherwise>b</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$elname}"/>
If you want to output an element or nothing depending on a condition, just do
<xsl:if test="test2">
<e/>
</xsl:if>

XSLT get a variable in case of element name

I can get an xml with one of three nodes.
<root>
<headerA>
<!-- Content -->
</headerA>
</root>
in place of <headerA> there can be <headerB> or <headerŠ”> node with similar content. In this case I want to have html output as:
<span> Type [X] </span>
Where [X] is A, B or C depending on the tag name of the element.
As Tomalak has already observed, your question is rather vague. It sounds as if you might be asking how to write either a single template for the three kinds of header:
<xsl:template match="headerA | headerB | headerC">
<span>
<xsl:apply-templates/>
</span>
</xsl:template>
or else how to write similar but distinct templates for them:
<xsl:template match="headerA">
<span> Type A </span>
</xsl:template>
<xsl:template match="headerB">
<span> Type B </span>
</xsl:template>
<!--* Template for headerC left as an exercise for the reader *-->
or else how to write a single template that does slightly different things based on what it matches:
<xsl:template match="headerA | headerB | headerC">
<span>
<xsl:choose>
<xsl:when test="self::headerA">
<xsl:text> Type A </xsl:type>
</xsl:when>
<xsl:when test="self::headerB">
<xsl:text> Type B </xsl:type>
</xsl:when>
<!--* etc. *-->
<xsl:otherwise>
<xsl:message terminate="yes"
>I thought this could not happen.</xsl:message>
</xsl:otherwise>
</xsl:choose>
<xsl:apply-templates/>
</span>
</xsl:template>
If you figure out which of these helps you, you will be closer to understanding what question you are trying to ask.
<xsl:template match="*[starts-with(name(), 'header')]">
<xsl:variable name="type" select="substring-after(name(), 'header')" />
<span>
<xsl:value-of select="concat(' Type ', $type, ' ')" />
</span>
</xsl:template>
This would work for any element named headerX, where can be X any string.

XSLT xsl:apply-templates Conditional Syntax

I've got the following XSLT code which lists out the folders and their file items from a specified node.
This all works fine but I'd like to parameterise the page and optionally filter its output by a tag value.
Being an XLST numpty I'm stumped with the syntax for the conditional I should be putting in under the <xsl:when test="$tag"> clause - can someone please help ?
<xsl:variable name="tag" select="umbraco.library:Request('tag')" />
<xsl:template match="/">
<!-- Root folder in Media that holds the folders to output -->
<xsl:variable name="mediaRootFolderId" select="5948" />
<!-- Pass in true() to get XML for all nodes below -->
<xsl:variable name="mediaRootNode" select="umbraco.library:GetMedia($mediaRootFolderId, true())" />
<xsl:choose>
<xsl:when test="$tag">
</xsl:when>
<xsl:otherwise>
<!-- If we didn't get an error, output Folder elements that contain Image elements -->
<xsl:apply-templates select="$mediaRootNode[not(error)]/Folder[File]" >
<xsl:sort select="#nodeName"/>
</xsl:apply-templates>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Template for folders -->
<xsl:template match="Folder">
<div class="folder">
<h2>Folder: <xsl:value-of select="#nodeName" /></h2>
<div class="images">
<xsl:apply-templates select="File">
<xsl:sort select="#nodeName"/>
</xsl:apply-templates>
</div>
</div>
</xsl:template>
<!-- Template for files -->
<xsl:template match="File">
File: <a href="{umbracoFile}" alt="{#nodeName}" ><xsl:value-of select="#nodeName" /></a> <br/>
</xsl:template>
Instead of the long <xsl:choose> instruction, use:
<xsl:apply-templates select=
"$mediaRootNode[not($tag)][not(error)]
/Folder[File]" >
Explanation: For the XPath expression in the select attribute above to select a non-empty set of nodes it is necessary that boolean($tag) is true(). Thus the above single <xsl:apply-templates> instruction is equivalent to the long <xsl:choose> in the question.
you can test if $tag is set like this.
<xsl:param name="tag">
<xsl:message terminate="yes">
$tag has not been set
</xsl:message>
</xsl:param>
This isn't standard though, it'll work on most XSLT processors though.
If you wanted to be absolutely save, you could set the value to an illegal value (such as 1 div 0) and test for it in the body of the template:
<xsl:param name="tag" select="1 div 0" />
<xsl:if test="$tag = 1 div 0">
<xsl:message terminate="yes">
$tag has not been set, or has been set to Infinity, which is invalid.
</xsl:message>
</xsl:if>
Source: O'Reilly XSLT Cookbook

Unique ID and multiple classes with XPath

I'm using XSLT for displaying a ul menu containing li and a.
I want the following:
Find the first li a element and add the .firstitem class.
Find the last li a element and add the .lastitem class.
Find the active li a element and add the .active class.
Add an unique ID to each li a element. (I.e. URL friendly menu text as ID).
I've managed to make step 1-3 work. Except that when I try to add the classes, it actually replaces the other classes rather than adding to them.
Here's the code:
<li>
<a>
<!-- Add .firstitem class -->
<xsl:if test="position() = 1">
<xsl:attribute name="class">firstitem</xsl:attribute>
</xsl:if>
<!-- Add .lastitem class -->
<xsl:if test="postition() = count(//Page)">
<xsl:attribute name="class">lastitem</xsl:attribute>
</xsl:if>
<!-- Add .active class -->
<xsl:if test="#Active='True'">
<xsl:attribute name="class">active</xsl:attribute>
</xsl:if>
<!-- Add link URL -->
<xsl:attribute name="href"><xsl:value-of select="#FriendlyHref" disable-output-escaping="yes"/></xsl:attribute>
<!-- Add link text -->
<xsl:value-of select="#MenuText" disable-output-escaping="yes"/>
</a>
</li>
In realtity, the a element could contain all those three classes. But as is goes through the code, it replaces everything in the class attribute. How can I add the classes instead of replacing them?
And step number 4 on my list, is to get a unique ID, preferably based on #MenuText. I know there is a replace() function, but I can't get it to work and my editor says that replace() isn't a function.
The menu item text contains spaces, dashes and other symbols that are not valid for using in the id attribute. How can I replace those symbols?
<a>
<xsl:attribute name="class">
<!-- Add .firstitem class -->
<xsl:if test="position() = 1">
<xsl:text> firstitem</xsl:text>
</xsl:if>
<!-- Add .lastitem class -->
<xsl:if test="postition() = count(//Page)">
<xsl:text> lastitem</xsl:text>
</xsl:if>
<!-- Add .active class -->
<xsl:if test="#Active='True'">
<xsl:text> active</xsl:text>
</xsl:if>
</xsl:attribute>
<!-- Add link URL -->
<xsl:attribute name="href"><xsl:value-of select="#FriendlyHref" disable-output-escaping="yes"/></xsl:attribute>
<!-- Add link text -->
<xsl:value-of select="#MenuText" disable-output-escaping="yes"/>
</a>
replace() is an XSLT2.0 function. When using XSLT1.0 you need a custom template to do most string manipulations.
I'm adding this to Martijn Laarman's answer, which covers your requirements 1-3 and has my vote:
To remove everything except a certain range of characters from a string with XSLT 1.0 (your 4th requirement), do the following.
<!-- declare at top level -->
<xsl:variable
name="validRange"
select="'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
/>
<!-- later, within a templateā€¦ -->
<xsl:attribute name="id">
<xsl:value-of select="
concat(
'id_',
translate(
#MenuText,
translate(#MenuText, $validRange, ''),
''
)
)
" />
</xsl:attribute>
The inner translate() removes any valid character from #MenuText, leaving only the invalid ones. These are fed to the outer translate(), which now can remove all invalid chars from the #MenuText, whatever they might be in this instance. Only the valid chars remain.
You can make a function out of it:
<xsl:template name="HtmlIdFromString">
<xsl:param name="input" select="''" />
<xsl:value-of select="
concat('id_', translate( $input, translate($input, $validRange, ''), ''))
" />
</xsl:template>
and call it like this:
<xsl:attribute name="id">
<xsl:call-template name="HtmlIdFromString">
<xsl:with-param name="input" select="#MenuText" />
</xsl:call-template>
</xsl:attribute>
Use
<xsl:template match="#*">
<xsl:copy>
<xsl:apply-templates select="#*"/>
</xsl:copy>
</xsl:template>
to copy all existing attributes.
The replace() function is only supported in xslt 2.0 but i found this workaround for xslt 1.0.

with-param not working in apply-templates

I am currently trying to generate the creation SQL for my tables based off of a Visio diagram. I am doing this using the approach found here.
http://www.dougboude.com/blog/1/2008/11/SQL-Forward-Engineering-with-Visio-2003-Professional.cfm
I am attempting to modify the xslt file found there to better model the syntax that we use in our office. Unfortunately, I cannot get the part that involves passing the table name into the template for the table columns to work. The template gets called, but it seems to ignore my parameters.
<xsl:template match="Entity" mode="table">
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name = '<xsl:value-of select="#PhysicalName"/>')
<br />
CREATE TABLE dbo.[<xsl:value-of select="#PhysicalName"/>]
(
<br />
<xsl:for-each select="EntityAttributes/EntityAttribute">
<span style="padding-left: 20px;">
<xsl:apply-templates select="../../../../Attributes/Attribute[#AttributeID = current()/#EntityAttributeID]" mode="table">
<xsl:with-param name="EntityName" select="#PhysicalName" />
</xsl:apply-templates>
</span>
<xsl:if test="count(../../EntityAttributes/EntityAttribute) != position()">,</xsl:if>
<br />
</xsl:for-each>
)
<br />
GO
<p />
<xsl:apply-templates select="EntityAnnotations/EntityAnnotation[#AnnotationType='Primary Key']" mode="pk"/>
<xsl:apply-templates select="EntityAnnotations/EntityAnnotation[#AnnotationType='Alternate Key']" mode="ak"/>
<xsl:apply-templates select="EntityAnnotations/EntityAnnotation[#AnnotationType='Index']" mode="idx"/>
</xsl:template>
<!-- Create column for each EntityAttribute -->
<xsl:template match="Attribute" mode="table">
<xsl:param name="EntityName"></xsl:param>
<xsl:variable name="nullability">
<xsl:choose>
<xsl:when test='#AllowNulls = "false"'>NOT NULL CONSTRAINT DF_<xsl:value-of select="$EntityName" />_<xsl:value-of select="#PhysicalName"/>
</xsl:when>
<xsl:otherwise> NULL</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:variable name="incremental">
<xsl:choose>
<xsl:when test='#PhysicalDatatype = "int identity"'> INT IDENTITY(1,1)</xsl:when>
<xsl:otherwise><xsl:value-of select="#PhysicalDatatype"/></xsl:otherwise>
</xsl:choose>
</xsl:variable>
[<xsl:value-of select="#PhysicalName"/>] <span style="text-transform:uppercase;"> <xsl:value-of select="$incremental"/></span> <xsl:value-of select="$nullability"/>
</xsl:template>
The parameter is not ignored, but I guess it is empty. You call:
<xsl:with-param name="EntityName" select="#PhysicalName" />
where #PhysicalName must be an attribute of EntityAttributes/EntityAttribute element from the for-each. The fact that you use #PhysicalName earlier in
CREATE TABLE dbo.[<xsl:value-of select="#PhysicalName"/>]
makes me think in reality it is an attribute of the Entity element the template matches. You need to store its value in a variable first (before the for-each):
<xsl:variable name="PhysicalName" select="#PhysicalName" />
and then use it like this:
<xsl:with-param name="EntityName" select="$PhysicalName" />
<!-- -------------------------------------^ -->
The for-each resets the context node with every iteration, I guess this is where it goes wrong for you.