?
Solved

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

Posted on 2006-04-13
18
Medium Priority
?
1,746 Views
Last Modified: 2008-01-09
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
Comment
Question by:jmc430
  • 13
  • 5
18 Comments
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16449677
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16449711
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16449738
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
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:jmc430
ID: 16449942
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16449959
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16449983
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16450004
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
 

Author Comment

by:jmc430
ID: 16450079
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16450092
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16450098
Can you check the Xalan version you are using?
I don't have this problem with the latest

cheers
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16450123
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16451808

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

Author Comment

by:jmc430
ID: 16453533
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
 

Author Comment

by:jmc430
ID: 16453837
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
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16459662
> 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
 
LVL 60

Accepted Solution

by:
Geert Bormans earned 2000 total points
ID: 16459681
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
 

Author Comment

by:jmc430
ID: 16480246
Thanks so much Geert!  You're awesome!!!
0
 
LVL 60

Expert Comment

by:Geert Bormans
ID: 16480386
you are welcome
0

Featured Post

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

The Problem How to write an Xquery that works like a SQL outer join, providing placeholders for absent data on the outer side?  I give a bit more background at the end. The situation expressed as relational data Let’s work through this.  I’ve …
I was working on a PowerPoint add-in the other day and a client asked me "can you implement a feature which processes a chart when it's pasted into a slide from another deck?". It got me wondering how to hook into built-in ribbon events in Office.
this video summaries big data hadoop online training demo (http://onlineitguru.com/big-data-hadoop-online-training-placement.html) , and covers basics in big data hadoop .
This lesson discusses how to use a Mainform + Subforms in Microsoft Access to find and enter data for payments on orders. The sample data comes from a custom shop that builds and sells movable storage structures that are delivered to your property. …
Suggested Courses
Course of the Month15 days, left to enroll

840 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question