XSLT Add Element Recursively - xslt

I am brand new to XSLT and trying to transform an incoming file to then be processed by another program. The goal is to try and add blank lines to an existing XML file to mimic the page length for the other program to process correctly. I need to count the <line> elements between each <page> element and if that total is less than 75, add the remaining lines as blank. What is the best way to recursively add a blank line in XSLT? So far my incoming XML looks like this:
<page>
<line>Some text</line>
<line>Some text</line>
<line>Some text</line>
etc...
</page>
I have been looking at examples that use nodes to group things together like this (setting the group size variable):
<xsl:call-template name="file">
<xsl:with-param name="nodes" select=".|following-sibling::*[not(position() > $pGroupSize - 1)]"/>
</xsl:call-template>
I have also been finding results that talk about segmented recursion like the example below:
<xsl:template name="priceSumSegmented">
<xsl:param name="productList"/>
<xsl:param name="segmentLength" select="5"/>
<xsl:choose>
<xsl:when test="count($productList) > 0">
<xsl:variable name="recursive_result1">
<xsl:call-template name="priceSum">
<xsl:with-param name="productList"
select="$productList[position() <= $segmentLength]"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="recursive_result2">
<xsl:call-template name="priceSumSegmented">
<xsl:with-param name="productList"
select="$productList[position() > $segmentLength]"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$recursive_result1 + $recursive_result2"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="0"/></xsl:otherwise>
</xsl:choose>
</xsl:template>
Any help on the best way to approach this would be appreciated.

I think you're making this more complicated than it needs to be. Even in XSLT 1.0, you could do simply:
<xsl:template match="page">
<xsl:copy>
<xsl:copy-of select="line"/>
<xsl:call-template name="generate-lines">
<xsl:with-param name="n" select="75 - count(line)"/>
</xsl:call-template>
</xsl:copy>
</xsl:template>
<xsl:template name="generate-lines">
<xsl:param name="n"/>
<xsl:if test="$n > 0">
<line/>
<!-- recursive call -->
<xsl:call-template name="generate-lines">
<xsl:with-param name="n" select="$n - 1"/>
</xsl:call-template>
</xsl:if>
</xsl:template>

Related

XSLT: Using following-sibling on an array of strings?

