xsltproc XML parser multi value - linux

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>

Related

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

In XSLT, How do I split the text content of an element into lines?

I'm using XSLT to parse the textual content of an XML element. This text contains newlines, but I can't seem to parse them correctly. I'm using code I found online to chop up the text. Here's the relevant part of the code.
<xsl:variable name="first">
<xsl:value-of select="substring-before($source, $newline)"/>
</xsl:variable>
<xsl:variable name="rest">
<xsl:value-of select="substring-after($source, $newline)"/>
</xsl:variable>
This is part of a recusrive template that pushes $rest into itself.
The problem is that the code sample doesn't define $newline.
If I set $newline to a letter, like 's', the text gets split up just fine (e.g. it will turn the input "resounding" into "re" and "ounding"). But when I try to set $newline to the newline character, that is
or  , it recurses forever and gives me a stack overflow. I also tried to define an ENTITY for newline but it makes no difference.
The input has ordinary CR/LF at the end of each line (I'm on a Windows box).
What am I doing wrong?
If you can use EXSLT try with str:tokenize
<xsl:for-each select="str:tokenize($source, $newline)">
<xsl:value-of select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
Or similarly with XSLT 2.0:
<xsl:for-each select="tokenize($source, $newline)">
<xsl:sequence select="."/>
<xsl:text>
</xsl:text>
</xsl:for-each>
You may be able to use the below.
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/">
<root>
<xsl:for-each select="root/str">
<str>
<xsl:call-template name="strSplit">
<xsl:with-param name="str" select="."/>
<xsl:with-param name="seqno" select="1"/>
</xsl:call-template>
</str>
</xsl:for-each>
</root>
</xsl:template>
<xsl:template name="strSplit">
<xsl:param name="str"/>
<xsl:param name="seqno"/>
<xsl:variable name="afterLeadingWS"
select="substring-after($str, substring-before($str,substring-before(normalize-space($str), ' ')))"/>
<xsl:choose>
<xsl:when test="contains($afterLeadingWS, '
')">
<line>
<xsl:attribute name="seqno"><xsl:value-of select="$seqno"/></xsl:attribute>
<xsl:attribute name="length"><xsl:value-of select="string-length(substring-before($afterLeadingWS, '
'))"/></xsl:attribute>
<xsl:value-of select="substring-before($afterLeadingWS, '
')"/>
</line>
<xsl:call-template name="strSplit">
<xsl:with-param name="str" select="substring-after($afterLeadingWS, '
')"/>
<xsl:with-param name="seqno" select="$seqno + 1"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<line>
<xsl:attribute name="seqno"><xsl:value-of select="$seqno"/></xsl:attribute>
<xsl:value-of select="$afterLeadingWS"/>
</line>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Applied to
<?xml version="1.0" encoding="UTF-8"?>
<root>
<str>
yigifgniuq h
eukwgf kuew hgk.uhgku
,/v.,silghouihhg
</str>
<str>
09734ymmnyr n.0808
o149013483ymr7rg
738924m c0
</str>
</root>
the output result is
<?xml version="1.0" encoding="UTF-8"?>
<root>
<str>
<line seqno="1" length="13">yigifgniuq h </line>
<line seqno="2" length="21">eukwgf kuew hgk.uhgku</line>
<line seqno="3" length="18"> ,/v.,silghouihhg</line>
<line seqno="4"> </line>
</str>
<str>
<line seqno="1" length="18">09734ymmnyr n.0808</line>
<line seqno="2" length="16">o149013483ymr7rg</line>
<line seqno="3" length="11">738924m c0 </line>
<line seqno="4" length="2"> </line>
<line seqno="5"> </line>
</str>
</root>
Note that leading tabs (or blanks) are seen as part of lines.
Maestro13's answer brought me closest, and I ended up merging the template I had with his, to produce this, which I share here for future generations. It's a template that returns the length of the longest line in the string you pass to it.
<xsl:template name="longestCodeLine">
<xsl:param name="str"/>
<xsl:choose>
<!-- Is this the last line? -->
<xsl:when test="contains($str, '
')">
<!-- No. First isolate all remaining lines, and recurse to find its longest line. -->
<xsl:variable name="bestOfTheRest">
<xsl:call-template name="longestCodeLine">
<xsl:with-param name="str" select="substring-after($str, '
')"/>
</xsl:call-template>
</xsl:variable>
<xsl:choose>
<!-- Compare the longest of the remaining lines to this one. Which one's longer? -->
<!-- If the longest of the remaining lines is longer, return that line. -->
<xsl:when test="string-length($bestOfTheRest) > string-length(substring-before($str, '
'))">
<xsl:value-of select="$bestOfTheRest"/>
</xsl:when>
<!-- If this line longer, return this line. -->
<xsl:otherwise>
<xsl:value-of select="substring-before($str, '
')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:when>
<!-- If there are no \n's left, this is your last string. So it is by definition the longest one left. -->
<xsl:otherwise>
<xsl:value-of select="$str"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
I have used this template once. It is a named template so you can call it where ever you need it. The text here is split up in 70 character pieces:
<xsl:template name="Texts">
<xsl:param name="string" select="TEXTITEM" />
<xsl:param name="line-length" select="70"/>
<xsl:variable name="line" select="substring($string,1,$line-length)"/>
<xsl:variable name="rest" select="substring($string, $line-length+1)"/>
<xsl:if test="$line">
<MYTEXT>
<xsl:value-of select="$line"/>
</MYTEXT>
</xsl:if>
<xsl:if test="$rest">
<xsl:call-template name="Texts">
<xsl:with-param name="string" select="$rest"/>
<xsl:with-param name="line-length" select="$line-length"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
Thought I'd add a line splitting code that adds newlines after white space.
<xsl:function name="kode:splitLongLine">
<xsl:param name="string"/>
<xsl:variable name="regex">
<xsl:text>(((.){1,55})( |$))</xsl:text>
</xsl:variable>
<xsl:variable name="result">
<xsl:analyze-string select="$string" regex="{$regex}">
<xsl:matching-substring>
<xsl:value-of select="concat(regex-group(1),'
')"/>
</xsl:matching-substring>
<xsl:non-matching-substring>
<xsl:value-of select="concat('REPORT ERROR: ', .)"/>
</xsl:non-matching-substring>
</xsl:analyze-string>
</xsl:variable>
<xsl:sequence select="$result"/>
</xsl:function>

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.

How to format a string to Pascal case in XSLT?

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>

Resources