13

I have a String and I need to convert the first letter of each word to upper case and rest to lower case using xsl, For example,

Input String= dInEsh sAchdeV kApil Muk

Desired Output String= Dinesh Sachdev Kapil Muk

Although, I know I have to use translate function for the purpose but how can I translate the first charter of each word to Upper-case and rest all in lower- case using XSLT 1.0

Thanks

Phil H
  • 19,928
  • 7
  • 68
  • 105
dinesh028
  • 2,137
  • 5
  • 30
  • 47

9 Answers9

18

The following is not "nice", and I'm sure somebody (mainly Dimitri) could come up with something far simpler (especially in XSLT 2.0)... but I've tested this and it works

<xsl:template name="CamelCase">
  <xsl:param name="text"/>
  <xsl:choose>
    <xsl:when test="contains($text,' ')">
      <xsl:call-template name="CamelCaseWord">
        <xsl:with-param name="text" select="substring-before($text,' ')"/>
      </xsl:call-template>
      <xsl:text> </xsl:text>
      <xsl:call-template name="CamelCase">
        <xsl:with-param name="text" select="substring-after($text,' ')"/>
      </xsl:call-template>
    </xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="CamelCaseWord">
        <xsl:with-param name="text" select="$text"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template name="CamelCaseWord">
  <xsl:param name="text"/>
  <xsl:value-of select="translate(substring($text,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ')" /><xsl:value-of select="translate(substring($text,2,string-length($text)-1),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz')" />
</xsl:template>

