• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1783
  • Last Modified:

Capitalize First Letter of All Words EXCEPT Conjunction (And, or, etc.)

Greetings,

Is there a way to capitalize the first letters of all words, excluding conjunctions and modifiers (grammatical elements) ?

For example, I have element tags like this:

<title>Objectives of the FINE DOCUMENT</title>

Currently I am using this:

<span style="text-transform: capitalize;">
<xsl:apply-templates select="child::title" mode="outIt"/>
</span>

However, this renders the title tag in an undesirable manner:

"Objectives Of The Fine Document"                        <= incorrect

This is ideally what I would like to have outputted:

"Objectives of the Fine Document"                         <= how do i achieve this?

Is this possible?  I have scoured the web in search of an XSLT solution, to no avail.  

Please help me!  Thank you so much for any advice or guidance.

Best regards,
Jamie



0
jmc430
Asked:
jmc430
  • 13
  • 5
1 Solution
 
Geert BormansInformation ArchitectCommented:
Hi jmc430,

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:map="urn:internal" exclude-result-prefixes="map">
    <xsl:template match="/">
        <xsl:call-template name="titleize">
            <xsl:with-param name="title" select="normalize-space(//title)"/>
        </xsl:call-template>
    </xsl:template>
   
    <xsl:template name="titleize">
        <xsl:param name="title"/>
        <xsl:choose>
            <xsl:when test="contains($title, ' ')">
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="substring-before($title, ' ')"/>
                </xsl:call-template>
                <xsl:text> </xsl:text>
                <xsl:call-template name="titleize">
                    <xsl:with-param name="title" select="substring-after($title, ' ')"></xsl:with-param>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="$title"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
   
    <xsl:template name="upperFirst">
        <xsl:param name="word"/>
        <xsl:choose>
            <xsl:when test="document('')//map:lower/map:entry[text() = $word]">
                <xsl:value-of select="translate($word,'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="translate(substring($word, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
                <xsl:value-of select="translate(substring($word, 2 , string-length($word) - 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
   
    <map:lower>
        <map:entry>of</map:entry>
        <map:entry>for</map:entry>
        <map:entry>the</map:entry>
        <map:entry>and</map:entry>
        <map:entry>it</map:entry>
        <map:entry>on</map:entry>
    </map:lower>
</xsl:stylesheet>

Cheers!
0
 
Geert BormansInformation ArchitectCommented:
Jamie,

I used this XML to test
<?xml version="1.0" encoding="UTF-8"?>
<title>one for the road</title>

what you need from my XSLT to plug it in your XSLT
- the two named templates
- the lookup table (map: elements), put it before closing the stylesheet
- these two attributes in the stylesheet element

note that I normalize-space the argument that I pass to the template
I do that to make sure that I don't have to deal with
- trailing spaces
- leading spaces
- multiple spaces in a row, newlines, tabs
This normalisation is usually acceptable in titles and it saves me a lot of extra testing

I decided to put all the words you don't want capitalised in a lookup table,
so you can simply add your own
like this
        <map:entry>me</map:entry>
        <map:entry>you</map:entry>
etc...
nice if you want to make this language specific



0
 
Geert BormansInformation ArchitectCommented:
Jaime,

please note that you might want to protect acronyms such as BBC

here is how to change the XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:map="urn:internal" exclude-result-prefixes="map">
    <xsl:template match="/">
        <xsl:call-template name="titleize">
            <xsl:with-param name="title" select="normalize-space(//title)"/>
        </xsl:call-template>
    </xsl:template>
   
    <xsl:template name="titleize">
        <xsl:param name="title"/>
        <xsl:choose>
            <xsl:when test="contains($title, ' ')">
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="substring-before($title, ' ')"/>
                </xsl:call-template>
                <xsl:text> </xsl:text>
                <xsl:call-template name="titleize">
                    <xsl:with-param name="title" select="substring-after($title, ' ')"></xsl:with-param>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="$title"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
   
    <xsl:template name="upperFirst">
        <xsl:param name="word"/>
        <xsl:choose>
            <xsl:when test="document('')//map:lower/map:entry[text() = $word]">
                <xsl:value-of select="translate($word,'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:when>
            <xsl:when test="document('')//map:upper/map:entry[text() = $word]">
                <xsl:value-of select="translate($word, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' )"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="translate(substring($word, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
                <xsl:value-of select="translate(substring($word, 2 , string-length($word) - 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
   
    <map:lower>
        <map:entry>of</map:entry>
        <map:entry>for</map:entry>
        <map:entry>the</map:entry>
        <map:entry>and</map:entry>
        <map:entry>it</map:entry>
        <map:entry>on</map:entry>
    </map:lower>

    <map:upper>
        <map:entry>BBC</map:entry>
        <map:entry>TVA</map:entry>
        <map:entry>BA</map:entry>
        <map:entry>BEA</map:entry>
    </map:upper>
   
</xsl:stylesheet>
0
Cloud Class® Course: Microsoft Exchange Server

The MCTS: Microsoft Exchange Server 2010 certification validates your skills in supporting the maintenance and administration of the Exchange servers in an enterprise environment. Learn everything you need to know with this course.

 
jmc430Author Commented:
Hi Gertone!

Thanks for your response!  For some reason, when I try to render your code, I get the following error:

javax.servlet.ServletException: javax.xml.transform.TransformerConfigurationException: javax.xml.transform.TransformerException: javax.xml.transform.TransformerException: Extra illegal tokens: 'urn:internal', ':', 'lower', '/', 'urn:internal', ':', 'entry', '[', 'text', '(', ')', '=', '$', 'word', ']'


I basically tried using the stylesheet you provided along with the test xml file... is this something that my system environment does not support?

Thank you so much for all your help.  I really appreciate it!

Best regards,
Jamie
0
 
Geert BormansInformation ArchitectCommented:
Jaimie,

I am not a Java Programmer,
so it is abit hard to track this down.

Some transformers require explicit enabling of the use of the document('') function.
maybe you can check that first
(it is, is there a property defined for enabling the document() function)

other than that, there is nothing fancy in the stylesheet
0
 
Geert BormansInformation ArchitectCommented:
jmc430,

maybe, the parser doesn't like this form
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:map="urn:internal" exclude-result-prefixes="map">

try this
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:map="http://www.internal.net/mapping" exclude-result-prefixes="map">

cheers
0
 
Geert BormansInformation ArchitectCommented:
jmc430,

Yep, did some research
and aparently this is a "feature" in an older version of XALAN-J
just change the namespace URI for map and you are OK... I hope :-)

cheers

Geert
0
 
jmc430Author Commented:
Hi Geert!

I corrected the namespace URI for map as you suggested (and that works now!)  :).

