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