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
Related
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>
I'm trying to create a variable that stores the value of an input string (TypeInput) in init cap form. This new variable will be used in different places in my stylesheet. I created a template that I call to convert the input string to init cap form. However, when I run the stylesheet, the resulting variable TypeInputInitCap shows up as NodeSet(1) in the debugger and doesn't output text in my output. Any ideas why? See sample below.
<xsl:variable name="TypeInputInitCap">
<xsl:call-template name="ConvertToInitCapString">
<xsl:with-param name="str" select="$TypeInput"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
<xsl:template name="ConvertToInitCapString">
<xsl:param name="str"></xsl:param>
<!-- Extract each component of the name delimited by . -->
<xsl:variable name="TokenNodeSet">
<xsl:for-each select="tokenize($str, '.')">
<!-- Init cap each component -->
<xsl:value-of select="concat(upper-case(substring(.,1,1)), lower-case(substring(.,2)))"></xsl:value-of>
</xsl:for-each>
</xsl:variable>
<xsl:for-each select="$TokenNodeSet">
<xsl:value-of select="."></xsl:value-of>
<xsl:if test="not(last())">
<xsl:text>.</xsl:text>
</xsl:if>
</xsl:for-each>
</xsl:template>
I think that the problem is that the $TokenNodeSet variable contains just a single string, and so the second for-each just loops once.
What about doing this instead:
<xsl:template name="ConvertToInitCapString">
<xsl:param name="str"></xsl:param>
<xsl:for-each select="tokenize($str, '\.')">
<xsl:value-of select="concat(upper-case(substring(.,1,1)), lower-case(substring(.,2)))"/>
<xsl:if test="not(last())">
<xsl:text>.</xsl:text>
</xsl:if>
</xsl:for-each>
EDIT
Fixed the tokenize() call above as suggested by LarsH in the comments
I would replace
<xsl:variable name="TokenNodeSet">
<xsl:for-each select="tokenize($str, '.')">
<!-- Init cap each component -->
<xsl:value-of select="concat(upper-case(substring(.,1,1)), lower-case(substring(.,2)))"></xsl:value-of>
</xsl:for-each>
</xsl:variable>
with
<xsl:variable name="TokenNodeSet" select="for $token in tokenize($str, '\.') return concat(upper-case(substring($token,1,1)), lower-case(substring($token,2)))" />
or better yet with
<xsl:variable name="TokenNodeSet" as="xs:string*" select="for $token in tokenize($str, '\.') return concat(upper-case(substring($token,1,1)), lower-case(substring($token,2)))" />
or finally as it is XSLT 2.0 where there are no nodesets I would rename the variable as e.g.
<xsl:variable name="TokenSequence" as="xs:string*" select="for $token in tokenize($str, '\.') return concat(upper-case(substring($token,1,1)), lower-case(substring($token,2)))" />
Thanks all for your help. The second part in my template was not necessary, so I'm now using this version, which works. It re-adds a '.' character between the tokens. (I didn't use the short version suggested in this thread because I will end up with an extra dot at the end if concatenated.):
<xsl:template name="ConvertToInitCapString">
<xsl:param name="str" select="."></xsl:param>
<!-- Extract each component of the name delimited by . -->
<xsl:for-each select="tokenize($str, '\.')">
<!-- Init cap each component -->
<xsl:value-of select="concat(upper-case(substring(.,1,1)), lower-case(substring(.,2)))"></xsl:value-of>
<xsl:if test="position() != last()">
<xsl:value-of select="'.'"></xsl:value-of>
</xsl:if>
</xsl:for-each>
</xsl:template>
This is the first time I have ever posted a question so apologese in advance if I jibber here.
I am trying to put together a CQWP with jQuery tabs slider functionality. The HTML I want to output should be in the form of 2 UL's. The first with li anchor tags with #associated-ul-id
The second ul's should have ids that associate with the list items in the first. Eg
<div id="tabs" class="news">
<div class="news-pagination">
« Prev
<ul id="carouseltext" class="horizontal-text order">
<li>System</li>
<li>School</li>
</ul>
» Next
<div class="clear"> </div>
</div>
<ul id="tabs-1" class="feed order">
<li>title 1</li>
<li>title 2</li>
</ul>
<ul id="tabs-2" class="feed order">
<li>title 3</li>
</ul>
</div>
The original XML starts off in the form
My XSL goes through the XML twice to fill the 2 ul's. The first time it just adds a new list item when the __begincolumn and __begingroup variables are true. I striped down the functionality here to just output the header. Here's a stripped down version of the XSL
<xsl:template match="/">
<xsl:variable name="Rows" select="/dsQueryResponse/Rows/Row" />
<xsl:variable name="RowCount" select="count($Rows)" />
<xsl:variable name="FirstRow" select="1" />
<xsl:param name="ColNumber" select="1" />
<xsl:for-each select="$Rows" >
<xsl:variable name="CurPosition" select="position()" />
<xsl:variable name="BeginNewsItemsList1" select="string('<ul id="tabs-')" />
<xsl:variable name="BeginNewsItemsList2" select="string('"class="feed order">')" />
<xsl:variable name="BeginNewsItemsList" select="concat($BeginNewsItemsList1, $ColNumber, $BeginNewsItemsList2)" />
<xsl:if test="($CurPosition >= $FirstRow and $CurPosition <= $LastRow)">
<xsl:variable name="StartNewGroup" select="#__begingroup = 'True'" />
<xsl:variable name="StartNewColumn" select="#__begincolumn = 'True'" />
<xsl:when test="$StartNewGroup and $StartNewColumn">
<xsl:choose>
<xsl:when test="$CurPosition = $FirstRow">
<xsl:value-of disable-output-escaping="yes" select="$BeginNewsItemsList" />
</xsl:when>
<xsl:otherwise>
<!-- other instructions -->
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:when test="$StartNewGroup">
<xsl:call-template name="OuterTemplate.CallFooterTemplate"/>
<xsl:value-of disable-output-escaping="yes" select="concat($EndColumn, $BeginNewsItemsList)" />
</xsl:when>
<xsl:otherwise>
</xsl:otherwise>
</xsl:if>
</xsl:for-each>
</xsl:template>
<xsl:template name="OuterTemplate.Count">
<xsl:param name="ColNumber" />
<xsl:value-of select="$ColNumber + 1" />
</xsl:template>
For the second for-each loop I'm having trouble setting up a counter so that I can add the number to the end of the id for each new list id="tabs-1", id="tabs-2", etc.
In theory I think I should set a parameter outside my for-each loop and then in the loop call a template that gets the parameter value and increments it. That would mean it would increment only when the template is called.
I can't use position() for this as it doesn't correspond to the values I want. I've tried to follow a couple a few blogs about recursive programming with xsl, but I can't seem to find anything that works. I'm sure I'm just writing the XSL wrong, but I'm having a bit of a brain dump now.
If anybody could point me in the right direction that would be awesome. Thanks very much.
You can't change variable's values after declaration. You can use them in expressions and/or pass as parameters. Thus, you can't use outside variable as counter explicitly. One available trick is recursive cycle like:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="root">
<HTML>
<BODY>
<xsl:call-template name="for">
<xsl:with-param name="i" select="1"/>
<xsl:with-param name="n" select="5"/>
</xsl:call-template>
</BODY>
</HTML>
</xsl:template>
<xsl:template name="for">
<xsl:param name="i"/>
<xsl:param name="n"/>
<xsl:value-of select="$i"/>
<xsl:if test="$i < $n">
<xsl:text>, </xsl:text>
<xsl:call-template name="for">
<xsl:with-param name="i" select="$i+1"/>
<xsl:with-param name="n" select="$n"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
result:
1, 2, 3, 4, 5
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.
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.