I want to select part of a String using xslt.
Input :
p:endnote_bl1
p:endnote_bl2
p:endnote_bl3
p:endnote_bl4
Output Should be :
endnote_bl1
endnote_bl2
endnote_bl3
endnote_bl4
Can I do this using split or something else? I am using XSLT 2.0
Use replace function
replace('p:endnote_bl1', '^[^:]*:', '')
You could do simply:
substring-after($string, ':')
There are a multitude of ways to do this.
This transformation shows four different solutions, the first two of which are XPath 1.0 (XSLT 1.0) solutions:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vSolution1">
<xsl:apply-templates select="/*" mode="substring-after"/>
</xsl:variable>
<xsl:variable name="vSolution2">
<xsl:apply-templates select="/*" mode="substring"/>
</xsl:variable>
<xsl:variable name="vSolution3">
<xsl:apply-templates select="/*" mode="split"/>
</xsl:variable>
<xsl:variable name="vSolution4">
<xsl:apply-templates select="/*" mode="replace"/>
</xsl:variable>
<xsl:template match="/">
<xsl:copy-of select="$vSolution1"/>
==================
<xsl:copy-of select="$vSolution2"/>
==================
<xsl:copy-of select="$vSolution3"/>
==================
<xsl:copy-of select="$vSolution4"/>
</xsl:template>
<xsl:template match="node()|#*" mode="#all">
<xsl:copy>
<xsl:apply-templates select="node()|#*" mode="#current"/>
</xsl:copy>
</xsl:template>
<xsl:template match="s/text()" mode="substring-after">
<xsl:value-of select="substring-after(., 'p:')"/>
</xsl:template>
<xsl:template match="s/text()" mode="substring">
<xsl:value-of select="substring(., 3)"/>
</xsl:template>
<xsl:template match="s/text()" mode="split">
<xsl:value-of select="tokenize(., ':')[2]"/>
</xsl:template>
<xsl:template match="s/text()" mode="replace">
<xsl:value-of select="replace(., '^.+:', '')"/>
</xsl:template>
</xsl:stylesheet>
When this transformation is applied on the XML document below (none was provided!):
<t>
<s>p:endnote_bl1</s>
<s>p:endnote_bl2</s>
<s>p:endnote_bl3</s>
<s>p:endnote_bl4</s>
</t>
We get four identical, correct results, each produced by one of the four solutions:
<t>
<s>endnote_bl1</s>
<s>endnote_bl2</s>
<s>endnote_bl3</s>
<s>endnote_bl4</s>
</t>
==================
<t>
<s>endnote_bl1</s>
<s>endnote_bl2</s>
<s>endnote_bl3</s>
<s>endnote_bl4</s>
</t>
==================
<t>
<s>endnote_bl1</s>
<s>endnote_bl2</s>
<s>endnote_bl3</s>
<s>endnote_bl4</s>
</t>
==================
<t>
<s>endnote_bl1</s>
<s>endnote_bl2</s>
<s>endnote_bl3</s>
<s>endnote_bl4</s>
</t>
Related
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.
The match attribute of <xsl:template> defines which nodes this
template rule applies to. I think, the matched nodes are implicit coming from initial source document.
For example, here is a part of my XSLT template:
<xsl:mode name="unroll" on-no-match="shallow-copy"/>
<xsl:template match="StructFormat[#repeat]" mode="unroll">
...
</xsl:template>
<xsl:variable name="complete-struct">
<xsl:apply-templates mode="unroll"/>
</xsl:variable>
This template processes the intial source ducument, and the result is saved in a variable. How can I let this template rule apply to a temporary document loaded by document() function? I tried like this and it didn't work:
<xsl:template match="/" mode="unroll">
<xsl:apply-templates select="document('a.xml')/*"/>
</xsl:template>
<xsl:template match="#*|node()" mode="unroll">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
The part with the global variable
<xsl:variable name="complete-struct">
<xsl:apply-templates mode="unroll"/>
</xsl:variable>
builds a variable processing the child nodes of the global context item (https://www.w3.org/TR/xslt-30/#dt-global-context-item)
you can change that to
<xsl:variable name="complete-struct">
<xsl:apply-templates select="doc('a.xml')/node()" mode="unroll"/>
</xsl:variable>
to process nodes from another document or if you run your XSLT processor with its API check where/how you could set that global context item to your particular document if needed/wanted (see http://saxonica.com/html/documentation/javadoc/net/sf/saxon/s9api/Xslt30Transformer.html#setGlobalContextItem-net.sf.saxon.s9api.XdmItem- for Saxon 9.9).
I think your attempt adding
<xsl:template match="#*|node()" mode="unroll">
<xsl:copy>
<xsl:apply-templates select="#*|node()"/>
</xsl:copy>
</xsl:template>
is wrong, your initial code has a declaration <xsl:mode name="unroll" on-no-match="shallow-copy"/> which should do fine and if you wanted to spell it out you would need
<xsl:template match="#*|node()" mode="unroll">
<xsl:copy>
<xsl:apply-templates select="#*|node()" mode="#current"/>
</xsl:copy>
</xsl:template>
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>
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.
I'm trying to format strings in XSLT that needs to be in pascal case to be used appropriately for the application I'm working with.
For example:
this_text would become ThisText
this_long_text would become ThisLongText
Is it possible to also set this up where I can send an input to the format so I do not have to recreate the format multiple times?
This transformation:
<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="vLower" select=
"'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="vUpper" select=
"'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="text()">
<xsl:call-template name="Pascalize">
<xsl:with-param name="pText" select="concat(., '_')"/>
</xsl:call-template>
</xsl:template>
<xsl:template name="Pascalize">
<xsl:param name="pText"/>
<xsl:if test="$pText">
<xsl:value-of select=
"translate(substring($pText,1,1), $vLower, $vUpper)"/>
<xsl:value-of select="substring-before(substring($pText,2), '_')"/>
<xsl:call-template name="Pascalize">
<xsl:with-param name="pText"
select="substring-after(substring($pText,2), '_')"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
</xsl:stylesheet>
when applied on this XML document:
<t>
<a>this_text</a>
<b>this_long_text</b>
</t>
produces the desired result:
<t>
<a>ThisText</a>
<b>ThisLongText</b>
</t>
BTW, this is camelCase and this is PascalCase
Here, two years after the fact, is an XSLT 2.0 solution:
<xsl:function name="fn:pascal-case">
<xsl:param name="string"/>
<xsl:value-of select="string-join(for $s in tokenize($string,'\W+') return concat(upper-case(substring($s,1,1)),substring($s,2)),'')"/>
</xsl:function>
It will pascalize either 'this_long_text' or 'this-long-text' to 'ThisLongText' because it breaks on any non-word characters.
In the regex flavors I am most familiar with (perl, pcre, etc.), an underscore is considered part of the '\w' character class (therefore not part of \W), but for XSLT 2.0 the XSD datatypes are used (http://www.w3.org/TR/xmlschema-2/) and '\w' is defined as:
[#x0000-#x10FFFF]-[\p{P}\p{Z}\p{C}] (all characters except the set of "punctuation", "separator" and "other" characters)
so '\W' includes an underscore.
This version worked for me. I added a choose that outputs "the rest" of the string when no more underbars are present.
<xsl:variable name="vLower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="vUpper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template name="Pascalize">
<xsl:param name="pText" />
<xsl:if test="$pText">
<xsl:value-of select="translate(substring($pText,1,1), $vLower, $vUpper)" />
<xsl:choose>
<xsl:when test="contains($pText, '_')">
<xsl:value-of select="substring-before(substring($pText,2), '_')" />
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($pText,2)" />
</xsl:otherwise>
</xsl:choose>
<xsl:call-template name="Pascalize">
<xsl:with-param name="pText" select="substring-after(substring($pText,2), '_')" />
</xsl:call-template>
</xsl:if>
</xsl:template>
Also, in case anyone comes here looking for the reverse process (which I happened to also require today and could find not a single example of anywhere)...
<xsl:variable name="vLower" select="'abcdefghijklmnopqrstuvwxyz'"/>
<xsl:variable name="vUpper" select="'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"/>
<xsl:template name="TitleCase">
<xsl:param name="pText" />
<xsl:call-template name="TitleCase_recurse">
<xsl:with-param name="pText" select="concat(translate(substring($pText,1,1), $vLower, $vUpper), substring($pText,2))" />
</xsl:call-template>
</xsl:template>
<xsl:template name="TitleCase_recurse">
<xsl:param name="pText" />
<xsl:if test="string-length($pText) > 1">
<xsl:if test="not(substring($pText,1,1) = ' ' and substring($pText,1,1) = ' ')">
<xsl:value-of select="substring($pText,1,1)" />
</xsl:if>
<xsl:if test="translate(substring($pText,1,1), $vLower, $vUpper) != substring($pText,1,1)">
<xsl:if test="translate(substring($pText,2,1), $vLower, $vUpper) = substring($pText,2,1)">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:if>
<xsl:call-template name="TitleCase_recurse">
<xsl:with-param name="pText" select="substring($pText,2)" />
</xsl:call-template>
</xsl:if>
<xsl:if test="string-length($pText) = 1">
<xsl:value-of select="$pText" />
</xsl:if>
</xsl:template>
I love it when my subconscious brain pops up an answer a few hours after I've completely given up consciously. ;-)
I was trying to achieve the "pascalizing" with the following XLST function call:
<xsl:value-of select="fn:replace(#name,'_(\w{1})','\U$1')"/>
Unfortunately the processor throws the error message "Invalid replacement string in replace():
\ character must be followed by \ or $"
the problem is the \U modifier which is supposed to do the uppercase conversion of the matched pattern. If I change it to
<xsl:value-of select="fn:replace(#name,'_(\w{1})','\\U$1')"/>
the output string contains the sequence '\U' because it is now esacped - but I don't want to escape it, I want it do be effective ;-) . I did test
<xsl:value-of select="fn:replace(#name,'_(\w{1})','$1')"/>
(without converting the match to uppercase) and that works fine. But of course it does no uppercasing, just removes underscores and replaces the letter after the underscore by itself instead of capitalizing it. Am I doing something wrong here or is the \U modifier simply not supported in the regex implementation of my XSLT processor?
Thanks to Dimitre, I was able to get most of the way there. When running my strings through the Pascalize template, the bit after the last '_' was cut off. There's probably a cleaner way of doing it, but here's the code I used:
<xsl:template name="Pascalize">
<xsl:param name="pText"/>
<xsl:if test="$pText">
<xsl:value-of select="translate(substring($pText,1,1), $vLower, $vUpper)"/>
<xsl:value-of select="substring-before(substring($pText,2), '_')"/>
<xsl:call-template name="Pascalize">
<xsl:with-param name="pText" select="substring-after(substring($pText,2), '_')"/>
</xsl:call-template>
<xsl:call-template name="GrabLastPart">
<xsl:with-param name="pText" select="$pText"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="GrabLastPart">
<xsl:param name="pText"/>
<xsl:choose>
<xsl:when test="contains($pText, '_')">
<xsl:call-template name="GrabLastPart">
<xsl:with-param name="pText" expr="substring-after($pText, '_')"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="substring($pText, 2)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>