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



jmc430Asked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Gertone (Geert Bormans)Information 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!
Gertone (Geert Bormans)Information 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



Gertone (Geert Bormans)Information 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>
Microsoft Azure 2017

Azure has a changed a lot since it was originally introduce by adding new services and features. Do you know everything you need to about Azure? This course will teach you about the Azure App Service, monitoring and application insights, DevOps, and Team Services.

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
Gertone (Geert Bormans)Information 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
Gertone (Geert Bormans)Information 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
Gertone (Geert Bormans)Information 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
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
Gertone (Geert Bormans)Information 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
Gertone (Geert Bormans)Information ArchitectCommented:
Can you check the Xalan version you are using?
I don't have this problem with the latest

cheers
Gertone (Geert Bormans)Information 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
Gertone (Geert Bormans)Information ArchitectCommented:

> xmlns:map="urn:internal" exclude-result-prefixes="map">
note that I forgot to change the namespace in the above solution
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
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
Gertone (Geert Bormans)Information 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
Gertone (Geert Bormans)Information 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

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
jmc430Author Commented:
Thanks so much Geert!  You're awesome!!!
Gertone (Geert Bormans)Information ArchitectCommented:
you are welcome
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
XML

From novice to tech pro — start learning today.