tags:

views:

480

answers:

3

Hello,

Is it possible to split a tag at lower to upper case boundaries i.e. for example, tag 'UserLicenseCode' should be converted to 'User License Code' so that the column headers look a little nicer.

I've done something like this in the past using Perl's regular expressions, but XSLT is a whole new ball game for me.

Any pointers in creating such a template would be greatly appreciated!

Thanks Krishna

+1  A: 

Using recursion, it is possible to walk through a string in XSLT to evaluate every character. To do this, create a new template which accepts only one string parameter. Check the first character and if it's an uppercase character, write a space. Then write the character. Then call the template again with the remaining characters inside a single string. This would result in what you want to do.

That would be your pointer. I will need some time to work out the template. :-)


It took some testing, especially to get the space inside the whole thing. (I misused a character for this!) But this code should give you an idea...

I used this XML:

<?xml version="1.0" encoding="UTF-8"?>
<blah>UserLicenseCode</blah>

and then this stylesheet:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format"&gt;
    <xsl:output method="text"/>
    <xsl:variable name="Space">*</xsl:variable>
    <xsl:template match="blah">
    <xsl:variable name="Split">
     <xsl:call-template name="Split">
      <xsl:with-param name="Value" select="."/>
      <xsl:with-param name="First" select="true()"/>
     </xsl:call-template></xsl:variable>
     <xsl:value-of select="translate($Split, '*', ' ')" />
    </xsl:template>
    <xsl:template name="Split">
     <xsl:param name="Value"/>
     <xsl:param name="First" select="false()"/>
     <xsl:if test="$Value!=''">
      <xsl:variable name="FirstChar" select="substring($Value, 1, 1)"/>
      <xsl:variable name="Rest" select="substring-after($Value, $FirstChar)"/>
       <xsl:if test="not($First)">
        <xsl:if test="translate($FirstChar, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', '..........................')= '.'">
         <xsl:value-of select="$Space"/>
        </xsl:if>
       </xsl:if>
       <xsl:value-of select="$FirstChar"/>
       <xsl:call-template name="Split">
        <xsl:with-param name="Value" select="$Rest"/>
       </xsl:call-template>
     </xsl:if>
    </xsl:template>
</xsl:stylesheet>

and I got this as result:

User License Code

Do keep in mind that spaces and other white-space characters do tend to be stripped away from XML, which is why I used an '*' instead, which I translated to a space.

Of course, this code could be improved. It's what I could come up with in 10 minutes of work. In other languages, it would take less lines of code but in XSLT it's still quite fast, considering the amount of code lines it contains.

Workshop Alex
Thanks! Works like a charm. I didn't see this post before I posted my solution. This one handles boundary conditions better.Thanks for the quick response!
Krishna
A: 

I think I got it, but not sure if this is the best way..

  0">

It works fine, but may not be efficient as it loops over each character in the input string.

Krishna
A: 

An XSLT + FXSL solution (in XSLT 2.0, but almost the same code will work with XSLT 1.0 and FXSL 1.x:

<xsl:stylesheet version="2.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="http://fxsl.sf.net/"
xmlns:testmap="testmap"
exclude-result-prefixes="f testmap"
>
   <xsl:import href="../f/func-str-dvc-map.xsl"/>
   <testmap:testmap/>

   <xsl:output omit-xml-declaration="yes" indent="yes"/>

   <xsl:template match="/">
     <xsl:variable name="vTestMap" select="document('')/*/testmap:*[1]"/>

     '<xsl:value-of select="f:str-map($vTestMap, 'UserLicenseCode')"
       />'
   </xsl:template>

    <xsl:template name="mySplit" match="*[namespace-uri() = 'testmap']"
     mode="f:FXSL">
      <xsl:param name="arg1"/>

      <xsl:value-of select=
       "if(lower-case($arg1) ne $arg1)
         then concat(' ', $arg1)
         else $arg1
       "/>
    </xsl:template>
</xsl:stylesheet>

When the above transformation is applied on any source XML document (not used), the expected correct result is produced:

' User License Code'

Do note:

  1. We are using the DVC version of the FXSL function/template str-map(). This is a Higher-order function (HOF) which takes two arguments: another function and a string. str-map() applies the function on every character of the string and returns the concatenation of the results.

  2. Because the lower-case() function is used (in the XSLT 2.0 version), we are not constrained to only the Latin alphabet.

Dimitre Novatchev