The basic idea is that you call CamelCase, if it find a space, then it runs CamelCaseWord on everything before the space (i.e. the first word) and then calls CamelCase again with the everything after the space (i.e. the rest of the sentence). Otherwise if no space is found (because it's got to the last word in the sentence), then it just calls CamelCaseWord.

The CamelCaseWord template simply translates the first character from lower to upper (if necessary) and all remaining characters from upper to lower (if necessary).

So to call it you'd have...

<xsl:call-template name="CamelCase">
   <xsl:with-param name="text">dInEsh sAchdeV kApil Muk</xsl:with-param>
</xsl:call-template>
freefaller
  • 19,368
  • 7
  • 57
  • 87
  • Thank you very much. This really help me to translate first character to upper. But this one assumes space in between strings. How can I dynamically find any non-alphanumeric letter instead of space and capitalise first letter. For e.g O'connel will be O'Connel and dave-manel will be Dave-Manel – Patty Sep 06 '21 at 12:21
  • @Patty - it's been a long time since I've done XSLT. I'm afraid I'm unable to answer your supplementary question. You'd be better off asking it as a brand new question. Sorry I can't be of more help – freefaller Sep 06 '21 at 13:13
  • Thats fine no worries and thank you for your response. – Patty Sep 06 '21 at 13:15
5

Additional:

I missed the 1.0 requirement in the question. This will only work from version 2.0.

Original answer below here.

I believe this one worked for me a while ago. Declare a function:

<xsl:function name="my:titleCase" as="xs:string">
    <xsl:param name="s" as="xs:string"/>
    <xsl:choose>
        <xsl:when test="lower-case($s)=('and','or')">
            <xsl:value-of select="lower-case($s)"/>
        </xsl:when>
        <xsl:when test="$s=upper-case($s)">
            <xsl:value-of select="$s"/>
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="concat(upper-case(substring($s, 1, 1)), lower-case(substring($s, 2)))"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

And use it:

<xsl:sequence select="string-join(for $x in tokenize($text,'\s') return my:titleCase($x),' ')"/>

credit goes to samjudson => http://p2p.wrox.com/xslt/80938-title-case-string.html

Kris
  • 40,604
  • 9
  • 72
  • 101
4

Here is a 8 years old FXSL 1.x (an XSLT 1.0 libray written completely in XSLT 1.0) solution:

test-strSplit-to-Words10.xsl:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
>
   <xsl:import href="strSplitWordDel.xsl"/>

   <!-- To be applied on: test-strSplit-to-Words10.xml -->

   <xsl:output indent="yes" omit-xml-declaration="yes"/>
   <xsl:variable name="vLower" 
        select="'abcdefgijklmnopqrstuvwxyz'"/>
   <xsl:variable name="vUpper" 
        select="'ABCDEFGIJKLMNOPQRSTUVWXYZ'"/>

    <xsl:template match="/">
      <xsl:variable name="vwordNodes">
        <xsl:call-template name="str-split-word-del">
          <xsl:with-param name="pStr" select="/"/>
          <xsl:with-param name="pDelimiters" 
                          select="', .(&#9;&#10;&#13;'"/>
        </xsl:call-template>
      </xsl:variable>

      <xsl:apply-templates select="ext:node-set($vwordNodes)/*"/>
    </xsl:template>

    <xsl:template match="word">
       <xsl:choose>
         <xsl:when test="not(position() = last())">
           <xsl:value-of 
           select="translate(substring(.,1,1),$vLower,$vUpper)"/>
           <xsl:value-of select="substring(.,2)"/>
         </xsl:when>
         <xsl:otherwise>
           <xsl:value-of select="."/>
         </xsl:otherwise>
       </xsl:choose>
    </xsl:template>

    <xsl:template match="delim">
      <xsl:value-of select="."/>
    </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the following XML document (test-strSplit-to-Words10.xml):

<t>004.lightning crashes (live).mp3</t>

the result is:

004.Lightning Crashes (Live).mp3

When applied to this XML document (your provided sample):

dInEsh sAchdeV kApil Muk

the result is:

DInEsh SAchdeV KApil Muk

With just a little tweek, we get this code:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:ext="http://exslt.org/common"
>
   <xsl:import href="strSplitWordDel.xsl"/>

   <!-- To be applied on: test-strSplit-to-Words10.xml -->

   <xsl:output indent="yes" omit-xml-declaration="yes"/>
   <xsl:variable name="vLower" 
        select="'abcdefgijklmnopqrstuvwxyz'"/>
   <xsl:variable name="vUpper" 
        select="'ABCDEFGIJKLMNOPQRSTUVWXYZ'"/>

    <xsl:template match="/">
      <xsl:variable name="vwordNodes">
        <xsl:call-template name="str-split-word-del">
          <xsl:with-param name="pStr" select="/"/>
          <xsl:with-param name="pDelimiters" 
                          select="', .(&#9;&#10;&#13;'"/>
        </xsl:call-template>
      </xsl:variable>

      <xsl:apply-templates select="ext:node-set($vwordNodes)/*"/>
    </xsl:template>

    <xsl:template match="word">
           <xsl:value-of 
           select="translate(substring(.,1,1),$vLower,$vUpper)"/>
           <xsl:value-of select="translate(substring(.,2), $vUpper, $vLower)"/>
    </xsl:template>

    <xsl:template match="delim">
      <xsl:value-of select="."/>
    </xsl:template>
</xsl:stylesheet>

which now produces the wanted result:

Dinesh Sachdev Kapil Muk

Explanation:

The str-split-word-del template of FXSL can be used for tokenization with (possibly more than one) delimiters specified as a string parameter.

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431
2

Here's another short solution. It uses pure XSL-T 2.0. I know OP had a requirement for XSL-T 1.0, but since this page is ranked #1 on Google for 'xsl-t title case function' in 2015, this seems more relevant:

<xsl:function name="xx:fixCase">
    <xsl:param name="text" />
    <xsl:for-each select="tokenize($text,' ')">
        <xsl:value-of select="upper-case(substring(.,1,1))" />
        <xsl:value-of select="lower-case(substring(.,2))" />
        <xsl:if test="position() ne last()">
            <xsl:text> </xsl:text>
        </xsl:if>
    </xsl:for-each>
</xsl:function>

Where 'xx' is your own namespace.

Richard Kennard
  • 1,325
  • 11
  • 20
2

You can also try this:

http://www.xsltfunctions.com/xsl/functx_camel-case-to-words.html

 <xsl:function name="functx:camel-case-to-words" as="xs:string"
                  xmlns:functx="http://www.functx.com">
      <xsl:param name="arg" as="xs:string?"/>
      <xsl:param name="delim" as="xs:string"/>

      <xsl:sequence select="
       concat(substring($arg,1,1),
                 replace(substring($arg,2),'(\p{Lu})',
                            concat($delim, '$1')))
     "/>

</xsl:function>

and backwards: http://www.xsltfunctions.com/xsl/functx_words-to-camel-case.html

<xsl:function name="functx:words-to-camel-case" as="xs:string"
              xmlns:functx="http://www.functx.com">
  <xsl:param name="arg" as="xs:string?"/>

  <xsl:sequence select="
     string-join((tokenize($arg,'\s+')[1],
       for $word in tokenize($arg,'\s+')[position() > 1]
       return functx:capitalize-first($word))
      ,'')
 "/>

</xsl:function>
0

This same function on XQUERY:

function Xquery to Camel Case.

declare function xf:toCamelCase($text as xs:string?) as xs:string{
    if(contains($text,' ')) then
        fn:concat(xf:CamelCaseWord(substring-before($text,' ')),' ', xf:toCamelCase(substring-after($text,' ')))
    else
        xf:CamelCaseWord($text)
};

declare function xf:CamelCaseWord($text as xs:string?) as xs:string{
    fn:concat( translate(substring($text,1,1),'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'), 
                translate(substring($text,2,string-length($text)-1),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'))
};
0

A very short solution, utilising EXSLT's split() function:

<xsl:variable name='text' select='"dInEsh sAchdeV kApil Muk"' />
<xsl:variable name='lowers' select='"abcdefghijklmnopqrstuvwxyz"' />
<xsl:variable name='uppers' select='"ABCDEFGHIJKLMNOPQRSTUVWXYZ"' />

<xsl:template match="/">

    <xsl:for-each select='str:split($text, " ")'>
        <xsl:value-of select='concat(
            translate(substring(., 1, 1), $lowers, $uppers),
            translate(substring(., 2), $uppers, $lowers),
            " "
        )' />
    </xsl:for-each>
</xsl:template>

Working demo: http://www.xmlplayground.com/CNmKdF

Mitya
  • 33,629
  • 9
  • 60
  • 107
0

It can be achieved using single line expression. Try out this

string-join(for $x in tokenize($text,'\s') return concat(upper-case(substring($x, 1, 1)), lower-case(substring($x, 2))),' ')

Note - This has been verified with Orbeon Forms formula.

deadshot
  • 8,881
  • 4
  • 20
  • 39
0

I ended up with something a tiny bit different. Using another answer I got:

A sequence of more than one item is not allowed as the result of a call to toddmo:proper-case#1 ("Hey", " ")

so this fixes that.

<xsl:stylesheet xmlns:toddmo="https://todd.mo"

<xsl:function name="toddmo:proper-case" as="xs:string">
    <xsl:param name="text" />
    <xsl:variable name="words">
        <xsl:for-each select="tokenize($text,' ')">
            <xsl:value-of select="concat(upper-case(substring(.,1,1)),lower-case(substring(.,2)))"/>
            <xsl:if test="position()!=last()">
                <xsl:text> </xsl:text>                    
            </xsl:if>
        </xsl:for-each>
    </xsl:variable>
    <xsl:value-of select="string-join($words,'')"/>
</xsl:function>

test

<xsl:value-of select="toddmo:proper-case('hI my name is toddmo')"/>

output

Hi My Name Is Toddmo

toddmo
  • 20,682
  • 14
  • 97
  • 107