XSLT: replace an integer by a string - string

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.

Related

What's the best way to move space-delimited tokens from one attribute to another in XSLT-2.0?

I'm trying to move space-delimited tokens from one attribute to another in XSLT-2.0. For example, given
<!-- SOURCE DOCUMENT -->
<?xml version="1.0" encoding="UTF-8"?>
<root>
<p class="foo"/>
<p class="foo bar baz"/>
<p class="foo bar baz" outputclass="BAR"/>
<p class="foo bar baz" outputclass="BAR HELLO"/>
</root>
I need to move #class="foo" to #outputclass="FOO" and #class="bar" to #outputclass="BAR", deleting the source attribute if it becomes empty and augmenting the target attribute if it exists (simple token-set operations):
<!-- RESULTING DOCUMENT -->
<?xml version="1.0" encoding="UTF-8"?>
<root>
<p outputclass="FOO"/>
<p class="baz" outputclass="FOO BAR"/>
<p class="baz" outputclass="FOO BAR"/>
<p class="baz" outputclass="FOO BAR HELLO"/>
</root>
I think I have everything figured out except the actual token-moving part. Every direction I go down ends up complicated and broken, and I feel like XSLT-2.0 surely has a simple approach that I'm missing.
Here's what I have so far:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mine="mine:local"
exclude-result-prefixes="xs"
version="2.0">
<!-- baseline identity transform -->
<!-- (for non-elements - attributes, whitespace PCDATA, etc.) -->
<xsl:template match="#*|(node() except *)">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<!-- for element nodes, remap attributes then copy element -->
<xsl:template match="*">
<!-- get original attribute sequence -->
<xsl:variable name="atts1" select="#*"/>
<!-- use our function to remap two attribute tokens -->
<xsl:variable name="atts2" select="mine:remap($atts1, 'class', 'foo', 'outputclass', 'FOO')"/>
<xsl:variable name="atts3" select="mine:remap($atts2, 'class', 'bar', 'outputclass', 'BAR')"/>
<!-- stuff updated attribute sequence into element -->
<xsl:copy>
<xsl:sequence select="$atts3"/>
<xsl:apply-templates select="node()"/>
</xsl:copy>
</xsl:template>
<!-- remap #from_att~="$from_token" to #to_att~="$to_token" -->
<xsl:function name="mine:remap">
<xsl:param name="orig_atts"/>
<xsl:param name="from_att"/>
<xsl:param name="from_token"/>
<xsl:param name="to_att"/>
<xsl:param name="to_token"/>
<!-- ******** TOKEN-MOVING MAGIC!?! ******** -->
<xsl:sequence select="$orig_atts"/>
</xsl:function>
</xsl:stylesheet>
Basically I need to figure out how TOKEN-MOVING MAGIC!?! can move a single token (including deletion of empty "from" attributes). I've searched quite a bit but I haven't seen this particular problem covered.
Edit: The number and names of attributes to remap can be anything, and their values are case-sensitive. It's the magic inside the mine:remap function to remap a single value in an attribute sequence that I'm looking for.
Edit: The reason for approaching attribute modification with a function is that we have a number of different token remappings to apply to different files, and I hoped to allow our non-XSLT-savvy users to easily adjust the remappings to their needs. I was unable to figure out how to provide similar generalization with a template-matching-based approach.
Thanks!
Here is a short XSLT 2.0 solution (just 26 lines):
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p/#class[tokenize(., ' ') = ('foo', 'bar')]">
<xsl:if test="tokenize(., ' ')[not(. = ('foo', 'bar'))]">
<xsl:attribute name="class"
select="string-join(tokenize(., ' ')[not(. = ('foo', 'bar'))], ' ')"/>
</xsl:if>
<xsl:attribute name="outputclass" select=
"upper-case(string-join(
(
tokenize(., ' ')[. = ('foo', 'bar')],
tokenize(../#outputclass, ' ')
[not(lower-case(.) = tokenize(current(), ' '))]
),
' '
)
)"/>
</xsl:template>
<xsl:template match="p/#outputclass[../#class[tokenize(., ' ') = ('foo', 'bar')]]"/>
</xsl:stylesheet>
When this transformation is applied on the provided XML document:
<root>
<p class="foo"/>
<p class="foo bar baz"/>
<p class="foo bar baz" outputclass="BAR"/>
<p class="foo bar baz" outputclass="BAR HELLO"/>
</root>
the wanted, correct result is produced:
<root>
<p outputclass="FOO"/>
<p class="baz" outputclass="FOO BAR"/>
<p class="baz" outputclass="FOO BAR"/>
<p class="baz" outputclass="FOO BAR HELLO"/>
</root>
Update:
Here is the same transformation with almost everything parameterized, as requested in a comment by the OP, just 32 lines:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pfromName" select="'class'"/>
<xsl:param name="ptoName" select="'outputclass'"/>
<xsl:param name="pTokens" select="'foo', 'bar'"/>
<xsl:param name="pnewNames" select="'FOO', 'BAR'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p/#*[name() = $pfromName][tokenize(., ' ') = $pTokens]">
<xsl:if test="tokenize(., ' ')[not(. = $pTokens)]">
<xsl:attribute name="{$pfromName}"
select="string-join(tokenize(., ' ')[not(. = $pTokens)], ' ')"/>
</xsl:if>
<xsl:attribute name="{$ptoName}" select=
"upper-case(string-join(
(
tokenize(., ' ')[. = $pTokens],
tokenize(../#*[name()=$ptoName], ' ')
[not(lower-case(.) = tokenize(current(), ' '))]
),
' '
)
)"/>
</xsl:template>
<xsl:template
match="p/#*[name()=$ptoName][../#*[name()=$pfromName][tokenize(., ' ') = $pTokens]]"/>
</xsl:stylesheet>
Update2:
Here is a completely parameterized XSLT 2.0 transformation (not using the upper-case() and lower-case() functions), just 37 lines:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:param name="pfromName" select="'class'"/>
<xsl:param name="ptoName" select="'outputclass'"/>
<xsl:param name="pTokens" select="'foo', 'bar'"/>
<xsl:param name="pnewNames" select="'FOO', 'BAR'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="p/#*[name() = $pfromName][tokenize(., ' ') = $pTokens]">
<xsl:if test="tokenize(., ' ')[not(. = $pTokens)]">
<xsl:attribute name="{$pfromName}"
select="string-join(tokenize(., ' ')[not(. = $pTokens)], ' ')"/>
</xsl:if>
<xsl:attribute name="{$ptoName}" select=
"string-join(
distinct-values(
(for $token in tokenize(., ' ')[. = $pTokens],
$n in 1 to count($pTokens),
$ind in $n[$token eq $pTokens[$n]]
return $pnewNames[$ind]
,
tokenize(../#*[name()=$ptoName], ' ')
)
),
' '
)
"/>
</xsl:template>
<xsl:template
match="p/#*[name()=$ptoName][../#*[name()=$pfromName][tokenize(., ' ') = $pTokens]]"/>
</xsl:stylesheet>
In the following sample I have tried to delegate as much as possible to templates:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:param name="tokens" as="xs:string*"
select="'foo', 'bar'"/>
<xsl:param name="collation" as="xs:string">http://www.w3.org/2005/xpath-functions/collation/html-ascii-case-insensitive</xsl:param>
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="*[#class][exists($tokens[contains-token(current()/#class, ., $collation)])]">
<xsl:copy>
<xsl:variable name="new-att" as="attribute()">
<xsl:attribute name="outputclass"/>
</xsl:variable>
<xsl:apply-templates select="#*, $new-att[not(current()/#outputclass)]">
<xsl:with-param name="tokens-found"
select="$tokens[contains-token(current()/#class, ., $collation)]"/>
</xsl:apply-templates>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="#class">
<xsl:param name="tokens-found"/>
<xsl:variable name="remaining-tokens" select="tokenize(., ' ')[not(. = $tokens-found)]"/>
<xsl:if test="exists($remaining-tokens)">
<xsl:attribute name="{name()}" select="$remaining-tokens"/>
</xsl:if>
</xsl:template>
<xsl:template match="#outputclass">
<xsl:param name="tokens-found"/>
<xsl:variable name="new-tokens" select="$tokens-found[not(contains-token(current(), ., $collation))]"/>
<xsl:attribute name="{name()}" select="$new-tokens, ."/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/bEzkTcx/1
I haven't implemented the upper-case transformation of the tokens to be moved, I guess it should be easy to add that.
The code uses XSLT 3 with XPath 3 and the function https://www.w3.org/TR/xpath-functions/#func-contains-token but it has a definition in the spec that you could use in a user-defined XSLT 2 function. It is of course also easy to not declare the identity transformation using xsl:mode but by spelling it out.
XSLT 3 is available with Saxon 9.8 or later for Java and .NET, with Saxon-C for C/C++, with bindings for PHP and Python and with Saxon-JS 2 inside of modern web browsers and for Node.js.
Here is what I ended up with for the mine:remap() function:
<!-- remap #from_att~="$from_token" to #to_att~="$to_token" -->
<xsl:function name="mine:remap">
<xsl:param name="orig_atts" as="attribute()*"/>
<xsl:param name="from_att"/>
<xsl:param name="from_token"/>
<xsl:param name="to_att"/>
<xsl:param name="to_token"/>
<!-- get tokenized list of values of "from" attributes -->
<xsl:variable name="from_att_values" select="tokenize($orig_atts[name() = $from_att], ' ')"/>
<xsl:choose>
<!-- does the "from" attribute contain our value to replace? -->
<xsl:when test="$from_att_values = $from_token">
<!-- if so, iterate through attributes to preserve their order -->
<xsl:for-each select="$orig_atts">
<xsl:choose>
<!-- if "from" and "to" attributes are the same, replace $from_token with $to_token in-place -->
<xsl:when test="(name(.) = $from_att) and ($from_att = $to_att)">
<xsl:attribute name="{name(.)}" select="for $t in $from_att_values
return ($t[$t != $from_token], $to_token[$t = $from_token])"/>
</xsl:when>
<!-- if "from" attribute, define with $from_token value removed -->
<xsl:when test="name(.) = $from_att">
<xsl:variable name="new_from_att_values" select="$from_att_values[not(. = $from_token)]"/>
<xsl:if test="count($new_from_att_values) > 0">
<xsl:attribute name="{$from_att}" select="$new_from_att_values"/>
</xsl:if>
</xsl:when>
<!-- if "to" attribute, define with $to_token value added -->
<xsl:when test="name(.) = $to_att">
<xsl:attribute name="{$to_att}" select="distinct-values((tokenize(., ' '), $to_token))"/>
</xsl:when>
<xsl:otherwise>
<xsl:copy/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
<!-- if there was no "from" attribute to modify above, create it here -->
<xsl:if test="not($orig_atts[name() = $to_att])">
<xsl:attribute name="{$to_att}" select="$to_token"/>
</xsl:if>
</xsl:when>
<!-- if not, return original attributes -->
<xsl:otherwise>
<xsl:sequence select="$orig_atts"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
I iterate through the attributes to preserve their order, then I use xsl:choose to handle the from (remove a token), to (add a token), or other (copy) attributes.

