Uppercase 1st Charcter Of Each Word Of Text And Lowercase The Rest

I find this script useful sometimes:


use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"

set aString to "uppercase 1st charcter of each word of text and lowercase the rest"

set theWords to words of aString
set {resultString, aCount} to {"", count of theWords}

repeat with i from 1 to aCount
	set aWord to item i of theWords
	set aWordFirstLetter to my uppercaseString(aWord's character 1)
	if (count of aWord) > 1 then
		set aWordRest to my lowercaseString((aWord's characters 2 thru -1) as text)
	else
		set aWordRest to ""
	end if
	set resultString to resultString & aWordFirstLetter & aWordRest
	if i < aCount then set resultString to resultString & " "
end repeat
return resultString

on lowercaseString(aString)
	return (current application's NSString's stringWithString:aString)'s lowercaseString() as text
end lowercaseString

on uppercaseString(aString)
	return (current application's NSString's stringWithString:aString)'s uppercaseString() as text
end uppercaseString

Hi KniazidisR.

NSStrings also have capitalizedString() and localizedCapitalizedString() properties, which may be of interest to you (although they don’t do very well with “1st”). The documentation recommends the latter for text presented to a user.

Thanks, localized version (here I fixed “1St” bug):


use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"

set aString to "uppercase 1st chArActer of each word of text and lowercase tHe rest"
set {theWords, resultString} to {words of aString, ""}
set aCount to count of theWords

repeat with i from 1 to aCount
	set aWord to item i of theWords
	try
		set aFirstChar to (character 1 of aWord) as number
		set aWord to my localizedLowercaseString(aWord)
	on error
		set aWord to my localizedCapitalizedString(aWord)
	end try
	set resultString to resultString & aWord
	if i < aCount then set resultString to resultString & " "
end repeat
return resultString

on localizedCapitalizedString(aString)
	set aNSString to current application's NSString's stringWithString:aString
	set aString to aNSString's localizedCapitalizedString() as text
end localizedCapitalizedString

on localizedLowercaseString(aString)
	set aNSString to current application's NSString's stringWithString:aString
	set aString to aNSString's localizedLowercaseString() as text
end localizedLowercaseString

NOTE: Without fixing the bug with none letter type beginning of words all is much simpler:


use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"

set aString to "uppercase 1st charcter of each word of text and lowercase the rest"
set aNSString to current application's NSString's stringWithString:aString
set aString to aNSString's localizedCapitalizedString() as text

I thought I’d give this a try with basic AppleScript. I timed this script and the result was 5.7 milliseconds, and I timed KniazidisR’s script from post 3 and the result was 1.6 milliseconds.

set aString to "uppercase 1st character of each WORD and lowercase the rest"

set upperCase to "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
set lowerCase to "abcdefghijklmnopqrstuvwxyz"

set aString to every character in aString

set spaceBefore to true

set modifiedString to {}
repeat with aCharacter in aString
	set aCharacter to aCharacter as text
	if aCharacter = " " then
		set the end of modifiedString to aCharacter
		set spaceBefore to true
	else if spaceBefore then
		if aCharacter is in lowerCase then
			set aCharacter to (item (offset of aCharacter in lowerCase) of upperCase)
		end if
		set the end of modifiedString to aCharacter
		set spaceBefore to false
	else
		if aCharacter is in upperCase then
			set aCharacter to (item (offset of aCharacter in upperCase) of lowerCase)
		end if
		set end of modifiedString to aCharacter
		set spaceBefore to false
	end if
end repeat

modifiedString as text

It could be more efficient to do it the simple way anyway and then check to see if any adjustments are required:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions


set aString to "uppercase 1st chArActer of each word of text and lowercase tHe rest"

set aNSString to current application's NSString's stringWithString:aString
set aNSString to aNSString's localizedCapitalizedString()'s mutableCopy()

-- After getting the capitalized string, use a regex to locate any runs of digits followed by letters:
set aRegex to current application's NSRegularExpression's regularExpressionWithPattern:("\\d++[[:alpha:]]++") options:(0) |error|:(missing value)
set matchRanges to (aRegex's matchesInString:(aNSString) options:(0) range:({0, aNSString's |length|()}))'s valueForKey:("range")
-- If any are found, replace them with lower-case versions.
repeat with i from (count matchRanges) to 1 by -1
	set thisRange to item i of matchRanges
	set matchedWord to (aNSString's substringWithRange:(thisRange))
	tell aNSString to replaceCharactersInRange:(thisRange) withString:(matchedWord's localizedLowercaseString())
end repeat

return aNSString as text

Thanks, Nigel. Your script is really 1.5 times faster than mine. But there is no cause for concern.

As I checked the simple AppleScript code from Peavine is not faster, but 6 times slower than mine and 9 times slower than yours. In addition, it will not work with localized strings. I tested with x100 repetitions.

KniazidisR. The times shown in my post were backwards–they should have been 2 milliseconds for Nigel’s version of your script and 8 milliseconds for mine. Thanks for noting that. I didn’t know Script Geek would run scripts with AObjC, so I reran my tests using Script Geek and the times were:

A 12-word string:

Nigel/KniazidisR - 1.5 milliseconds
Peavine - 7.1 milliseconds

A 123-word string:

Nigel/KniazidisR - 3.2 milliseconds
Peavine - 82.6 milliseconds

Edited: The occurrences of “???” below were actually an emoji character, but the site software doesn’t seem to like them. See Nigel’s version below.

You can speed it up even more by eliminating the repeat loop:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

set aString to "uppercase 1st chArActer of each word of text and lowercase tHe rest"

set aNSString to current application's NSString's stringWithString:aString
set aNSString to aNSString's stringByReplacingOccurrencesOfString:"(\\d++)([[:alpha:]]++)" withString:"????$1????A$2" options:(current application's NSRegularExpressionSearch) range:{0, aNSString's |length|()}
set aNSString to aNSString's localizedCapitalizedString()
set aNSString to aNSString's stringByReplacingOccurrencesOfString:"????(\\d++)????A" withString:"$1" options:(current application's NSRegularExpressionSearch) range:{0, aNSString's |length|()}
return aNSString as text

This assumes the pattern ???\d++???[[:alpha:]]++ doesn’t appear in the string, and I think that’s a reasonable assumption for any string you’re trying to convert to title-case :slight_smile: But you could make the sentinal characters more obscure if you wished.

For the simple example string above, it takes less than a third as long as the repeat-loop code, and I suspect the gains would grow if there were more matches.

:cool: :slight_smile:

It doesn’t actually work with the question marks. They need to be escaped in the second regex pattern! (“\?\?\?\?”, “\?{4}”, or “\Q???\E”.) :wink: Here’s a repost using site-friendly code, although the actual emojis look better in an editor! :slight_smile:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

set aString to "uppercase 1st chArActer of each word of text and lowercase tHe rest"

set aNSString to current application's NSString's stringWithString:aString
set sentinel to (character id 129347) -- Whiskey glass.
set aNSString to aNSString's stringByReplacingOccurrencesOfString:"(\\d++)([[:alpha:]]++)" withString:(sentinel & "$1" & sentinel & "A$2") options:(current application's NSRegularExpressionSearch) range:{0, aNSString's |length|()}
set aNSString to aNSString's localizedCapitalizedString()
set aNSString to aNSString's stringByReplacingOccurrencesOfString:(sentinel & "(\\d++)" & sentinel & "A") withString:"$1" options:(current application's NSRegularExpressionSearch) range:{0, aNSString's |length|()}
return aNSString as text

I reran my timing tests with a string containing 60 words using the latest version of Script Geek. I tested my script (because its basic AppleScript) and scripts written by KniazidisR and Shane, both of which were edited by Nigel. The results were:

POST NUMBER - POSTED BY - MILLISECONDS

4 - Peavine - 33.8

5 - Nigel - 2.2

9 - Nigel - 0.6

Just as a check, I ran all three scripts in a timing script posted some time ago by Nigel and the results were close to those returned by Script Geek.