Xslt Concatenate two nodelists with seperator - string

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.

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.

Print xml nodes for each element in a single row

I need to print the category and hours values from all nodes in this xml as comma separated values in a single row through xslt -
XML
<?xml version="1.0" encoding="UTF-8"?>
<course>
<subcourse>
<code>ABC</code>
<name>REFCOURSE</name>
<date>Date</date>
<category>SDF</category>
<hours>7</hours>
</subcourse>
<subcourse>
<code>DEF</code>
<name>ORIGCOURSE</name>
<date>Date</date>
<category>UIT</category>
<hours>9</hours>
</subcourse>
</course>
Output needed -
SDF,7,UIT,9
By taking help from stakoverflow, here's what I've done 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" version="2.0">
<xsl:param name="range-1-begin" select="4"/>
<xsl:param name="range-1-end" select="5"/>
<xsl:param name="range-2-begin" select="6"/>
<xsl:param name="range-2-end" select="7"/>
<xsl:output method="text" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="subcourse">
<info><xsl:apply-templates/></info>
</xsl:template>
<xsl:template match="subcourse">
<xsl:if test = "not(position()= 1)">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select="."/>
</xsl:template>
</xsl:stylesheet>
Output - ABCREFCOURSEDateSDF7,DEFORIGCOURSEDateUIT9
I need it to iterate through every subcourse and pick category and hours if exist. I could not find how to pick only category and hours.
<?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" version="2.0">
<xsl:template match="//subcource">
<xsl:if test="category">
<xsl:if test = "not(position()=1)">
<xsl:text>,</xsl:text>
</xsl:if>
<xsl:value-of select="category"/><xsl:text>,</xsl:text><xsl:value-of select="hours"/>
</xsl:if>
</xsl:template>
</xsl:stylesheet>

XSLT: replace an integer by a 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.

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>

how to handle array of strings in ".xsl" file?

I have an array of strings in .xsl file, now I have to use the each string separeted by spaces differently. How I can get the strings?
I have following array of strings:
strarray="hw.oh.xml hg.hd.gnl th.ik.lkj"
I have to get "hw.oh.xml" , "hg.hd.gnl" , "th.ik.lkj" strings separetaly to perform some operation on it.
How I can do that?
There are many ways to do this:
I. Using the XPath substring-before() and substring-after() functions:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vStrArray" select="'hw.oh.xml hg.hd.gnl th.ik.lkj'"/>
<xsl:template match="/">
<xsl:value-of select="substring-before($vStrArray, ' ')"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="substring-before(substring-after($vStrArray, ' '),' ')"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="substring-after(substring-after($vStrArray, ' '),' ')"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on any XML document (not used), the wanted result (each item from the "array") is produced:
hw.oh.xml
hg.hd.gnl
th.ik.lkj
This method can quickly become overwhelmingly complex and is not recommended except for "arrays" of just 2-3 items.
II. Representing the "array" as an XML document in XSLT 1.0:
<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"/>
<my:array>
<item>hw.oh.xml</item>
<item>hg.hd.gnl</item>
<item>th.ik.lkj</item>
</my:array>
<xsl:variable name="vStrArray"
select="document('')/*/my:array/*"/>
<xsl:template match="/">
<xsl:value-of select="$vStrArray[1]"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="$vStrArray[2]"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="$vStrArray[3]"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the same XML document (any), the wanted correct result is produced:
hw.oh.xml
hg.hd.gnl
th.ik.lkj
I recommend this method of representing an "array" -- for XSLT 1.0 applications.
III. XSLT 2.0 / XPath 2.0
Simply use a sequence of strings:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vStrArray"
select="'hw.oh.xml', 'hg.hd.gnl', 'th.ik.lkj'"/>
<xsl:template match="/">
<xsl:sequence select=
"for $i in 1 to count($vStrArray)
return
concat($vStrArray[$i], '
')
"/>
</xsl:template>
</xsl:stylesheet>
Result:
hw.oh.xml
hg.hd.gnl
th.ik.lkj
UPDATE: The OP commented that he is stuck with the initial representation of space-separated values, contained in a single string.
IV. Convert the space-separated values string into an XML fragment for easy use.
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vStrArray" select="'hw.oh.xml hg.hd.gnl th.ik.lkj'"/>
<xsl:variable name="vrtfDoc">
<xsl:call-template name="makeIndex">
<xsl:with-param name="pText" select="$vStrArray"/>
</xsl:call-template>
</xsl:variable>
<xsl:variable name="vStrIndex" select="ext:node-set($vrtfDoc)/*"/>
<xsl:template match="/">
<xsl:value-of select="$vStrIndex[1]"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="$vStrIndex[2]"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="$vStrIndex[3]"/>
</xsl:template>
<xsl:template name="makeIndex">
<xsl:param name="pText"/>
<xsl:if test="string-length($pText)>0">
<item>
<xsl:value-of select=
"substring-before(concat($pText,' '), ' ')"/>
</item>
<xsl:call-template name="makeIndex">
<xsl:with-param name="pText" select=
"substring-after($pText,' ')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
This transformation creates an XML fragment from the string in which every <item> element contains just one of the string values. Then its use is just as if it were an array.

Resources