Avoiding circular key definitions in XSLT - xslt-3.0

I am writing some code which turns valid XML instances into presentations of code in a DITA codeblock.
I have some wrapper elmenents in my input that allow me to define some emphasis on the output. This is easy for node() items as the wrapper directly wraps the code to be emphasised. For attributes and so on, though, I need to specify some #select on my emphasis element.
Here is a brief code excerpt of how I'm trying to do this (for attributes; I've removed similar templates for other types of content):
<xsl:key name="emph" match="eg:emph[#select]">
<xsl:variable name="selected">
<xsl:evaluate xpath="#select" context-item="."/>
</xsl:variable>
<xsl:for-each select="$selected">
<xsl:sequence select="generate-id()"/>
</xsl:for-each>
</xsl:key>
<xsl:template match="#*[key('emph', generate-id(.))]" mode="eg">
<xsl:variable name="style" select="if (not(key('emph', generate-id(.))/#style)) then 'italic' else key('emph', generate-id())/#style"/>
<xsl:text> </xsl:text>
<ph outputclass="{$style}">
<xsl:next-match>
<xsl:with-param name="includeSpace" select="false()"/>
</xsl:next-match>
</ph>
</xsl:template>
<xsl:template match="#*" mode="eg">
<xsl:param name="includeSpace" as="xs:boolean" select="true()"/>
<xsl:if test="$includeSpace">
<xsl:text> </xsl:text>
</xsl:if>
<ph outputclass="AttributeName">
<xsl:value-of select="name()"/>
</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">
<xsl:value-of select="."/>
</ph>
<ph outputclass="attributeQuotes">"</ph>
</xsl:template>
With an input such as:
<eg:emph select="abbrev-journal-title/#abbrev-type">
<abbrev-journal-title abbrev-type="custom">JPhysA</abbrev-journal-title>
</eg:emph>
I would like to generate something like:
<ph outputclass="XmlFurniture"><</ph><ph outputclass="ElementName">abbrev-journal-title</ph> <ph outputclass="italic"><ph outputclass="AttributeName">abbrev-type</ph><ph outputclass="equals">=</ph><ph outputclass="attributeQuotes">"</ph><ph outputclass="AttributeValue">custom</ph><ph outputclass="attributeQuotes">"</ph></ph><ph outputclass="XmlFurniture">></ph>JPhysA<ph outputclass="XmlFurniture"></</ph><ph outputclass="ElementName">abbrev-journal-title</ph><ph outputclass="XmlFurniture">></ph>
Transforming with Saxon (PE 9.8.0.12) returns 'key definition is circular' errors - but as far as I can tell, that isn't actually the case.
Can anyone suggest a workaround, or at least explain why this approach isn't working?