Xslt Concatenate two nodelists with seperator

I am trying to concatenate two list of nodes which have child elements with a string-join but I am loosing the xml tags in the child elements
Input:
node1: hello I am trying <abc> some </abc> xslt code
node2: but not working
expected output
hello I am trying <abc> some </abc> xslt code, but not working
If you have a sequence of two element nodes containing the content you have shown and you want to create an output with a separator , between those nodes then one way would be to push the elements through a template that adds the separator:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="#all"
version="3.0">
<xsl:mode on-no-match="shallow-copy"/>
<xsl:template match="root">
<xsl:param name="seq1" select="node1, node2"/>
<xsl:copy>
<xsl:apply-templates select="$seq1"/>
</xsl:copy>
</xsl:template>
<xsl:template match="node1 | node2">
<xsl:if test="position() > 1">, </xsl:if>
<xsl:apply-templates/>
</xsl:template>
</xsl:stylesheet>
https://xsltfiddle.liberty-development.net/pPJ8LVh
If you want to work on a string level with string-join then you first need to serialize the contents e.g.
<xsl:output method="text"/>
<xsl:template match="root">
<xsl:param name="seq1" select="node1, node2"/>
<xsl:copy>
<xsl:value-of select="$seq1 ! serialize(node())" separator=", "/>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/pPJ8LVh/1
<xsl:output method="text"/>
<xsl:template match="root">
<xsl:param name="seq1" select="node1, node2"/>
<xsl:variable name="str1" select="string-join($seq1 ! serialize(node()), ', ')"/>
<xsl:copy>
<xsl:value-of select="$str1"/>
</xsl:copy>
</xsl:template>
https://xsltfiddle.liberty-development.net/pPJ8LVh/2
As you can see, the last two examples create strings output as text, it seems more likely that you want to create a result nodes, like done in the first suggestion.

