I have this structure
<ROWS>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>great</good>
<day>month</day>
</ROW>
<ROW>
<TEXT> This is a #good# #day# </TEXT>
<good>Fun</good>
<day>morning</day>
</ROW>
</ROWS>
How do I change that to
<statement> This is a great month, this is a Fun morning </statement>
Using only XSLT 1.0?
The original XML can change tag name. But not the structure! Any ideas?
This seems somewhat similar to creating form letters from a template. Assuming the example is not to be meant literally, you could try something like:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<statements>
<xsl:for-each select="ROWS/ROW/TEXT">
<statement>
<xsl:call-template name="merge">
<xsl:with-param name="string" select="."/>
</xsl:call-template>
</statement>
</xsl:for-each>
</statements>
</xsl:template>
<xsl:template name="merge">
<xsl:param name="string"/>
<xsl:param name="sep" select="'#'"/>
<xsl:choose>
<xsl:when test="contains($string, $sep) and contains(substring-after($string, $sep), $sep)">
<xsl:value-of select="substring-before($string, $sep)" />
<xsl:variable name="placeholder" select="substring-before(substring-after($string, $sep), $sep)" />
<xsl:value-of select="../*[name() = $placeholder]" />
<!-- recursive call -->
<xsl:call-template name="merge">
<xsl:with-param name="string" select="substring-after(substring-after($string, $sep), $sep)" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$string" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Given an input of:
<ROWS>
<ROW>
<TEXT>The quick brown #animal# jumps over the #property# dog.</TEXT>
<animal>fox</animal>
<property>lazy</property>
</ROW>
<ROW>
<TEXT>A journey of a #number# miles #action# with a single #act#.</TEXT>
<number>thousand</number>
<action>begins</action>
<act>step</act>
</ROW>
</ROWS>
the result will be:
<?xml version="1.0" encoding="UTF-8"?>
<statements>
<statement>The quick brown fox jumps over the lazy dog.</statement>
<statement>A journey of a thousand miles begins with a single step.</statement>
</statements>
Look at the XSLT 1.0 spec; find the section on string functions. Study the contains, substring-before, and substring-after functions. The solution to your problem should become clear; if it doesn't, you should at least be able to get far enough on your problem to pose a question that does not look as if it could be paraphrased as "Please do my homework for me."
Related
I have a DMN document that is using <![CDATA["text"]]> to represent string values. I am trying to all the words in an input string against one of the strings in these CDATA sections but I cannot figure out which XPath expression will do the trick.
Here is a sample DMN file:
<definitions xmlns="http://www.omg.org/spec/DMN/20151101/dmn.xsd" id="definitions_0fyde0d"
name="definitions" namespace="http://camunda.org/schema/1.0/dmn">
<decision id="decision" name="TroubleArea">
<decisionTable id="decisionTable">
<input id="input1" label="UserText">
<inputExpression id="inputExpression1" typeRef="string">
<text/>
</inputExpression>
</input>
<output id="output1" label="Subsystem" name="" typeRef="string"/>
<rule id="row-22012340-2">
<inputEntry id="UnaryTests_1hacpom">
<text><![CDATA["signal", "input", "connection"]]></text>
</inputEntry>
<outputEntry id="LiteralExpression_0wvuvyc">
<text><![CDATA["input"]]></text>
</outputEntry>
</rule>
<rule id="row-22012340-3">
<inputEntry id="UnaryTests_0cmpu76">
<text><![CDATA["screen"]]></text>
</inputEntry>
<outputEntry id="LiteralExpression_0hkc81e">
<text><![CDATA["output"]]></text>
</outputEntry>
</rule>
</decisionTable>
</decision>
The input is a single string, which needs to be matched against any string between quotes in the CDATA sections of <inputEntry> elements. When the match is found, I need to return the string in the <outputEntry> of the same <rule>.
After adding the namespace into my XSL, I can match the <decisionTable>, but I am still not getting any matches on any of the strings. Here is the code I am using to check if there are matches at all. This is not getting the <outputEntry> string yet, just "Yes" or "No" to tell me if there is a match at all.
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:dmn="http://www.omg.org/spec/DMN/20151101/dmn.xsd"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" encoding="UTF-8"/>
<xsl:param name="input"/>
<xsl:template match="/">
<result>
<xsl:variable name="table">
<xsl:value-of select="//dmn:decisionTable"/>
</xsl:variable>
<xsl:for-each select="distinct-values(tokenize($input,'%20'))">
<item>
<xsl:value-of select="."/>
<xsl:text>: </xsl:text>
<xsl:call-template name="matchrule">
<xsl:with-param name="text">
<xsl:value-of select="concat('"',.,'"')"/>
</xsl:with-param>
<xsl:with-param name="table">
<xsl:value-of select="$table"/>
</xsl:with-param>
</xsl:call-template>
</item>
</xsl:for-each>
</result>
</xsl:template>
<xsl:template name="matchrule">
<xsl:param name="table"/>
<xsl:param name="text"/>
<xsl:choose>
<xsl:when test="$table//dmn:rule[contains(dmn:inputEntry/dmn:text,$text)]">
<xsl:text>Yes</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>No</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
Testing this with the input string "something%20with%20the%20screen%20or%20the%20screen%20brightness" gives the result:
result xmlns:dmn="http://www.omg.org/spec/DMN/20151101/dmn.xsd">
<item>something: No</item>
<item>with: No</item>
<item>the: No</item>
<item>screen: No</item>
<item>or: No</item>
<item>brightness: No</item>
I cannot change the DMN to not use those <![CDATA]> entries, as the table is created by another tool that I have no control over.
I think one way is to use
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xpath-default-namespace="http://www.omg.org/spec/DMN/20151101/dmn.xsd"
exclude-result-prefixes="xs"
version="2.0">
<xsl:output method="xml" indent="yes" encoding="UTF-8"/>
<xsl:param name="input">something%20with%20the%20screen%20or%20the%20screen%20brightness</xsl:param>
<xsl:template match="/">
<result>
<xsl:variable name="rules" select="//rule"/>
<xsl:for-each select="distinct-values(tokenize($input,'%20'))">
<item>
<xsl:value-of select="."/>
<xsl:text>: </xsl:text>
<xsl:apply-templates select="$rules[inputEntry/text[contains(., concat('"', current(), '"'))]]"/>
</item>
</xsl:for-each>
</result>
</xsl:template>
<xsl:template match="rule">
<xsl:value-of select="outputEntry/text"/>
</xsl:template>
</xsl:stylesheet>
which outputs
<result>
<item>something: </item>
<item>with: </item>
<item>the: </item>
<item>screen: "output"</item>
<item>or: </item>
<item>brightness: </item>
</result>
Online sample http://xsltransform.net/gVhD8RW.
I'm trying to use xsl:key to lookup items in an external XML document, using the XSL document() function. I am able to get the xsl:key part to work if, instead of using document(), I just merge the two XML files (using XmlDocument in C#). However both XML files are very large, and I'm starting to get "out of memory" errors in some cases. Also I need to be able to use xls:key, otherwise the process takes hours.
In XSLT 2.0, I believe you can do something like this:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="lookupDoc" select="document('CodeDescriptions.xml')" />
<xsl:key name="LookupDescriptionByCode" match="Code/#description" use="../#code" />
<xsl:template match="ItemCode">
<xsl:call-template name="MakeSpanForCode">
<xsl:with-param name="code" select="text()" />
</xsl:call-template>
</xsl:template>
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<xsl:value-of select="$lookupDoc/key('LookupDescriptionByCode', $code)" />
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
How do you accomplish this in XSLT 1.0 though?
You have two possibilities:
without key
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<xsl:value-of select="$lookupDoc/*/Code[#code = $code]/#description" />
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
with key
The key definition applies to all documents, but you need to change the context node before using the key() function:
<xsl:template name="MakeSpanForCode">
<xsl:param name="code" />
<xsl:element name="span">
<xsl:attribute name="title">
<!-- trick: change context node to external document -->
<xsl:for-each select="$lookupDoc">
<xsl:value-of select="key('LookupDescriptionByCode', $code)"/>
</xsl:for-each>
</xsl:attribute>
<xsl:value-of select="$code" />
</xsl:element>
</xsl:template>
Also see two great mailing list answers from Mike Kay and Jeni Tennison on this topic
I have a little problem.
A node in my XML may contains and integer, and i have to replace this integer by a string.
Each number match with a string.
For example i have:
Integer - String
1 - TODO
2 - IN PROGRESS
3 - DONE
4 - ERROR
5 - ABORTED
Original XML:
<root>
<status>1</status>
</root>
Converted XML:
<root>
<status>TODO</status>
</root>
So i want replace 1 by "TODO", 2 by "IN PROGRESS" ...
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root/status">
<root>
<status>
<xsl:variable name="text" select="." />
<xsl:choose>
<xsl:when test="contains($text, '1')">
<xsl:value-of select="'TODO'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</status></root>
</xsl:template>
</xsl:stylesheet>
I'am asking if there is another way to do that.
There are a number of ways of doing this. Where the translation is from consecutive integers in the range 1 to N, I would use
<xsl:variable name="index" select="xs:integer(status)"/>
<xsl:value-of select="('TODO', 'IN PROGRESS', 'DONE', 'ERROR', 'ABORTED')[$index]"/>
In other cases where there's a small number of values I might use template rules:
<xsl:template match="status[.='1']" mode="lookup">TODO</xsl:template>
<xsl:template match="status[.='2']" mode="lookup">IN PROGRESS</xsl:template>
etc.
In other cases a lookup table makes sense (note that Dimitre's version with its cumbersome document('') call is designed for XSLT 1.0 - it's considerably simpler if you're using 2.0. When people don't say what version they are using I generally assume 2.0 and Dimitre generally assumes 1.0.)
I'm increasingly seeing people make the mistake of using contains() when they mean "=". If you want to test whether the content of a node is "X", use $node = "X", not contains($node, "X").
One way to do this, is to create a sort of 'look-up' table of values. This could be embedded in the XSLT, or put in a separate file. For example, if you put it in the XSLT file, it would look something like this..
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lookup="lookup">
<lookup:data>
<status code="1">TO DO</status>
<status code="2">IN PROGRESS</status>
<status code="3">DONE</status>
</lookup:data>
Then, you would also create a variable to access this data
<xsl:variable name="lookup" select="document('')/*/lookup:data"/>
Finally, to look up the value, you would simply do this
<xsl:value-of select="$lookup/status[#code = '1']/>
Here is the full XSLT in this case
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:lookup="lookup">
<xsl:output method="xml" indent="yes"/>
<lookup:data>
<status code="1">TO DO</status>
<status code="2">IN PROGRESS</status>
<status code="3">DONE</status>
</lookup:data>
<xsl:variable name="lookup" select="document('')/*/lookup:data"/>
<xsl:template match="status/text()">
<xsl:value-of select="$lookup/status[#code = current()]" />
</xsl:template>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
When applied to your sample XML, the following is output
<root>
<status>TODO</status>
</root>
It could be better to have these in a separate file though, as then they can be re-used in other stylesheets. To do this, just create a file, called 'lookup.xml', and add the XML
<data>
<status code="1">TO DO</status>
<status code="2">IN PROGRESS</status>
<status code="3">DONE</status>
</data>
Note, you don't need namespaces in this case. Then just change the definition of the variable to the following
<xsl:variable name="lookup" select="document('lookup.xml')/data"/>
You have lots of unnecessary code in your solution. The following is a simplified version which works the same way:
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/root/status">
<root>
<status>
<xsl:choose>
<xsl:when test="contains(.,'1')">TODO</xsl:when>
<xsl:otherwise><xsl:value-of select="."/></xsl:otherwise>
</xsl:choose>
</status>
</root>
</xsl:template>
</xsl:stylesheet>
The simplest approach is to start with the identity transform and then add special cases:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="status[. = '1']">
<status>TODO</status>
</xsl:template>
<!-- likewise for status[. = '2'] etc. -->
<!-- copy everything else -->
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
With XSLT Version 3.0 you can use a map type:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable name="MyMap" select="
map {
'1' : 'TODO',
'2' : 'IN PROGRESS',
'3' : 'DONE',
'4' : 'ERROR',
'5' : 'ABORTED'}">
</xsl:variable>
<xsl:template match="/root/status">
<status>
<xsl:variable name="text" select="."/>
<xsl:value-of select="$MyMap( $text )"/>
</status>
</xsl:template>
</xsl:stylesheet>
My word. XSLT is not easy and I don't think it should be made any harder than need be by showing off your knowledge of the inner workings as shown in some of the other answers.
For ease you've hit the nail on the head, use a Choose statement. I'd probably pull it out into a separate templates (I uses these like methods in other languages) simply to ease testing and help clean up your code a little for ease of reading.
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template name="PrintStatus">
<!-- param we can pass a value into or default to the current node -->
<xsl:param name="text" select="." />
<xsl:choose>
<xsl:when test="contains($text, '1')">
<xsl:value-of select="'TODO'"/>
</xsl:when>
<!-- Assume your others go here -->
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="/root/status">
<root>
<status>
<xsl:call-template name="PrintStatus" />
</status>
</root>
</xsl:template>
</xsl:stylesheet>
Keep it simple unless you need the extra complications.
A trick I sometimes use in cases like this is to use a list of values in one string, and take a substring, like this:
<xsl:variable name="statuslist">TODO IN PROGRESSDONE ERROR ABORTED </xsl:variable>
<xsl:template match="status/text()">
<xsl:value-of select="normalize-space(substring($statuslist, ( . - 1 ) * 11 , 11))" />
</xsl:template>
Note, the values in the 'statuslist' are exactly 11 characters apart (the length of your longest value), hence the * 11 and ,11 in your substring. Because you count from 1 not 0, you have to subtract 1 from your index. Alternatively you could pad the variable with 11 spaces at the beginning rather than subtract 1, it's up to you. The normalize-space call just strips the excess spaces from the extracted value.
If you want to make it neater, you could put a separator between each value, and use *12,11 in that substring call instead.
It's not a solution that scales well if you have a large number of possible values, and obviously it needs your possible ids to be in a sequence, but if there's only a few values it's fairly compact.
I'm using XSLT to parse the textual content of an XML element. This text contains newlines, but I can't seem to parse them correctly. I'm using code I found online to chop up the text. Here's the relevant part of the code.
<xsl:variable name="first">
<xsl:value-of select="substring-before($source, $newline)"/>
</xsl:variable>
<xsl:variable name="rest">
<xsl:value-of select="substring-after($source, $newline)"/>
</xsl:variable>
This is part of a recusrive template that pushes $rest into itself.
The problem is that the code sample doesn't define $newline.
If I set $newline to a letter, like 's', the text gets split up just fine (e.g. it will turn the input "resounding" into "re" and "ounding"). But when I try to set $newline to the newline character, that is
or , it recurses forever and gives me a stack overflow. I also tried to define an ENTITY for newline but it makes no difference.
The input has ordinary CR/LF at the end of each line (I'm on a Windows box).
What am I doing wrong?
If you can use EXSLT try with str:tokenize
<xsl:for-each select="str:tokenize($source, $newline)">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
Or similarly with XSLT 2.0:
<xsl:for-each select="tokenize($source, $newline)">
<xsl:sequence select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
You may be able to use the below.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each select="root/str">
<str>
<xsl:call-template name="strSplit">
<xsl:with-param name="str" select="."/>
<xsl:with-param name="seqno" select="1"/>
</xsl:call-template>
</str>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="strSplit">
<xsl:param name="str"/>
<xsl:param name="seqno"/>
<xsl:variable name="afterLeadingWS"
select="substring-after($str, substring-before($str,substring-before(normalize-space($str), ' ')))"/>
<xsl:choose>
<xsl:when test="contains($afterLeadingWS, '
')">
<line>
<xsl:attribute name="seqno"><xsl:value-of select="$seqno"/></xsl:attribute>
<xsl:attribute name="length"><xsl:value-of select="string-length(substring-before($afterLeadingWS, '
'))"/></xsl:attribute>
<xsl:value-of select="substring-before($afterLeadingWS, '
')"/>
</line>
<xsl:call-template name="strSplit">
<xsl:with-param name="str" select="substring-after($afterLeadingWS, '
')"/>
<xsl:with-param name="seqno" select="$seqno + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<line>
<xsl:attribute name="seqno"><xsl:value-of select="$seqno"/></xsl:attribute>
<xsl:value-of select="$afterLeadingWS"/>
</line>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Applied to
<?xml version="1.0" encoding="UTF-8"?>
<root>
<str>
yigifgniuq h
eukwgf kuew hgk.uhgku
,/v.,silghouihhg
</str>
<str>
09734ymmnyr n.0808
o149013483ymr7rg
738924m c0
</str>
</root>
the output result is
<?xml version="1.0" encoding="UTF-8"?>
<root>
<str>
<line seqno="1" length="13">yigifgniuq h </line>
<line seqno="2" length="21">eukwgf kuew hgk.uhgku</line>
<line seqno="3" length="18"> ,/v.,silghouihhg</line>
<line seqno="4"> </line>
</str>
<str>
<line seqno="1" length="18">09734ymmnyr n.0808</line>
<line seqno="2" length="16">o149013483ymr7rg</line>
<line seqno="3" length="11">738924m c0 </line>
<line seqno="4" length="2"> </line>
<line seqno="5"> </line>
</str>
</root>
Note that leading tabs (or blanks) are seen as part of lines.
Maestro13's answer brought me closest, and I ended up merging the template I had with his, to produce this, which I share here for future generations. It's a template that returns the length of the longest line in the string you pass to it.
<xsl:template name="longestCodeLine">
<xsl:param name="str"/>
<xsl:choose>
<!-- Is this the last line? -->
<xsl:when test="contains($str, '
')">
<!-- No. First isolate all remaining lines, and recurse to find its longest line. -->
<xsl:variable name="bestOfTheRest">
<xsl:call-template name="longestCodeLine">
<xsl:with-param name="str" select="substring-after($str, '
')"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<!-- Compare the longest of the remaining lines to this one. Which one's longer? -->
<!-- If the longest of the remaining lines is longer, return that line. -->
<xsl:when test="string-length($bestOfTheRest) > string-length(substring-before($str, '
'))">
<xsl:value-of select="$bestOfTheRest"/>
</xsl:when>
<!-- If this line longer, return this line. -->
<xsl:otherwise>
<xsl:value-of select="substring-before($str, '
')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- If there are no \n's left, this is your last string. So it is by definition the longest one left. -->
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I have used this template once. It is a named template so you can call it where ever you need it. The text here is split up in 70 character pieces:
<xsl:template name="Texts">
<xsl:param name="string" select="TEXTITEM" />
<xsl:param name="line-length" select="70"/>
<xsl:variable name="line" select="substring($string,1,$line-length)"/>
<xsl:variable name="rest" select="substring($string, $line-length+1)"/>
<xsl:if test="$line">
<MYTEXT>
<xsl:value-of select="$line"/>
</MYTEXT>
</xsl:if>
<xsl:if test="$rest">
<xsl:call-template name="Texts">
<xsl:with-param name="string" select="$rest"/>
<xsl:with-param name="line-length" select="$line-length"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Thought I'd add a line splitting code that adds newlines after white space.
<xsl:function name="kode:splitLongLine">
<xsl:param name="string"/>
<xsl:variable name="regex">
<xsl:text>(((.){1,55})( |$))</xsl:text>
</xsl:variable>
<xsl:variable name="result">
<xsl:analyze-string select="$string" regex="{$regex}">
<xsl:matching-substring>
<xsl:value-of select="concat(regex-group(1),'
')"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="concat('REPORT ERROR: ', .)"/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:sequence select="$result"/>
</xsl:function>
I am new to XSLT and have a requirement where in i have to manipulate a string as below.
Input string = "12345"
Output expected ="12345,1234,123,12"
Can anybody help me to achieve this in XSLT 2.0
Here is some XSLT/XPath 2.0 approach:
<xsl:stylesheet
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
exclude-result-prefixes="xs mf"
version="2.0">
<xsl:function name="mf:sub-sequences" as="xs:string*">
<xsl:param name="input" as="xs:string"/>
<xsl:param name="min-length" as="xs:integer"/>
<xsl:sequence select="reverse(
for $length in $min-length to string-length($input)
return substring($input, 1, $length)
)"/>
</xsl:function>
<xsl:template name="main">
<xsl:variable name="s" select="'12345'"/>
<xsl:value-of select="mf:sub-sequences($s, 2)" separator=","/>
</xsl:template>
</xsl:stylesheet>
This should do the trick:
<xsl:template match="/">
<xsl:call-template name="minus-one">
<xsl:with-param name="input" select="'12345'"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="minus-one">
<xsl:param name="input"/>
<xsl:value-of select="$input"/>
<xsl:if test="string-length($input) gt 2"><xsl:text>,</xsl:text>
<xsl:call-template name="minus-one">
<xsl:with-param name="input" select="substring($input, 1, string-length($input) - 1)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Here is a more efficient solution than the currently accepted one that doesn't use the reverse() function:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:my="my:my">
<xsl:output method="text"/>
<xsl:function name="my:subSequences" as="xs:string*">
<xsl:param name="pString" as="xs:string"/>
<xsl:param name="pstartLength" as="xs:integer"/>
<xsl:sequence select=
"for $totalLength in string-length($pString),
$length in 1 to $totalLength -$pstartLength +1,
$revLength in $totalLength -$length +1
return
substring($pString, 1, $revLength)"/>
</xsl:function>
<xsl:template match="/">
<xsl:value-of select="my:subSequences('12345', 2)" separator=","/>
</xsl:template>
</xsl:stylesheet>
When this transformation is executed, the wanted, correct result is produced:
12345,1234,123,12
Explanation:
The XPath 2.0 W3C Spec defines that if the first argument of the to operator is greater than the second argument, then the resulting sequence is the empty sequense.
It is still possible to avoid this limitation and to construct a decreasing integer sequence, like this:
for $k in 0 to $big - $small
return
$big - $k
Using such expression is more efficient, especially for large sequences, than first constructing an increasing sequence and then reversing it with the reverse() function.
on
<string>12345</string>
the following xslt will produce the result 12345,1234,123,12
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="string">
<xsl:call-template name="doTheFunkeyMonkey">
<xsl:with-param name="data" select="."/>
</xsl:call-template>
</xsl:template>
<xsl:template name="doTheFunkeyMonkey">
<xsl:param name="data"/>
<xsl:value-of select="$data"/>
<xsl:if test="string-length($data) > 2">
<xsl:text>,</xsl:text>
<xsl:call-template name="doTheFunkeyMonkey">
<xsl:with-param name="data" select="substring($data,1,string-length($data)-1)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>