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).
Related
I have a XML file following this scheme:
<translationData>
<product>
<attributeValue>
<value>1/4"</value>
<value1>1/4"</value1>
<currentValue>aaaa;bbbb</currentValue>
</attributeValue>
</product>
</translationData>
because of the semicolon in "currentValue" i need to escape the semicolon AND the double quotes in "value".
I am able to escape the semicolon by placing all text in qoutes as following:
XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" encoding="utf-8" />
<xsl:param name="delim" select="';'" />
<xsl:param name="quote" select="'"'" />
<xsl:param name="break" select="'
'" />
<xsl:template match="/">
<xsl:apply-templates select="translationData/product/attributeValue" />
</xsl:template>
<xsl:template match="attributeValue">
<xsl:apply-templates />
<xsl:if test="following::*">
<xsl:value-of select="$break" />
</xsl:if>
</xsl:template>
<xsl:template match="*">
<!-- remove normalize-space() if you want keep white-space at it is -->
<xsl:value-of select="concat($quote, translate(.,'"','\"'), $quote)" />
<xsl:if test="following-sibling::*">
<xsl:value-of select="$delim" />
</xsl:if>
</xsl:template>
<xsl:template match="text()" />
</xsl:stylesheet>
but somehow the Output is:
"1/4\";"1/4\";"aaaa;bbbb"
instead of
"1/4\"";"1/4\"";"aaaa;bbbb"
Where am I going wrong?
I am new to XML and XSLT and did not find any question handling this specific case.
XSLT code is from an answer by #Tomalak for another question. see here
The translate() function will only replace each single character with another single character.
To replace a single character " with a two-character string\" you need to use a named recursive template or - if your processor supports it - an extension function such as EXSLT str:replace().
Here's an example of using a recursive template:
...
<xsl:template match="*">
<xsl:text>"</xsl:text>
<xsl:call-template name="replace">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
<xsl:text>"</xsl:text>
<xsl:if test="position()!=last()">
<xsl:text>;</xsl:text>
</xsl:if>
</xsl:template>
...
<xsl:template name="replace">
<xsl:param name="text"/>
<xsl:param name="searchString">"</xsl:param>
<xsl:param name="replaceString">\"</xsl:param>
<xsl:choose>
<xsl:when test="contains($text,$searchString)">
<xsl:value-of select="substring-before($text,$searchString)"/>
<xsl:value-of select="$replaceString"/>
<!-- recursive call -->
<xsl:call-template name="replace">
<xsl:with-param name="text" select="substring-after($text,$searchString)"/>
<xsl:with-param name="searchString" select="$searchString"/>
<xsl:with-param name="replaceString" select="$replaceString"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
...
I can't find the exact answer for this question so I hope someone will help me here.
I have a string and I want get the substring after the last '.'. I'm using xslt 1.0.
How is this done? This is my code.
<xsl:choose>
<xsl:otherwise>
<xsl:attribute name="class">method txt-align-left case-names</xsl:attribute>
<xsl:value-of select="./#name"/> // this prints a string eg: 'something1.something2.something3'
</xsl:otherwise>
</xsl:choose>
When i paste the suggested code I get an error message. "Parsing an XSLT stylesheet failed."
I can't think of a way to do this with a single expression in XSLT 1.0, but you can do it with a recursive template:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/">
<n>
<xsl:call-template name="GetLastSegment">
<xsl:with-param name="value" select="'something1.something2.something3'" />
<xsl:with-param name="separator" select="'.'" />
</xsl:call-template>
</n>
</xsl:template>
<xsl:template name="GetLastSegment">
<xsl:param name="value" />
<xsl:param name="separator" select="'.'" />
<xsl:choose>
<xsl:when test="contains($value, $separator)">
<xsl:call-template name="GetLastSegment">
<xsl:with-param name="value" select="substring-after($value, $separator)" />
<xsl:with-param name="separator" select="$separator" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Result:
<n>something3</n>
I did the same behaviour with a xsl:function - usage is then a little bit simpler:
<xsl:function name="ns:substring-after-last" as="xs:string" xmlns:ns="yourNamespace">
<xsl:param name="value" as="xs:string?"/>
<xsl:param name="separator" as="xs:string"/>
<xsl:choose>
<xsl:when test="contains($value, $separator)">
<xsl:value-of select="ns:substring-after-last(substring-after($value, $separator), $separator)" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$value" />
</xsl:otherwise>
</xsl:choose>
</xsl:function>
And you can call it directly in a value-of:
<xsl:value-of select="ns:substring-after-last(.,'=')" xmlns:ns="yourNamespace"/>
Here is a solution using EXSLT str:tokenize:
<xsl:if test="substring($string, string-length($string)) != '.'"><xsl:value-of select="str:tokenize($string, '.')[last()]" /></xsl:if>
(the if is here because if your string ends with the separator, tokenize won't return an empty string)
I resolved it
<xsl:call-template name="GetLastSegment">
<xsl:with-param name="value" select="./#name" />
</xsl:call-template>
Did not need the
<xsl:with-param name="separator" value="'.'" />
in the template call
I have been attempting to perform multiple (different) string replacement with recursion and I have hit a roadblock. I have sucessfully gotten the first replacement to work, but the subsequent replacements never fire. I know this has to do with the recursion and how the with-param string is passed back into the call-template. I see my error and why the next xsl:when never fires, but I just cant seem to figure out exactly how to pass the complete modified string from the first xsl:when to the second xsl:when. Any help is greatly appreciated.
<xsl:template name="replace">
<xsl:param name="string" select="." />
<xsl:choose>
<xsl:when test="contains($string, '
')">
<xsl:value-of select="substring-before($string, '
')" />
<br/>
<xsl:call-template name="replace">
<xsl:with-param name="string" select="substring-after($string, '
')"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($string, 'TXT')">
<xsl:value-of select="substring-before($string, '
TXT')" />
<xsl:call-template name="replace">
<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>
This transformation is fully parameterized and doesn't need any tricks with default namespaces:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<my:params xml:space="preserve">
<pattern>
<old>
</old>
<new><br/></new>
</pattern>
<pattern>
<old>quick</old>
<new>slow</new>
</pattern>
<pattern>
<old>fox</old>
<new>elephant</new>
</pattern>
<pattern>
<old>brown</old>
<new>white</new>
</pattern>
</my:params>
<xsl:variable name="vPats"
select="document('')/*/my:params/*"/>
<xsl:template match="text()" name="multiReplace">
<xsl:param name="pText" select="."/>
<xsl:param name="pPatterns" select="$vPats"/>
<xsl:if test=
"string-length($pText) >0">
<xsl:variable name="vPat" select=
"$vPats[starts-with($pText, old)][1]"/>
<xsl:choose>
<xsl:when test="not($vPat)">
<xsl:copy-of select="substring($pText,1,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$vPat/new/node()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="multiReplace">
<xsl:with-param name="pText" select=
"substring($pText, 1 + not($vPat) + string-length($vPat/old/node()))"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when it is applied on this XML document:
<t>The quick
brown fox</t>
the wanted, correct result is produced:
The slow<br/>white elephant
Explanation:
The text is scanned from left to right and at any position, if the remaining string starts with one of the specified patterns, then the starting substring is replaced by the replacement specified for the firat matching patterns.
Do note: If we have search patterns:
"relation" --> "mapping"
"corelation" --> "similarity"
in the above order, and text:
"corelation"
then this solution produces the more correct result:
"similarity"
and the currently accepted solution by #Alejandro) produces:
"comapping"
Edit: With a small update we get another improvement: If at a given location more than one replace is possible, we perform the longest replace.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes"/>
<xsl:strip-space elements="*"/>
<my:params xml:space="preserve">
<pattern>
<old>
</old>
<new><br/></new>
</pattern>
<pattern>
<old>quick</old>
<new>slow</new>
</pattern>
<pattern>
<old>fox</old>
<new>elephant</new>
</pattern>
<pattern>
<old>brown</old>
<new>white</new>
</pattern>
</my:params>
<xsl:variable name="vrtfPats">
<xsl:for-each select="document('')/*/my:params/*">
<xsl:sort select="string-length(old)"
data-type="number" order="descending"/>
<xsl:copy-of select="."/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vPats" select=
"ext:node-set($vrtfPats)/*"/>
<xsl:template match="text()" name="multiReplace">
<xsl:param name="pText" select="."/>
<xsl:param name="pPatterns" select="$vPats"/>
<xsl:if test= "string-length($pText) >0">
<xsl:variable name="vPat" select=
"$vPats[starts-with($pText, old)][1]"/>
<xsl:choose>
<xsl:when test="not($vPat)">
<xsl:copy-of select="substring($pText,1,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$vPat/new/node()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="multiReplace">
<xsl:with-param name="pText" select=
"substring($pText,
1 + not($vPat) + string-length($vPat/old/node())
)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Thus, if we have two reps such as "core" --> "kernel" and "corelation" --> "similarity", The second would be used for a text containing the word "corelation", regardless of how the reps are ordered.
This stylesheet shows a verbose solution just for you to learn the pattern:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()" name="replace">
<xsl:param name="pString" select="string()"/>
<xsl:param name="pSearch" select="'THIS'"/>
<xsl:param name="pReplace" select="'THAT'"/>
<xsl:choose>
<xsl:when test="contains($pString, '
')">
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-before($pString, '
')"/>
</xsl:call-template>
<br/>
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-after($pString, '
')"/>
</xsl:call-template>
</xsl:when>
<xsl:when test="contains($pString, $pSearch)">
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-before($pString, $pSearch)"/>
</xsl:call-template>
<xsl:value-of select="$pReplace"/>
<xsl:call-template name="replace">
<xsl:with-param
name="pString"
select="substring-after($pString, $pSearch)"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$pString"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
With this input:
<t>THIS is a test.
But THAT is not.
THIS is also a test.</t>
Output:
<t>THAT is a test.<br />But THAT is not.<br />THAT is also a test.</t>
EDIT: Full parameterized solution.
<stylesheet version="1.0" xmlns="http://www.w3.org/1999/XSL/Transform">
<param name="pMap">
<s t="
" xmlns=""><br/></s>
<s t="THIS" xmlns="">THAT</s>
</param>
<template match="node()|#*">
<copy>
<apply-templates select="node()|#*"/>
</copy>
</template>
<template match="text()" name="replace">
<param name="pString" select="string()"/>
<param name="pSearches"
select="document('')/*/*[#name='pMap']/s"/>
<param name="vMatch" select="$pSearches[contains($pString,#t)][1]"/>
<choose>
<when test="$vMatch">
<call-template name="replace">
<with-param
name="pString"
select="substring-before($pString, $vMatch/#t)"/>
</call-template>
<copy-of select="$vMatch/node()"/>
<call-template name="replace">
<with-param
name="pString"
select="substring-after($pString, $vMatch/#t)"/>
</call-template>
</when>
<otherwise>
<value-of select="$pString"/>
</otherwise>
</choose>
</template>
</stylesheet>
Output:
<t>THAT is a test.<br/>But THAT is not.<br/>THAT is also a test.</t>
Note: There is a problem when using inline data in XML 1.0: you can't reset prefixed namespace declaration as in XML 1.1. The solution is to use a not common but valid notation: declare XSLT namespace as default namespace.
The problem may stem from differences in the encoding of newline, causing the XSLT processor not to recognize the CRLF in your match strings. I suggest testing with using comma in place of newline. The following will give you the expected result when called with the parameter "abc,def,ghi":
<xsl:template name="replace">
<xsl:param name="string" select="." />
<xsl:choose>
<xsl:when test="contains($string, ',')">
<xsl:value-of select="substring-before($string, ',')" />
<br/>
<xsl:call-template name="replace">
<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>
I modified the Dimitrie answer to put his solution in a template and using an exsl extension. Please check it, may be can be useful for someone.
<?xml version='1.0' ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:a="http://www.tralix.com/cfd/2"
extension-element-prefixes="exsl">
<xsl:output indent="yes"/>
<xsl:template match="/*">
<xsl:variable name="replacesList">
<replaces>
<replace><old>01</old><new>01 - Efectivo</new></replace>
<replace><old>02</old><new>02 - Cheque nominativo</new></replace>
<replace><old>03</old><new>03 - Transferencia electrónica de fondos</new></replace>
<replace><old>04</old><new>04 - Tarjeta de Crédito</new></replace>
<replace><old>05</old><new>05 - Monedero Electrónico</new></replace>
<replace><old>06</old><new>06 - Dinero electrónico</new></replace>
<replace><old>08</old><new>08 - Vales de despensa</new></replace>
<replace><old>28</old><new>28 - Tarjeta de Débito</new></replace>
<replace><old>29</old><new>29 - Tarjeta de Servicio</new></replace>
<replace><old>99</old><new>99 - Otros</new></replace>
</replaces>
</xsl:variable>
<descripcionMetodoDePago>
<xsl:call-template name="replaces">
<xsl:with-param name="text" select="text"/>
<xsl:with-param name="replaces">
<xsl:copy-of select="exsl:node-set($replacesList/*/*)"/>
</xsl:with-param>
</xsl:call-template>
</descripcionMetodoDePago>
</xsl:template>
<xsl:template name="replaces">
<xsl:param name="text"/>
<xsl:param name="replaces"/>
<xsl:if test="$text!=''">
<xsl:variable name="replace" select="$replaces/*[starts-with($text, old)][1]"/>
<xsl:choose>
<xsl:when test="not($replace)">
<xsl:copy-of select="substring($text,1,1)"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="$replace/new/node()"/>
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="replaces">
<xsl:with-param name="text" select=
"substring($text, 1 + not($replace) + string-length($replace/old/node()))"/>
<xsl:with-param name="replaces" select="$replaces"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
Although this question was asked (and answered) several years ago, neither this answer nor the (many!) other variants I found while searching the 'net over the last couple of days were able to do what I needed: replace multiple strings in nodes which may contain several kb of text.
Dimitre's version works well when nodes contain very little text, but when I tried to use it I almost immediately fell foul of the dreaded stack overflow (recursive calls, remember!) The problem with Dimitre's solution is that it tries to match the search patterns to the beginning of the text. This means that many (recursive) calls are made, each call using the right-most n-1 characters of the original text. For a 1k text that means over 1000 recursive calls!
After digging around for alternatives I came across an example by Ibrahim Naji (http://thinknook.com/xslt-replace-multiple-strings-2010-09-07/) which uses the more conventional substring-before/substring-after combination to perform the replacement. However, that code is limited to a single replacement string for any number of search strings.
I decided, therefore, that it was time to actually get my hands dirty (and learn XSLT at the same time!) The result is the following code which performs multiple string replacements (specified via an internal template, but that could easily be replaced with an external file, for example), and which (so far in my tests) doesn't suffer from excessive recursive calls.
It should be noted that the replacements are very basic (as are most other existing implementations) meaning that no attempts are made to only match entire words, for example. I hope the comments are enough to explain the way it works, particularly for other XSLT beginners (like myself).
And now the code...
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
xmlns:dps="dps:dps">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<!--
The original version of this code was published by Ibrahim Naji (http://thinknook.com/xslt-replace-multiple-strings-2010-09-07/).
It works but suffered the limitation of only being able to supply a single replacement text. An alternative implementation, which
did allow find/replace pairs to be specified, was published by Dimitre Novatchev
(https://stackoverflow.com/questions/5213644/xslt-multiple-string-replacement-with-recursion).
However, that implementation suffers from stack overflow problems if the node contains more than a few hundred bytes of text (and
in my case I needed to process nodes which could include several kb of data). Hence this version which combines the best features
of both implementations.
John Cullen, 14 July 2017.
-->
<!-- IdentityTransform, copy the input to the output -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- Process all text nodes. -->
<xsl:template match="text()">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="."/>
</xsl:call-template>
</xsl:template>
<!-- Table of replacement patterns -->
<xsl:variable name="vPatterns">
<dps:patterns>
<pattern>
<old><i></old>
<new><em></new>
</pattern>
<pattern>
<old></i></old>
<new></em></new>
</pattern>
<pattern>
<old><b></old>
<new><strong></new>
</pattern>
<pattern>
<old></b></old>
<new></strong></new>
</pattern>
</dps:patterns>
</xsl:variable>
<!--
Convert the internal table into a node-set. This could also be done via a call to document()
for example select="document('')/*/myns:params/*" with a suitable namespace declaration, but
in my case that was not possible because the code is being used in with a StreamSource.
-->
<xsl:variable name="vPats" select="exsl:node-set($vPatterns)/dps:patterns/*"/>
<!-- This template matches all text() nodes, and calls itself recursively to performs the actual replacements. -->
<xsl:template name="string-replace-all">
<xsl:param name="text"/>
<xsl:param name="pos" select="1"/>
<xsl:variable name="replace" select="$vPats[$pos]/old"/>
<xsl:variable name="by" select="$vPats[$pos]/new"/>
<xsl:choose>
<!-- Ignore empty strings -->
<xsl:when test="string-length(translate(normalize-space($text), ' ', '')) = 0">
<xsl:value-of select="$text"/>
</xsl:when>
<!-- Return the unchanged text if the replacement is larger than the input (so no match possible) -->
<xsl:when test="string-length($replace) > string-length($text)">
<xsl:value-of select="$text"/>
</xsl:when>
<!-- If the current text contains the next pattern -->
<xsl:when test="contains($text, $replace)">
<!-- Perform a recursive call, each time replacing the next occurrence of the current pattern -->
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="concat(substring-before($text,$replace),$by,substring-after($text,$replace))"/>
<xsl:with-param name="pos" select="$pos"/>
</xsl:call-template>
</xsl:when>
<!-- No (more) matches found -->
<xsl:otherwise>
<!-- Bump the counter to pick up the next pattern we want to search for -->
<xsl:variable name="next" select="$pos+1"/>
<xsl:choose>
<!-- If we haven't finished yet, perform a recursive call to process the next pattern in the list. -->
<xsl:when test="boolean($vPats[$next])">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="$text"/>
<xsl:with-param name="pos" select="$next"/>
</xsl:call-template>
</xsl:when>
<!-- No more patterns, we're done. Return the fully processed text. -->
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
There is XML document:
<data>how;many;i;can;tell;you</data>
Need to get XML using XSLT version 1:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
How I can do it?
UPDATE:
Output format must be XML.
This recursive solution is probably one of the shortest possible:
<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="data">
<manydata><xsl:apply-templates/></manydata>
</xsl:template>
<xsl:template match="text()" name="tokenize">
<xsl:param name="pText" select="."/>
<xsl:if test="string-length($pText)">
<onedata>
<xsl:value-of select=
"substring-before(concat($pText,';'),';')"/>
</onedata>
<xsl:call-template name="tokenize">
<xsl:with-param name="pText" select=
"substring-after($pText,';')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document;
<data>how;many;i;can;tell;you</data>
the wanted, correct result is produced:
<manydata>
<onedata>how</onedata>
<onedata>many</onedata>
<onedata>i</onedata>
<onedata>can</onedata>
<onedata>tell</onedata>
<onedata>you</onedata>
</manydata>
<xsl:template match="data">
<manydata>
<!--
empty <manydata/> will be generated,
if <data/> without child text()
-->
<xsl:apply-templates select="text()"/>
</manydata>
</xsl:template>
<xsl:template match="data/text()">
<!-- start point for recursion -->
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="';'"/>
<xsl:with-param name="string" select="text()"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="tokenize-string">
<xsl:param name="separator"/>
<xsl:param name="string"/>
<xsl:variable name="string-before-separator"
select="substring-before( $string, $separator )"/>
<onedata>
<xsl:choose>
<!-- if $separator presents in $string take first piece -->
<xsl:when test="$string-before-separator">
<xsl:value-of select="$string-before-separator"/>
</xsl:when>
<!-- take whole $string, if no $separator in the $string -->
<xsl:otherwise>
<xsl:value-of select="$string"/>
</xsl:otherwise>
</xsl:choose>
</onedata>
<!-- recursive call, if separator was found -->
<xsl:if test="$string-before-separator">
<xsl:call-template name="tokenize-string">
<xsl:with-param name="separator" select="$separator"/>
<xsl:with-param name="string"
select="substring-after( $string, $separator )"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Try this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="data">
<xsl:element name="manydata">
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="text()" />
</xsl:call-template>
</xsl:element>
</xsl:template>
<xsl:template name="splitsemicolons">
<xsl:param name="text" />
<xsl:choose>
<xsl:when test="contains($text,';')">
<xsl:element name="onedata">
<xsl:value-of select="substring-before($text,';')" />
</xsl:element>
<xsl:call-template name="splitsemicolons">
<xsl:with-param name="text" select="substring-after($text,';')" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:element name="onedata">
<xsl:value-of select="$text" />
</xsl:element>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
This uses a named template that is called recursively, each time outputting what's before the first ;, and calling itself with everything after the first ;. If there isn't a ;, it just outputs whatever's left as-is.
You can use the XSLT extension library to get the tokenize function. Here is how the final code will look like:
<xsl:template match="/">
<manydata>
<xsl:for-each select="str:tokenize(data, ';')">
<xsl:value-of select="." />
</xsl:for-each>
</manydata>
</xsl:template>
</xsl:stylesheet>
Please note you will have to import the extension library into you XSLT using:
<xsl:import href="path/str.xsl" />
before you use the library functions.
I need to check if a particular string contains a a particular word for example to check if,
SultansOfSwing contains the word Swing.
Let me also mention that the value of the string in question is unknown. As in it can be any word so we do not know the length et cetera.
I understand I can do this by using the contains keyword.
But once I know that this word contains the Swing keyword I want to display the string without this "Swing" word.. thus effectively displaying only "SultansOf".
I have been trying to explore how I can achieve this but not getting any break through.
Could somebody please advise which keyword or function will provide this facility ? How can I remove a particular word from within a string.
Given this for input:
<root>
<song>SultansOfSwing</song>
<song>SwingOfSultans</song>
<song>SultansSwingOf</song>
</root>
The output of this:
<?xml version='1.0' ?>
<root>
<swing-less-long>SultansOf</swing-less-long>
<swing-less-long>OfSultans</swing-less-long>
<swing-less-long>SultansOf</swing-less-long>
</root>
Can be gotten from this. Note the use of substring-before and substring-after.
<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:template match="/">
<root>
<xsl:apply-templates select="root/song"/>
</root>
</xsl:template>
<xsl:template match="song">
<swing-less-long>
<xsl:if test="contains(., 'Swing')">
<xsl:call-template name="remove">
<xsl:with-param name="value" select="."/>
</xsl:call-template>
</xsl:if>
</swing-less-long>
</xsl:template>
<xsl:template name="remove">
<xsl:param name="value"/>
<xsl:value-of select="concat(substring-before($value, 'Swing'), substring-after($value, 'Swing'))"/>
</xsl:template>
</xsl:stylesheet>
I think this string replacement function is quite exhaustive:
EDIT - needed to change $string to $string2. Should work now
<xsl:template name="string-replace">
<xsl:param name="string1" select="''" />
<xsl:param name="string2" select="''" />
<xsl:param name="replacement" select="''" />
<xsl:param name="global" select="true()" />
<xsl:choose>
<xsl:when test="contains($string1, $string2)">
<xsl:value-of select="substring-before($string1, $string2)" />
<xsl:value-of select="$replacement" />
<xsl:variable name="rest" select="substring-after($string1, $string2)" />
<xsl:choose>
<xsl:when test="$global">
<xsl:call-template name="string-replace">
<xsl:with-param name="string1" select="$rest" />
<xsl:with-param name="string2" select="$string2" />
<xsl:with-param name="replacement" select="$replacement" />
<xsl:with-param name="global" select="$global" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$rest" />
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string1" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
It's case-sensitive, mind you. In your case:
<xsl:call-template name="string-replace">
<xsl:with-param name="string1" select="'SultansOfSwing'" />
<xsl:with-param name="string2" select="'Swing'" />
<xsl:with-param name="replacement" select="''" />
</xsl:call-template>