Well, for the use of xsl:evaluate see https://www.w3.org/TR/xslt-30/#evaluate-dynamic-context explaining
The context item, position, and size depend on the result of
evaluating the expression in the context-item attribute. If this
attribute is absent, or if the result is an empty sequence, then the
context item, position, and size for evaluation of the target
expression are all absent.
As you don't set that attribute context-item on your xsl:evaluate at all your attempt with xpath="#select" doesn't make sense, I guess you want to use context-item="." to select the matched element.
As for selecting attribute nodes and storing them in a variable, I think you need to use
<xsl:variable name="selected" as="attribute()*">
<xsl:evaluate xpath="#select" context-item="."/>
</xsl:variable>
instead of
<xsl:variable name="selected">
<xsl:evaluate xpath="#select" context-item="."/>
</xsl:variable>
Then I would think that
<xsl:for-each select="$selected">
<xsl:sequence select="generate-id()"/>
</xsl:for-each>
can be shortened/simplified to
<xsl:sequence select="$selected!generate-id()"/>
in the context of XSLT 3.
I have now tried to build a minimal but complete example and to test that with Saxon, using Saxon 9.8.0.12 EE and Saxon 9.9.0.1 EE, I get no errors and it seems the approach work as far as the templates you have created are used.
Test XSLT is:
<?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:eg="http://example.com/eg"
exclude-result-prefixes="#all"
default-mode="eg"
version="3.0">
<xsl:mode name="eg" on-no-match="shallow-copy"/>
<xsl:output indent="yes"/>
<xsl:key name="emph" match="eg:emph[#select]">
<xsl:variable name="selected" as="attribute()*">
<xsl:evaluate xpath="#select" context-item="."/>
</xsl:variable>
<xsl:sequence select="$selected ! generate-id()"/>
</xsl:key>
<xsl:template match="#*[key('emph', generate-id(.))]" mode="eg">
<xsl:variable name="style" select="if (not(key('emph', generate-id(.))/#style)) then 'italic' else key('emph', generate-id())/#style"/>
<xsl:text> </xsl:text>
<ph outputclass="{$style}">
<xsl:next-match>
<xsl:with-param name="includeSpace" select="false()"/>
</xsl:next-match>
</ph>
</xsl:template>
<xsl:template match="#*" mode="eg">
<xsl:param name="includeSpace" as="xs:boolean" select="true()"/>
<xsl:if test="$includeSpace">
<xsl:text> </xsl:text>
</xsl:if>
<ph outputclass="AttributeName">
<xsl:value-of select="name()"/>
</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">
<xsl:value-of select="."/>
</ph>
<ph outputclass="attributeQuotes">"</ph>
</xsl:template>
</xsl:stylesheet>
Sample input is
<root>
<eg:emph select="abbrev-journal-title/#abbrev-type" xmlns:eg="http://example.com/eg">
<abbrev-journal-title abbrev-type="custom">JPhysA</abbrev-journal-title>
</eg:emph>
<eg:emph select="abbrev-journal-title/#abbrev-type" xmlns:eg="http://example.com/eg" style="bold">
<abbrev-journal-title abbrev-type="custom">JPhysA</abbrev-journal-title>
</eg:emph>
</root>
Result shows the attributes are matched and transformed:
<root>
<eg:emph xmlns:eg="http://example.com/eg">
<ph outputclass="AttributeName">select</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">abbrev-journal-title/#abbrev-type</ph>
<ph outputclass="attributeQuotes">"</ph>
<abbrev-journal-title>
<ph outputclass="italic">
<ph outputclass="AttributeName">abbrev-type</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">custom</ph>
<ph outputclass="attributeQuotes">"</ph>
</ph>JPhysA</abbrev-journal-title>
</eg:emph>
<eg:emph xmlns:eg="http://example.com/eg">
<ph outputclass="AttributeName">select</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">abbrev-journal-title/#abbrev-type</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeName">style</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">bold</ph>
<ph outputclass="attributeQuotes">"</ph>
<abbrev-journal-title>
<ph outputclass="bold">
<ph outputclass="AttributeName">abbrev-type</ph>
<ph outputclass="equals">=</ph>
<ph outputclass="attributeQuotes">"</ph>
<ph outputclass="AttributeValue">custom</ph>
<ph outputclass="attributeQuotes">"</ph>
</ph>JPhysA</abbrev-journal-title>
</eg:emph>
</root>

Related

xsltproc XML parser multi value