However, I am getting another error:
; Line#: 37; Column#: 80
javax.servlet.ServletException: Unknown error in XPath

basically from this line in template match="upperFirst":

 <xsl:when test="document('')//map:lower/map:entry[text() = $word]">

It says something about a null value - when I replace document('') with document('.') it doesn't output errors, but the capitalization does not work properly.

What could I be doing wrong?

Thanks so much for helping me!  

Best regards,
Jamie
0
 
Geert BormansInformation ArchitectCommented:
document('') points to the XSLT file itself
document('.') would point to a document that has the content of the current context as a URI... likely not existing

it might be an issue with Xalan-J and the document function

try adding a space in between the two apostrophes
0
 
Geert BormansInformation ArchitectCommented:
Can you check the Xalan version you are using?
I don't have this problem with the latest

cheers
0
 
Geert BormansInformation ArchitectCommented:
If we can't get this to work,
we can put the lookup table in a seperate file

like this
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:map="urn:internal" exclude-result-prefixes="map">
    <xsl:import href="mapping.xsl"/>
    <xsl:template match="/">
        <xsl:call-template name="titleize">
            <xsl:with-param name="title" select="normalize-space(//title)"/>
        </xsl:call-template>
    </xsl:template>
   
    <xsl:template name="titleize">
        <xsl:param name="title"/>
        <xsl:choose>
            <xsl:when test="contains($title, ' ')">
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="substring-before($title, ' ')"/>
                </xsl:call-template>
                <xsl:text> </xsl:text>
                <xsl:call-template name="titleize">
                    <xsl:with-param name="title" select="substring-after($title, ' ')"></xsl:with-param>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="$title"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
   
    <xsl:template name="upperFirst">
        <xsl:param name="word"/>
        <xsl:choose>
            <xsl:when test="document('mapping.xsl')//map:lower/map:entry[text() = $word]">
                <xsl:value-of select="translate($word,'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:when>
            <xsl:when test="document('mapping.xsl')//map:upper/map:entry[text() = $word]">
                <xsl:value-of select="translate($word, 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' )"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="translate(substring($word, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
                <xsl:value-of select="translate(substring($word, 2 , string-length($word) - 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>

