How to format a string to Pascal case in XSLT?

5.9k Views Asked by At

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?

5

There are 5 best solutions below

3
On BEST ANSWER

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

0
On

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.

1
On

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?

1
On

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) &gt; 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. ;-)

0
On

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>