I'm using xsltproc to parse XML content into text content. Need to separate multiple values for one tag.
My XML:
<data xml:space="preserve" id="USER">
<c1>USER NAME</c1>
<c2>ADDRESS</c2>
<c3>DET</c3>
<c4>AILS</c4>
<c5>1001</c5>
<c5 a="2">2001</c5>
<c5 a="3">3001</c5>
<c5 a="4">401</c5>
<c5 a="5">5001</c5>
<c5 a="6">6001</c5>
<c6>1</c6>
<c7>20991231M0601</c7>
</data>
My xsl:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<!-- write out comma separated file -->
<xsl:template match="data">
<xsl:value-of select="c1"/>
<xsl:value-of select="','"/>
<xsl:value-of select="c2"/>
<xsl:value-of select="','"/>
<xsl:value-of select="c3"/>
<xsl:value-of select="','"/>
<xsl:value-of select="c4"/>
<xsl:value-of select="','"/>
<xsl:apply-templates select="c5"/>
<xsl:value-of select="','"/>
<xsl:value-of select="c6"/>
<xsl:value-of select="','"/>
<xsl:value-of select="c7"/>
<xsl:value-of select="','"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
Post execution,
My output comes like,
USER NAME,ADDRESS,DET,AILS,10012001300140150016001,1,20991231M0601,
But i wanted an output like
USER NAME,ADDRESS,DET,AILS,1001|2001|3001|401|5001|6001,1,20991231M0601,
With separator for the multi value.
How about:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text"/>
<xsl:template match="data">
<xsl:for-each select="c1|c2|c3|c4">
<xsl:value-of select="."/>
<xsl:text>,</xsl:text>
</xsl:for-each>
<xsl:for-each select="c5">
<xsl:value-of select="."/>
<xsl:if test="position()!=last()">|</xsl:if>
</xsl:for-each>
<xsl:text>,</xsl:text>
<xsl:value-of select="c6"/>
<xsl:text>,</xsl:text>
<xsl:value-of select="c7"/>
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>

XSLT 1.0: Replacing all occurences of a string in a node-set

I have the following function which replaces all occurences of a search-string ($replace) in a string ($text) with another string ($by):
<xsl:template name="string-replace-all">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text"
select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
This works fine for replacing text in individual strings, however it doesn't work when trying to replace text in a node-set.
What I am looking for is a function which takes, for example, the following XML document:
<nodeSet>
<node>a1;a2;a3</node>
<node>b1;b2;b3</node>
</nodeSet>
and outputs the following:
<nodeSet>
<node>a1#a2#a3</node>
<node>b1#b2#b3</node>
</nodeSet>
The following template does the job when the target and replacement strings are known in advance:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl"
>
<xsl:template name="string-replace-all-in-nodeset">
<xsl:param name="nodeset" />
<xsl:apply-templates select="exsl:node-set($nodeset)" mode="str-repl-in-nodeset"/>
</xsl:template>
<xsl:template match="*/text()" mode="str-repl-in-nodeset">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="."/>
<xsl:with-param name="replace" select=" ';' "/>
<xsl:with-param name="by" select=" '#' "/>
</xsl:call-template>
</xsl:template>
<xsl:template name="string-replace-all">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="#*|node()" mode="str-repl-in-nodeset">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="str-repl-in-nodeset"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
However, I need to be able to pass the target and replacement string (';' and '#' in this case) dynamically. Is there any way of passing these parameters to the template matching all text nodes (match="*/text()") or any other way of achieving what I want?
Here is a stylesheet that defines global parameters for the replace and by strings and then passes them on to all templates in that mode str-repl-in-nodeset:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:transform
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0"
xmlns:exsl="http://exslt.org/common"
exclude-result-prefixes="exsl">
<xsl:param name="ns1">
<nodeSet>
<node>a1;a2;a3</node>
<node>b1;b2;b3</node>
</nodeSet>
</xsl:param>
<xsl:param name="replace" select="';'"/>
<xsl:param name="by" select="'#'"/>
<xsl:template match="#*|node()">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/">
<xsl:apply-templates select="exsl:node-set($ns1)" mode="str-repl-in-nodeset">
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="by" select="$by"/>
</xsl:apply-templates>
</xsl:template>
<xsl:template match="/ | #* | node()" mode="str-repl-in-nodeset">
<xsl:param name="replace"/>
<xsl:param name="by"/>
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="str-repl-in-nodeset">
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="by" select="$by"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>
<xsl:template match="*/text()" mode="str-repl-in-nodeset">
<xsl:param name="replace"/>
<xsl:param name="by"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="."/>
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="by" select="$by"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="string-replace-all">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:transform>

Removing asterisks and single quotes from attribute values using XSL

