Freemind to JSON to Protovis xlst transformation draft - xslt

I have a very simple taxonomy I'm editing in freemind, and want to visualize it in protovis as a sunburst visualisation. The depth of the taxonomy is unknown.
I've produced an attempt to built a XLST transformation that can be used with Freemind's export via xsl script functionality - to output data in the exact JSON format needed by Protovis to produce a sunburst - the idea being no further transforms are needed in javascript.
An example of the output JSON format I'm looking for is here:
http://mbostock.github.com/protovis/ex/sunburst.html
Effectively the freemind .mm file format is the input.
Running my alpha code (shown below) in stylus studio builds up a json format (badly formatted but seems legal) which feeds protovis ok when I save the output generated from stylus studio directly to a .js file manually. For some reason Freemind doesn't seem to export data using this code though...
Is there something I'm missing?
Any help appreciated.
Many thanks, Andrew
===========UPDATE=============
I've corrected the code, the problem was that some of my xsl wasn't supported by the xslt engine used by freemind. I corrected the code and moved it to github under a liberal license and removed it from here.
The adaptor is available here:
https://github.com/minkymorgan/Freemind2JSON#readme
Andrew

Here is my attempt. I based it of your version, and added a few more features.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="/map">
<xsl:text>var Map = {
</xsl:text>
<xsl:apply-templates select="node">
<xsl:with-param name="indent">
<xsl:text> </xsl:text>
</xsl:with-param>
</xsl:apply-templates>
<xsl:text>
};
</xsl:text>
</xsl:template>
<xsl:template match="node">
<xsl:param name="indent"/>
<xsl:if test="position() != 1">
<xsl:text>,
</xsl:text>
</xsl:if>
<xsl:value-of select="$indent"/>
<xsl:text>"</xsl:text>
<xsl:call-template name="escape-javascript">
<xsl:with-param name="string"
select="descendant-or-self::node/#TEXT"/>
</xsl:call-template>
<xsl:text>": </xsl:text>
<xsl:choose>
<xsl:when test="node">
<xsl:text>{
</xsl:text>
<xsl:apply-templates select="node">
<xsl:with-param name="indent">
<xsl:value-of select="$indent"/>
<xsl:text> </xsl:text>
</xsl:with-param>
</xsl:apply-templates>
<xsl:text>
</xsl:text>
<xsl:value-of select="$indent"/>
<xsl:text>}</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>10</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!--
Javascript string escape template by Jeni Tennison
Source: http://holytshirt.blogspot.com/2008/06/xslt-javascript-escaping.html
Author page: http://www.jenitennison.com/
-->
<xsl:template name="escape-javascript">
<xsl:param name="string" />
<xsl:choose>
<xsl:when test='contains($string, "&apos;")'>
<xsl:call-template name="escape-javascript">
<xsl:with-param name="string"
select='substring-before($string, "&apos;")' />
</xsl:call-template>
<xsl:text>\'</xsl:text>
<xsl:call-template name="escape-javascript">
<xsl:with-param name="string"
select='substring-after($string, "&apos;")' />
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($string, '
')">
<xsl:call-template name="escape-javascript">
<xsl:with-param name="string"
select="substring-before($string, '
')" />
</xsl:call-template>
<xsl:text>\n</xsl:text>
<xsl:call-template name="escape-javascript">
<xsl:with-param name="string"
select="substring-after($string, '
')" />
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($string, '\')">
<xsl:value-of select="substring-before($string, '\')" />
<xsl:text>\\</xsl:text>
<xsl:call-template name="escape-javascript">
<xsl:with-param name="string"
select="substring-after($string, '\')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$string" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
If run on the Freemind test file, it produces the following output:
var Map = {
"Notetest": {
"Notetest": 10,
"This is a node": {
"with a linbreak \n subnode": 10,
"and another subnode": 10,
"and some folded subnodes": {
"fold1": 10,
"fold2": 10,
"fold3": 10
}
},
"Attributes": 10
}
};

In case it's of interest ... I've just pushed an XSLT script for converting FreeMind to JSON.
My script is a bit simpler and does not yet support Javascript escaping.
It's also not designed for use in Protovis
https://github.com/tohagan/freemind-to-json

Related

'tokenize()' is an unknown XSLT function

I am using Visual Studio 2015. I want to find the smallest number in the Comma separated list using XSLT.
<EndUnitizeDate>2020-07-13T15:01:43</EndUnitizeDate>
<InternalRecNum>12,3,44,55,66</InternalRecNum>
<LaunchNum>0</LaunchNum> <LeadingSts>900</LeadingSts>
I used tokenize for splitting but I am getting 'tokenize()' is an unknown XSLT function Error.
<xsl:variable name="smallValue" select="s0:WMWDATA/s0:WMFWUpload/s0:Receipts/s0:Receipt/s0:InternalRecNum/text()" />
<xsl:variable name="tokenizedLine" select="tokenize($smallValue, ',')" />
<xsl:for-each select="$tokenizedLine">
<xsl:sort select="." order="descending" />
<xsl:if test="position() = last()">
Smallest: <xsl:value-of select="." />
</xsl:if>
</xsl:for-each>
enter image description here
The tokenize() function requires an XSLT 2.0 processor.
Some XSLT 1.0 processors support tokenizing via en extension function. In pure XSLT 1.0, you need to use a recursive named template.
Here's an example of a template that will both tokenize the input AND find the smallest token:
<xsl:template name="min-token">
<xsl:param name="input"/>
<xsl:param name="prev-min"/>
<xsl:param name="delimiter" select="','"/>
<xsl:variable name="token" select="substring-before(concat($input, $delimiter), $delimiter)" />
<xsl:variable name="min">
<xsl:choose>
<xsl:when test="not($prev-min) or $token < $prev-min">
<xsl:value-of select="$token"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$prev-min"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:choose>
<xsl:when test="contains($input, $delimiter)">
<!-- recursive call -->
<xsl:call-template name="min-token">
<xsl:with-param name="input" select="substring-after($input, $delimiter)"/>
<xsl:with-param name="prev-min" select="$min"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$min"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Demo: https://xsltfiddle.liberty-development.net/aiynfe
If functions like tokenize() are more important to you than using Visual Studio, then this question describes alternatives you could consider:
https://stackoverflow.com/questions/11205268/how-to-use-xslt-2-0-in-visual-studio-2010`

Count unique values in comma separated value in xslt 1.0

I have a node in an XML file:
<TEST_STRING>12,13,12,14</TEST_STRING>
I need to count how many unique numbers/values this string has. For example, in this case there are 2 unique values i.e. 13 and 14.
Honestly speaking i could not build anything yet. It seems it is difficult in XSLT 1.0 but my system only supports 1.0.
Is there any solution for it?
If your processor supports a node-set extension function (either exslt or the Microsoft msxsl one) then you can do it in two steps, first split the string and build an XML fragment with one element per value, then use normal XPath techniques to find the singletons.
Step one can be done with a tail-recursive template:
<xsl:template name="splitString">
<xsl:param name="str"/>
<xsl:choose>
<xsl:when test="contains($str, ',')">
<item><xsl:value-of select="substring-before($str, ',')"/></item>
<xsl:call-template name="splitString">
<xsl:with-param name="str" select="substring-after($str, ',')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<item><xsl:value-of select="$str"/></item>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
In the appropriate place you can call this as
<xsl:variable name="itemsRTF">
<xsl:call-template name="splitString">
<xsl:with-param name="str" select="TEST_STRING"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="items" select="exsl:node-set($itemsRTF)"/>
The items variable now contains a fragment of XML like
<item>12</item>
<item>13</item>
<item>12</item>
<item>14</item>
The next challenge is to find the singletons, which you can do with an expression like
$items/item[not(. = (preceding-sibling::item | following-sibling::item))]
(There are more efficient approaches using a key but for small numbers of items it's probably not worth the bother). So to count the singletons
<xsl:value-of select="count($items/item[not(. = (preceding-sibling::item | following-sibling::item))])"/>
It's possible without nodesets. It's not optimal, of course (hello, Shlemiel The Painter), but it's pretty easy - increment counter only if there is no given string ahead or behind.
Template
<xsl:template name="calcUnique">
<xsl:param name="str"/>
<xsl:param name="back"/>
<xsl:param name="count"/>
<xsl:if test="$str">
<xsl:choose>
<xsl:when test="contains($str, ',')">
<xsl:variable name="part" select="substring-before($str, ',')"/>
<xsl:call-template name="calcUnique">
<xsl:with-param name="str" select="substring-after($str, ',')"/>
<xsl:with-param name="back" select="concat($back, ',', $part)"/>
<xsl:with-param name="count">
<xsl:choose>
<xsl:when test="contains(concat($str, ','), concat(',', $part, ',')) or contains(concat($back, ','), concat(',', $part, ','))">
<xsl:value-of select="$count"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$count + 1"/></xsl:otherwise>
</xsl:choose>
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:choose>
<xsl:when test="contains(concat($back, ','), concat(',', $str, ','))">
<xsl:value-of select="$count"/>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$count + 1"/></xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:if>
</xsl:template>
Sample usage
<xsl:variable name="result">
<xsl:call-template name="calc">
<xsl:with-param name="str" select="TEST_STRING"/>
<xsl:with-param name="back" select="''"/>
<xsl:with-param name="count" select="0"/>
</xsl:call-template>
</xsl:variable>
<xsl:value-of select="$result"/>

Replace & with %26 in XSLT 1.0

I have XML coming back from a search engine with nodes like so
<team>Some team name with &</team>
I need to make a link of the team and while it might not be the optimal way it worked till I discovered that some team names include an ampersand
What I have is (team surrounded by double quotes)
<xsl:variable name="teamend" select="concat(team,'%22')"/>
<a href="{concat('http://site/page.aspx?k=team%3D%22', $teamend)}">
<xsl:call-template name="DisplayCustomField">
<xsl:with-param name="customfield" select="team" />
</xsl:call-template>
but if team contains an ampersand the link will be broken, how can I best fix this?
Thanks in advance
You can use this named template and call it before using the string in team:
<xsl:template name="replace">
<xsl:param name="string"/>
<xsl:param name="substring"/>
<xsl:param name="replacement"/>
<xsl:choose>
<xsl:when test="contains($string, $substring)">
<xsl:value-of select="substring-before($string, $substring)"/>
<xsl:value-of select="$replacement"/>
<xsl:call-template name="replace">
<xsl:with-param name="substring" select="$substring"/>
<xsl:with-param name="replacement" select="$replacement"/>
<xsl:with-param name="string" select="substring-after($string, $substring)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
You can call it like this, and replace any substring with another:
<xsl:variable name="string-with-escaped-ampersand">
<xsl:call-template name="replace">
<xsl:with-param name="string" select="team"/>
<xsl:with-param name="substring">&</xsl:with-param>
<xsl:with-param name="replacement">%26</xsl:with-param>
</xsl:call-template>
</xsl:variable>
And use it in your code:
<xsl:variable name="teamend" select="concat($string-with-escaped-ampersand,'%22')"/>
As #Tomalak pointed out in the comments, since you are generating an URL you might have to deal with several other characters which need to be encoded. There are functions for that in XSLT 2.0, but in XSLT 1.0 you will be limited to templates or extensions.

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).