</xsl:stylesheet>

save the following in file ''mapping.xsl' in the same directory as the above XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:map="urn:internal" exclude-result-prefixes="map">
   <map:lower>
        <map:entry>of</map:entry>
        <map:entry>for</map:entry>
        <map:entry>the</map:entry>
        <map:entry>and</map:entry>
        <map:entry>it</map:entry>
        <map:entry>on</map:entry>
    </map:lower>
    <map:upper>
        <map:entry>BBC</map:entry>
        <map:entry>TVA</map:entry>
        <map:entry>BA</map:entry>
        <map:entry>BEA</map:entry>
    </map:upper>
 </xsl:stylesheet>

cheers
0
 
Geert BormansInformation ArchitectCommented:

> xmlns:map="urn:internal" exclude-result-prefixes="map">
note that I forgot to change the namespace in the above solution
0
 
jmc430Author Commented:
Hi Geert,

I tried and have successfully gotten the files to load properly... yay!

However, the transformations are not working - the capitalization is still taking place for all words, including the words in the map xsl file..

Am I implementing your solution incorrectly?  

Thanks for letting me know.

Best regards,
Jamie
0
 
jmc430Author Commented:
Hi Geert,

Is there a way to test for the string length and then make the first letter capitalized if and only if the string length of the word is over 3 characters?  I do not really understand how to parse through all the words embedded within the element tags - is there a for-each statement to do this?

I am attempting to do something like this:

<xsl:param name="maxlength" select="3"/>

<xsl:template match="title"  mode="checkup" name="lengthcheck">
  <xsl:param name="string" select="string(.)"/>
 
  <xsl:variable name="found"
    select="string-length($thisline) &lt;= $maxlength"/>
  <xsl:if test="$found">
    <xsl:value-of select="translate($found,'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
    <xsl:copy-of select="."/>
  </xsl:if>
  <xsl:if test="contains($string, '&#xA;') and not($found)">
<span style="text-transform: capitalize;">
<xsl:apply-templates select="child::title" mode="outIt"/>
</span>
  </xsl:if>
</xsl:template>

but this doesn't work ... what am I doing wrong?

I really appreciate you helping me!

Thanks!
Jamie
0
 
Geert BormansInformation ArchitectCommented:
> Am I implementing your solution incorrectly?
I suppose so, it works like a charm at this end...

Are you using the version with the import?
it is likely that the test for the lowercase words fails because of the document function
if that function doesn't find the mapping.xsl, the test will fail

did you name the file "mapping.xsl"?

Anyway, I can imagine that you prefer a solution with a cut-off word length

> I do not really understand how to parse through all the words embedded within the element tags - is there a for-each statement to do this?

no, you need to do that recursively, as I did in my first solution,
take the string, pass it to a named template, chop off the first word
see if it needs uppercasing
and pass the remainder of the string back to the named template
That is the only way in XSLT