I have this XML fragment:
<svrl:successful-report test="."
location="/*[local-name()='ClinicalDocument']/*[local-name()='component']/*[local-name()='structuredBody']/*[local-name()='component'][1]/*[local-name()='section']">
I want to get the value #location and remove the special characters " *[local-name()=' " and " '] ". In other words, I want the output to be
/ClinicalDocument/component/structuredBody/component[1]/section
I'm currently using this string replace template:
<xsl:template name="string-replace-all">
<xsl:param name="text" />
<xsl:param name="replace" />
<xsl:param name="by" />
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)" />
<xsl:value-of select="$by" />
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text"
select="substring-after($text,$replace)" />
<xsl:with-param name="replace" select="$replace" />
<xsl:with-param name="by" select="$by" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text" />
</xsl:otherwise>
</xsl:choose>
</xsl:template>
and applying the template like this
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="#location"/>
<xsl:with-param name="replace" select="'[local-name()='"/>
<xsl:with-param name="by" select="''"/>
</xsl:call-template>
That only gives this result:
/*'ClinicalDocument']/*'component']/*'structuredBody']/*'component'][1]/*'section']
How can I get the output I want?
The other answer to this question is a good one, but has problems with a source XML document like this:
<test xmlns:svrl="my:my">
<svrl:successful-report test="." location=
"/*[local-name()='ClinicalDocument']/*[local-name()='component'][.='abc']"/>
</test>
The result it produces when applied on this document is:
/ClinicalDocument/component[.='abc
But the correct result is:
/ClinicalDocument/component[.='abc']
This transformation has no problems with the above XML document:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="#location">
<xsl:attribute name="location">
<xsl:call-template name="makeExplicit">
<xsl:with-param name="pText" select="substring(.,2)"/>
</xsl:call-template>
</xsl:attribute>
</xsl:template>
<xsl:template name="makeExplicit">
<xsl:param name="pText" select="."/>
<xsl:if test="$pText">
<xsl:variable name="vStep" select="substring-before(concat($pText, '/'), '/')"/>
<xsl:call-template name="processStep">
<xsl:with-param name="pStep" select="$vStep"/>
</xsl:call-template>
<xsl:call-template name="makeExplicit">
<xsl:with-param name="pText" select="substring-after($pText, '/')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="processStep">
<xsl:param name="pStep"/>
<xsl:text>/</xsl:text>
<xsl:value-of select="substring-before(concat($pStep, '*['), '*[')"/>
<xsl:variable name="vPred" select="substring-after($pStep, '*[local-name()=')"/>
<xsl:value-of select="substring-before(substring($vPred, 2), "'")"/>
<xsl:value-of select="substring-after($vPred, ']')"/>
</xsl:template>
</xsl:stylesheet>
When applied on the above document, the correct, wanted result is produced:
<test xmlns:svrl="my:my">
<svrl:successful-report test="."
location="/ClinicalDocument/component[.='abc']"/>
</test>
You need to do two 'find and replace' here. Firstly you need to remove all of the prefixes *[local-name()=', and then you need to remove the suffixes of ']
You can do this by passing the results of the first call-template as a parameter to the second call-template
Here is the full XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:svrl="my:my">
<xsl:template match="svrl:successful-report">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="#location"/>
<xsl:with-param name="replace" select=""*[local-name()='""/>
<xsl:with-param name="by" select="''"/>
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="replace" select=""']""/>
<xsl:with-param name="by" select="''"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="string-replace-all">
<xsl:param name="text"/>
<xsl:param name="replace"/>
<xsl:param name="by"/>
<xsl:choose>
<xsl:when test="contains($text, $replace)">
<xsl:value-of select="substring-before($text,$replace)"/>
<xsl:value-of select="$by"/>
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="substring-after($text,$replace)"/>
<xsl:with-param name="replace" select="$replace"/>
<xsl:with-param name="by" select="$by"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$text"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
When applied to the following XML
<test xmlns:svrl="my:my">
<svrl:successful-report test="." location="/*[local-name()='ClinicalDocument']/*[local-name()='component']/*[local-name()='structuredBody']/*[local-name()='component'][1]/*[local-name()='section']"/>
</test>
The following is output
/ClinicalDocument/component/structuredBody/component[1]/section

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