I'm trying to map XML to JSON using XSLT 3.0
my broad plan is to take the input, map it to some elements in memory, and then map that to 'map's and 'array's to by applying templates and then letting the XSLT serialise that as JSON.
Here is my initial effort:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
version="3.0">
<xsl:output method="json" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="dsl" as="element()">
<epg lastBuildDate="10/4/2019 9:46:00 AM">
</epg>
</xsl:variable>
<xsl:variable name="output">
<xsl:apply-templates select="$dsl" mode="interpret"/>
</xsl:variable>
<xsl:sequence select="$output"/>
</xsl:template>
<xsl:template match="epg" mode="interpret">
<xsl:sequence select="map {
'lastBuildDate' : #lastBuildDate
}"/>
</xsl:template>
</xsl:stylesheet>
sadly I get
Cannot add a map to an XDM node tree
in the 'interpret' template.
The error occurs with
<xsl:variable name="output">
<xsl:apply-templates select="$dsl" mode="interpret"/>
</xsl:variable>
as that is an implicit use of
<xsl:variable name="output">
<xsl:document>
<xsl:apply-templates select="$dsl" mode="interpret"/>
</xsl:document>
</xsl:variable>
While maps can contain nodes as values, nodes can't contain maps.
For that simple example it should work to use
<xsl:variable name="output" as="item()*">
<xsl:apply-templates select="$dsl" mode="interpret"/>
</xsl:variable>
or even
<xsl:variable name="output" as="item()">
<xsl:apply-templates select="$dsl" mode="interpret"/>
</xsl:variable>
or
<xsl:variable name="output" as="map(*)">
<xsl:apply-templates select="$dsl" mode="interpret"/>
</xsl:variable>
this works, I'll mark the other answer as correct though there is a 2nd issue where you have to 'cast' the attribute to a string
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
exclude-result-prefixes="xs"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
version="3.0">
<xsl:output method="json" encoding="UTF-8" indent="yes"/>
<xsl:template match="/">
<xsl:variable name="dsl" as="element()">
<epg lastBuildDate="10/4/2019 9:46:00 AM">
</epg>
</xsl:variable>
<xsl:variable name="output" as="map(*)*">
<xsl:apply-templates select="$dsl" mode="interpret"/>
</xsl:variable>
<xsl:sequence select="$output"/>
</xsl:template>
<xsl:template match="epg" as="map(*)" mode="interpret">
<xsl:sequence select="map {
'lastBuildDate' : #lastBuildDate => xs:string()
}"/>
</xsl:template>
</xsl:stylesheet>
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.
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>
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 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.