String replace and concatenation

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

XSLT applied to an external CSV file, How?

I have some code that loads in an xml file and applies an XSLT to it using “XslCompiledTransform”. I now need to change the code as the input file may not be XML, it could also be a CSV file. I have an XSLT that will convert the CSV to the format I want but it uses a hardcoded path. The problem I have is that the input filename will change so can I use a widecard in the name? Secondly, in code can I pass in the file as a string to an XSLT object and have the result fall out the bottom? I’ve been doing some googling and not found any code to do this. Is it even possible?
Here is the XSLT I am using. Not the hardcoded path and filename.
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:fn="fn"
exclude-result-prefixes="xs fn">
<xsl:output indent="yes" encoding="US-ASCII"/>
<xsl:param name="pathToCSV" select="'file:///C:/Downloads/inputcsv.csv'"/>
<xsl:function name="fn:getTokens" as="xs:string+">
<xsl:param name="str" as="xs:string"/>
<xsl:analyze-string select="concat($str, ',')" regex='(("[^"]*")+|[^,]*),'>
<xsl:matching-substring>
<xsl:sequence select='replace(regex-group(1), "^""|""$|("")""", "$1")'/>
</xsl:matching-substring>
</xsl:analyze-string>
</xsl:function>
<xsl:template match="/" name="main">
<xsl:choose>
<xsl:when test="unparsed-text-available($pathToCSV)">
<xsl:variable name="csv" select="unparsed-text($pathToCSV)"/>
<xsl:variable name="lines" select="tokenize($csv, '
')" as="xs:string+"/>
<xsl:variable name="elemNames" select="fn:getTokens($lines[1])" as="xs:string+"/>
<root>
<xsl:for-each select="$lines[position() > 0]">
<row>
<xsl:variable name="lineItems" select="fn:getTokens(.)" as="xs:string+"/>
<xsl:for-each select="$elemNames">
<xsl:variable name="pos" select="position()"/>
<column>
<xsl:value-of select="$lineItems[$pos]"/>
</column>
</xsl:for-each>
</row>
</xsl:for-each>
</root>
</xsl:when>
<xsl:otherwise>
<xsl:text>Cannot locate : </xsl:text><xsl:value-of select="$pathToCSV"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Well XslCompiledTransform is an XSLT 1.0 processor so using XSLT 2.0 features like unparsed-text is not possible with XslCompiledTransform, you need to use Saxon 9 or XmlPrime or AltovaXML for XSLT 2.0.
Then you can pass in a parameter to your posted stylesheet as it has a global xsl:param named pathToCSV, how you pass in that parameter depends on the API of the XSLT processor you want to use (or on its command line parameters if you simply want to execute the stylesheet from the command line).

XSLT 2.0 : Split a string into comma separated values

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>

Resources