here is a new version of the old XSLT

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   
    <xsl:param name="maxLength">3</xsl:param>
   
    <xsl:template match="/">
        <xsl:call-template name="titleize">
            <xsl:with-param name="title" select="normalize-space(//title)"/>
        </xsl:call-template>
    </xsl:template>
   
    <xsl:template name="titleize">
        <xsl:param name="title"/>
        <xsl:choose>
            <xsl:when test="contains($title, ' ')">
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="substring-before($title, ' ')"/>
                </xsl:call-template>
                <xsl:text> </xsl:text>
                <xsl:call-template name="titleize">
                    <xsl:with-param name="title" select="substring-after($title, ' ')"></xsl:with-param>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="$title"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
   
    <xsl:template name="upperFirst">
        <xsl:param name="word"/>
        <xsl:choose>
            <xsl:when test="string-length($word) &lt;= $maxLength">
                <xsl:value-of select="translate($word,'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="translate(substring($word, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
                <xsl:value-of select="translate(substring($word, 2 , string-length($word) - 1), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>

It lowercases the words smaller than or equal to 3 characters,
which means BBC would become bbc
you can decide to leave the small words unchanged
for that
change this part
            <xsl:when test="string-length($word) &lt;= $maxLength">
                <xsl:value-of select="translate($word,'ABCDEFGHIJKLMNOPQRSTUVWXYZ' , 'abcdefghijklmnopqrstuvwxyz')"/>
            </xsl:when>
into
            <xsl:when test="string-length($word) &lt;= $maxLength">
                <xsl:value-of select="$word"/>
            </xsl:when>

this stylesheet gives the following result from my original test file
"one for the Road"
I assume you want the first word uppercased anyhow

I will post an example in a minute where I have special treatment for the first word
and where I will leave all characters other then the first alone
(so I don't lowercase the remainder)

you will then have enough variants to pick your prefered parts

cheers
0
 
Geert BormansInformation ArchitectCommented:
As promised

I just pass an extra boolean to see it is the first word in the sentence or not
note: I use the "1 = 1" to generate a boolean true

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   
    <xsl:param name="maxLength">3</xsl:param>
   
    <xsl:template match="/">
        <xsl:call-template name="titleize">
            <xsl:with-param name="title" select="normalize-space(//title)"/>
            <xsl:with-param name="firstWord" select="1 = 1"/>
        </xsl:call-template>
    </xsl:template>
   
    <xsl:template name="titleize">
        <xsl:param name="title"/>
        <xsl:param name="firstWord"/>
        <xsl:choose>
            <xsl:when test="contains($title, ' ')">
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="substring-before($title, ' ')"/>
                    <xsl:with-param name="firstWord" select="$firstWord"/>
                </xsl:call-template>
                <xsl:text> </xsl:text>
                <xsl:call-template name="titleize">
                    <xsl:with-param name="title" select="substring-after($title, ' ')"></xsl:with-param>
                    <xsl:with-param name="firstWord" select="1 = 2"/>
                </xsl:call-template>
            </xsl:when>
            <xsl:otherwise>
                <xsl:call-template name="upperFirst">
                    <xsl:with-param name="word" select="$title"/>
                    <xsl:with-param name="firstWord" select="$firstWord"/>
                </xsl:call-template>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
   
    <xsl:template name="upperFirst">
        <xsl:param name="word"/>
        <xsl:param name="firstWord"/>
        <xsl:choose>
            <xsl:when test="string-length($word) &lt;= $maxLength and not($firstWord)">
                <xsl:value-of select="$word"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="translate(substring($word, 1, 1), 'abcdefghijklmnopqrstuvwxyz', 'ABCDEFGHIJKLMNOPQRSTUVWXYZ')"/>
                <xsl:value-of select="substring($word, 2 , string-length($word) - 1)"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:template>
</xsl:stylesheet>


cheers

Geert
0
 
jmc430Author Commented:
Thanks so much Geert!  You're awesome!!!
0
 
Geert BormansInformation ArchitectCommented:
you are welcome
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

Join & Write a Comment

Featured Post

Cloud Class® Course: Certified Penetration Testing

This CPTE Certified Penetration Testing Engineer course covers everything you need to know about becoming a Certified Penetration Testing Engineer. Career Path: Professional roles include Ethical Hackers, Security Consultants, System Administrators, and Chief Security Officers.

  • 13
  • 5
Tackle projects and never again get stuck behind a technical roadblock.
Join Now