Having tokenized an attribute containing a file path (e.g. /dir1/dir2/dir3), I now have an array (or node set?) of strings.
I would like to recursively process the first item and pass the rest on - and had hoped to accomplish this using the 'following-sibling' axis. However, it turns out that it expects actual elements and not merely strings.
<xsl:template match="s:universe">
<xsl:call-template name="createSubFolder">
<xsl:with-param name="folderNames" select="tokenize(#path, '/')" />
</xsl:call-template>
</xsl:template>
<xsl:template name="createSubFolder">
<xsl:param name="folderNames" />
<xsl:if test="count($folderNames) > 0">
<folder>
<xsl:attribute name="name" select="$folderNames[1]" />
<xsl:if test="position() < count($folderNames)">
<folder>
<xsl:call-template name="createSubFolder">
<xsl:with-param name="folderNames" select="$folderNames[1]/following-sibling::text()" />
</xsl:call-template>
</folder>
</xsl:if>
</folder>
</xsl:if>
</xsl:template>
The only solution I currently imagine would be creating a custom function which feeds the tail end of an array to the template - but I have the feeling that there could/must be a better way.
As Martin Honnen mentioned in the comments, tokenize(#path, '/') returns sequence of string(s). So you cannot use following-sibling axis to the string type.
You can use subsequence($folderNames,2) or $folderNames[position() gt 1] to use recursive call like following:
<xsl:template name="createSubFolder">
<xsl:param name="folderNames" as="xs:string*"/>
<xsl:if test="exists(($folderNames[1]))">
<folder>
<xsl:attribute name="name" select="$folderNames[1]" />
<xsl:call-template name="createSubFolder">
<xsl:with-param name="folderNames" select="subsequence($folderNames,2)"/>
</xsl:call-template>
</folder>
</xsl:if>
</xsl:template>

XSL call template with dynamic nodes

Hello i need to achieve the below functionality in my XSL but it seems i'm stuck... Any help will be much appreciated.
Please see my comments inside the below code snippet.
<xsl:template name="/">
<xsl:call-template name="looptemplate">
<xsl:with-param name="x" select="1"/>
<xsl:with-param name="max" select="10"/>
</xsl:call-template>
</xsl:template>
<xsl:template name=" looptemplate">
<xsl:param name="x"/>
<xsl:param name="max"/>
<xsl:call-template name="TemplateToCall">
<xsl:with-param name="nodePath" select="a/b$i"></xsl:with-param>
<!--
Get dynamically root nodes
a/b1, a/b2, a/b3 etc
-->
</xsl:call-template>
<!--
Loop again until x reaches max
-->
</xsl:template>
<xsl:template name="TemplateToCall">
<xsl:param name="nodePath"/>
<xsl:for-each select="$nodePath">
<xsl:value-of select="value1"/>, <xsl:value-of select="value2"/>
</xsl:for-each>
</xsl:template>
You can't build an XPath as a string and evaluate it dynamically like that (at least not in plain XSLT 1.0 or 2.0, there will be an xsl:evaluate instruction in XSLT 3.0), but you could do something like
<xsl:call-template name="TemplateToCall">
<xsl:with-param name="nodes" select="a/*[local-name() = concat('b', $i)]"/>
and then in the called template
<xsl:template name="TemplateToCall">
<xsl:param name="nodes"/>
<xsl:for-each select="$nodes">

How to get the last index of a char in a string?

I have the following task:
There is an xml element containing a long string. I need to use xsl to transform this element into a number of html <input> tags. It works like this: if the string is longer than an <input> field can hold without scrolling I call the same template recursively to create another input field with the remainig text.
The problem is that the string is often splitted right in the middle of the word which is not nice.
So I need to find the position of the last space character that is not bigger than the size of the substring that fits into the <input> tag and print only the substring before it on the line.
So I prepare a substring of max length, that can fit in the field, but I have no idea of how to get the index of the last space in it and pass it as a parameter into the next call of the function.
UPD: here is what I've got so far
<xsl:template name="multilineInput">
<xsl:param name="input" select="."/>
<xsl:param name="maxFirst" select="."/>
<xsl:param name="firstLineWidth" select="."/>
<input>
<xsl:attribute name="readonly">readonly</xsl:attribute>
<xsl:attribute name="class">input_multiline</xsl:attribute>
<xsl:attribute name="style">width = "<xsl:value-of select="$firstLineWidth"/>"</xsl:attribute>
<xsl:attribute name="type">text</xsl:attribute>
<xsl:attribute name="value"><xsl:value-of select="substring($input, 1, $maxFirst)"/></xsl:attribute>
</input>
<xsl:if test="$maxFirst < string-length($input)">
<xsl:call-template name="multilineInput">
<xsl:with-param name="input" select="substring($input, $maxFirst+1, string-length($input)-$maxFirst)"/>
<xsl:with-param name="maxFirst" select="110"/>
<xsl:with-param name="firstLineWidth" select="'980'"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
The following recursive template can be used to return the last-index of a given delimiter:
<xsl:template name="last-index-of">
<xsl:param name="txt"/>
<xsl:param name="remainder" select="$txt"/>
<xsl:param name="delimiter" select="' '"/>
<xsl:choose>
<xsl:when test="contains($remainder, $delimiter)">
<xsl:call-template name="last-index-of">
<xsl:with-param name="txt" select="$txt"/>
<xsl:with-param name="remainder" select="substring-after($remainder, $delimiter)"/>
<xsl:with-param name="delimiter" select="$delimiter"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="lastIndex" select="string-length(substring($txt, 1, string-length($txt)-string-length($remainder)))+1"/>
<xsl:choose>
<xsl:when test="string-length($remainder)=0">
<xsl:value-of select="string-length($txt)"/>
</xsl:when>
<xsl:when test="$lastIndex>0">
<xsl:value-of select="($lastIndex - string-length($delimiter))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="0"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
It can be invoked like this:
<xsl:call-template name="last-index-of">
<xsl:with-param name="txt" select="'The quick brown fox jumped over the lazy dog.'"/>
<xsl:with-param name="delimiter" select="' '"></xsl:with-param>
</xsl:call-template>
And returns: 41
You can assign the result of the template call to a variable like this:
<xsl:variable name="last-index">
<xsl:call-template name="last-index-of">
<xsl:with-param name="txt" select="'The quick brown fox jumped over the lazy dog.'"/>
<xsl:with-param name="delimiter" select="' '"></xsl:with-param>
</xsl:call-template>
</xsl:variable>
This transformation imlements a simple and efficient algorithm: the last space in a string is the first space in the reversed string:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/">
<xsl:call-template name="lastCharIndex">
<xsl:with-param name="pText" select=
"'The quick brown fox jumped over the lazy dog.'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="lastCharIndex">
<xsl:param name="pText"/>
<xsl:param name="pChar" select="' '"/>
<xsl:variable name="vRev">
<xsl:call-template name="reverse">
<xsl:with-param name="pStr" select="$pText"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select=
"string-length($pText) - string-length(substring-before($vRev, $pChar))"/>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="pStr"/>
<xsl:variable name="vLength" select="string-length($pStr)"/>
<xsl:choose>
<xsl:when test="$vLength = 1"><xsl:value-of select="$pStr"/></xsl:when>
<xsl:otherwise>
<xsl:variable name="vHalfLength" select="floor($vLength div 2)"/>
<xsl:variable name="vrevHalf1">
<xsl:call-template name="reverse">
<xsl:with-param name="pStr"
select="substring($pStr, 1, $vHalfLength)"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vrevHalf2">
<xsl:call-template name="reverse">
<xsl:with-param name="pStr"
select="substring($pStr, $vHalfLength+1)"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="concat($vrevHalf2, $vrevHalf1)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on any XML document (not used), the wanted, correct result is produced:
41
You could use the EXSLT split template (description; implementation) or tokenize to split the string up by spaces. Then you could print all the text nodes except the last one, and/or use the length of the last text node to get the index of the last space.
See also this template which you could adapt to your needs.

OpenOffice bug while exporting from Calc using xslt filters

I have a row-based data in Calc which I want to export to xml using xslt filters. The filters work properly except when values in two adjacent columns are the same. For example, see the data below ...
SrNo Col2 Col3 Col4 Col5
1 PQR 123 567 LMN
2 OPQ 665 786 BCD
3 EUR 443 443 UFF
4 OLE 345 887 JAS
5 EJR 565 565 OEP
For the above data, this error occurs only for lines 3 and 5. For some reason, the filter skips col4 and takes the value from col5. For rest of the data the export works perfectly fine. Here is the xslt code ...
<row>
<col1><xsl:value-of select="table:table-cell[1]"/></col1>
<col2><xsl:value-of select="table:table-cell[2]"/></col2>
<col3><xsl:value-of select="table:table-cell[3]"/></col3>
<col4><xsl:value-of select="table:table-cell[4]"/></col4>
<col5><xsl:value-of select="table:table-cell[5]"/></col5>
</row>
Can someone please give any inputs on this ? It's pretty weird and I am badly stuck up due to this. Btw, I am using OpenOffice 3.1.1 (Build 9420) with xslt 2.0.
That was great code Rohit; it definitely helped send me in the right direction. I was having a hard time getting the XSLT 2.0 code working in my LibreOffice installation, so I converted the code to use XSLT 1.0 (named templates instead of functions calls, and an extension to be able to pass a node into the recursive function). In case someone needs it, my code is below. Note that it is not general purpose code - you will need to replace my fields with your own.
This particular example exports a spreadsheet into a valid .plist file that is recognized by XCode. It has been tested with LibreOffice 3.5 running on Vista.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
xmlns:xt="http://www.jclark.com/xt"
extension-element-prefixes="xt"
xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0"
xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
exclude-result-prefixes="office table text">
<!--xsl:output method = "xml" indent = "yes" encoding = "UTF-8" omit-xml-declaration = "no"/-->
<xsl:output method = "xml" indent = "yes" encoding = "UTF-8" omit-xml-declaration = "no" doctype-system = "http://www.apple.com/DTDs/PropertyList-1.0.dtd" doctype-public = "-//Apple//DTD PLIST 1.0//EN" />
<xsl:template name="getColumnValue">
<xsl:param name="tableRow"/>
<xsl:param name="colIndex"/>
<xsl:param name="currentIndex"/>
<xsl:choose>
<xsl:when test="$currentIndex < $colIndex">
<xsl:variable name="repeatColumns" select="xt:node-set($tableRow)/table:table-cell[$currentIndex]/#table:number-columns-repeated"/>
<xsl:choose>
<xsl:when test="$repeatColumns">
<xsl:choose>
<xsl:when test="$currentIndex + $repeatColumns - 1 >= $colIndex">
<xsl:value-of select="xt:node-set($tableRow)/table:table-cell[$currentIndex]"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name = "recursiveResult">
<xsl:call-template name="getColumnValue">
<xsl:with-param name="tableRow" select="$tableRow"/>
<xsl:with-param name="colIndex" select="$colIndex - $repeatColumns + 1"/>
<xsl:with-param name="currentIndex" select="$currentIndex + 1"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$recursiveResult"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:variable name = "recursiveResult">
<xsl:call-template name="getColumnValue">
<xsl:with-param name="tableRow" select="$tableRow"/>
<xsl:with-param name="colIndex" select="$colIndex"/>
<xsl:with-param name="currentIndex" select="$currentIndex + 1"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$recursiveResult"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="xt:node-set($tableRow)/table:table-cell[$colIndex]"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- By setting the PropertyValue "URL" in the properties used in storeToURL(), -->
<!-- we can pass a single parameter to this stylesheet. -->
<!-- Caveat: If we use the "URL" property in the stylesheet and call in OOo -->
<!-- from the menu "File" > "Export...", OOo assigns a target URL. And that -->
<!-- might not be what we want. -->
<xsl:param name="targetURL"/>
<xsl:variable name="exportDate">
<xsl:choose>
<xsl:when test="string-length(substring-before($targetURL,';'))=10">
<xsl:value-of select="substring-before($targetURL,';')"/>
</xsl:when>
<xsl:when test="string-length($targetURL)=10">
<xsl:value-of select="$targetURL"/>
</xsl:when>
</xsl:choose>
</xsl:variable>
<xsl:variable name="exportUser">
<xsl:if test="string-length(substring-after($targetURL,';'))>0">
<xsl:value-of select="substring-after($targetURL,';')"/>
</xsl:if>
</xsl:variable>
<xsl:template match="/">
<plist version="1.0">
<dict>
<key>Animations</key>
<array>
<!-- Process all tables -->
<xsl:apply-templates select="//table:table"/>
</array>
</dict>
</plist>
</xsl:template>
<xsl:template match="table:table">
<!-- Process all table-rows after the column labels in table-row 1 -->
<xsl:for-each select="table:table-row">
<xsl:if test="position()>1">
<dict>
<key>character</key>
<string>
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="1"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</string>
<key>animation</key>
<string>
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="2"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</string>
<key>cycle</key>
<xsl:variable name="cycleTmp">
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="3"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</xsl:variable>
<xsl:if test="$cycleTmp > 0">
<true/>
</xsl:if>
<xsl:if test="$cycleTmp = 0">
<false/>
</xsl:if>
<key>frames</key>
<integer>
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="4"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</integer>
<key>randomSpeedPercent</key>
<integer>
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="5"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</integer>
<key>spriteNameRoot</key>
<string>
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="6"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</string>
<key>spriteSheetName</key>
<string>
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="7"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</string>
<key>anchorX</key>
<integer>
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="11"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</integer>
<key>anchorY</key>
<integer>
<xsl:call-template name="getColumnValue"> <xsl:with-param name="colIndex" select="12"/> <xsl:with-param name="currentIndex" select="1"/> <xsl:with-param name="tableRow" select="."/> </xsl:call-template>
</integer>
</dict>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
The issue is due to number-columns-repeated attribute to table:table-cell element in the sheet's underlying data structure. For more details, please refer to http://user.services.openoffice.org/en/forum/viewtopic.php?f=45&t=29674 and http://user.services.openoffice.org/en/forum/viewtopic.php?f=9&t=11865.
Though the latter link claims to have resolved the issue, the solution is not quite what I was looking for. I required a simple index-based solution which allows a more flexible xml generation. Here is what I tried to work out.
I have used xslt 2.0 to make use of user defined functions. Here is the stylesheet ...
<xsl:output method="xml" indent="yes" encoding="UTF-8" omit-xml-declaration="no"/>
<xsl:function name="my:getColumnValue">
<xsl:param name="tableRow" as="node()"/>
<xsl:param name="colIndex"/>
<xsl:param name="currentIndex"/>
<xsl:choose>
<xsl:when test="$currentIndex < $colIndex">
<xsl:variable name="repeatColumns" select="$tableRow/table:table-cell[$currentIndex]/#table:number-columns-repeated"/>
<xsl:choose>
<xsl:when test="$repeatColumns">
<xsl:choose>
<xsl:when test="$currentIndex + $repeatColumns - 1 >= $colIndex"><xsl:value-of select="$tableRow/table:table-cell[$currentIndex]"/></xsl:when>
<xsl:otherwise><xsl:value-of select="my:getColumnValue($tableRow, $colIndex - $repeatColumns + 1, $currentIndex + 1)"/></xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise><xsl:value-of select="my:getColumnValue($tableRow, $colIndex, $currentIndex + 1)"/></xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$tableRow/table:table-cell[$colIndex]"/></xsl:otherwise>
</xsl:choose>
</xsl:function>
<xsl:template match="//table:table">
<Tests>
<!-- Process all table rows -->
<xsl:variable name="colCount" select="count(table:table-row[1]/table:table-cell)"/>
<xsl:for-each select="table:table-row">
<xsl:if test="position() > 1">
<Test>
<SrNo><xsl:value-of select="my:getColumnValue(.,1,1)"/></SrNo>
<Name><xsl:value-of select="my:getColumnValue(.,2,1)"/></Name>
<Age><xsl:value-of select="my:getColumnValue(.,3,1)"/></Age>
<Height><xsl:value-of select="my:getColumnValue(.,4,1)"/></Height>
<Address><xsl:value-of select="my:getColumnValue(.,5,1)"/></Address>
</Test>
</xsl:if>
</xsl:for-each>
</Tests>
</xsl:template>
The tags used above are just placeholders. Please replace them with appropriate ones in your xslt. This solution is limited by number of recursive calls allowed by the xslt processor.
If xslt 1.0 supports sending node as params, then we can try to replace the above udf to get a template based solution. If you find any bugs, do let me know.
Marcel's solution looks complicated, but works perfect and is not so complicated to adapt.
So, again in short for everybody:
You basically copy&paste the whole template into your xsl:
<xsl:template name="getColumnValue"> ... </>
Then, replace all the standard table:row (which is failing) with
<xsl:call-template name="getColumnValue">
<xsl:with-param name="colIndex" select="1"/>
<xsl:with-param name="currentIndex" select="1"/>
<xsl:with-param name="tableRow" select="."/>
</xsl:call-template>
Be sure to adjust the "colIndex" values, but leave the currentIndex on 1!
Finally, add the xt lines of the header to yours.
xmlns:xt="http://www.jclark.com/xt"
extension-element-prefixes="xt"
Perfect, Marcel!

XSL - Removing the filename from the path string

I've got a SharePoint problem which I need some help with. I'm creating some custom ItemStyles to format the output of a Content Query Webpart (CQWP) but I need to insert a "view all" button into the output.
View all needs to point to:
http://www.site.com/subsite/doclibrary1/Forms/AllItems.aspx
All the individual files in the document library have the link of:
http://www.site.com/subsite/doclibrary1/FileName.doc
So what I need is some XSL functions to strip FileName.doc from the end of the string.
I've tried using substring-before($variable, '.') to get rid of the .doc, but I then need to find a way to use substring-after to search for the LAST forward slash in the series and truncate the orphaned filename.
Using #Mads Hansen's post, this is the code which resolved the problem:
Template in ItemStyle.xsl
<xsl:template name="ImpDocs" match="Row[#Style='ImpDocs']" mode="itemstyle">
<xsl:variable name="SafeLinkUrl">
<xsl:call-template name="OuterTemplate.GetSafeLink">
<xsl:with-param name="UrlColumnName" select="'LinkUrl'"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="ViewAllLink">
<xsl:call-template name="OuterTemplate.getCleanURL">
<xsl:with-param name="path" select="#LinkUrl"/>
</xsl:call-template>
</xsl:variable>
<div class="DocViewAll">
View All
<!--Any other code you need for your custom ItemStyle here-->
</div>
</xsl:template>
Template in ContentQueryMain.xsl
<xsl:template name="OuterTemplate.getCleanURL">
<xsl:param name="path" />
<xsl:choose>
<xsl:when test="contains($path,'/')">
<xsl:value-of select="substring-before($path,'/')" />
<xsl:text>/</xsl:text>
<xsl:call-template name="OuterTemplate.getCleanURL">
<xsl:with-param name="path" select="substring-after($path,'/')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
Executing this stylesheet produces: http://www.site.com/subsite/doclibrary1/
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:template match="/">
<xsl:call-template name="getURL">
<xsl:with-param name="path">http://www.site.com/subsite/doclibrary1/FileName.doc</xsl:with-param>
</xsl:call-template>
</xsl:template>
<xsl:template name="getURL">
<xsl:param name="path" />
<xsl:choose>
<xsl:when test="contains($path,'/')">
<xsl:value-of select="substring-before($path,'/')" />
<xsl:text>/</xsl:text>
<xsl:call-template name="getURL">
<xsl:with-param name="path" select="substring-after($path,'/')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise />
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
The getURL template makes a recursive call to itself when there are "/" characters in the string. While there are still "/" characters, it spits out the values before the slash, and then invokes itself. When it reaches the last one, it stops.
The given solutions are not able to handle url's without filename and extension at the end (Path to folder)
I changed the ideas above to include this aswell...
<xsl:template name="getPath">
<xsl:param name="url" />
<xsl:choose>
<xsl:when test="contains($url,'/')">
<xsl:value-of select="substring-before($url,'/')" />
<xsl:text>/</xsl:text>
<xsl:call-template name="getPath">
<xsl:with-param name="url" select="substring-after($url,'/')" />
</xsl:call-template>
</xsl:when >
<xsl:otherwise>
<xsl:if test="not(contains($url,'.'))">
<xsl:value-of select="$url" />
</xsl:if>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Btw. Why does MS still not support XSLT 2.0!, i saw people complainin bout that back in 2007 -.-'
If you are using XSLT 2.0 (or more specifically, XPath 2.0), then you should be able to use the replace function, using a regular expression to capture the substring before the last "/":
http://www.w3.org/TR/xpath-functions/#func-replace
Unfortunately, "replace" did not exist in XSLT 1.0, so it depends on what XSLT processor you are using as to whether this will work for you.
This stylesheet:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="url">
<xsl:variable name="vReverseUrl">
<xsl:call-template name="reverse"/>
</xsl:variable>
<xsl:call-template name="reverse">
<xsl:with-param name="pString"
select="substring-after($vReverseUrl,'/')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="reverse">
<xsl:param name="pString" select="."/>
<xsl:if test="$pString">
<xsl:call-template name="reverse">
<xsl:with-param name="pString" select="substring($pString,2)"/>
</xsl:call-template>
<xsl:value-of select="substring($pString,1,1)"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
With this input:
<url>http://www.site.com/subsite/doclibrary1/FileName.doc</url>
Output:
http://www.site.com/subsite/doclibrary1
One line XPath 2.0:
string-join(tokenize(url,'/')[position()!=last()],'/')
See my answer to this question and use the same technique (#Alejandro's answer essentially copies this).