(* SmartString 1.31 Copyright 1994-2021 by Jon Pugh Jon's Home Page The best way to use this script is to store it compiled in your "~/Library/Script Libraries" folder. Then paste this into your script: use scripting additions use libSmartString : script "SmartString" property SmartString : libSmartString's SmartString tell SmartString setString("Your text here") -- do stuff end tell SmartString is an AppleScript script object which knows how to perform numerous operations on strings. The complete list of operations are: newString(aString) --> script object factory method getString() --> string (only reads) setString(aString) --> string setList(aList, seperator) --> string convertListToString(aList, delim) --> string (only reads) containsString(aString) --> boolean (only reads) subString(x, y) --> string (only reads) beforeString(subString) --> string (only reads) afterString(subString) --> string (only reads) betweenStrings(afterThis, beforeThis) --> string (only reads) betweenStringsIncluding(afterThis, beforeThis) --> string (only reads) partition(subString) --> {string, string, string} (only reads) appendString(aString) --> string prependString(aString) --> string insertBefore(subString, thisString) --> string insertAfter(subString, thisString) --> string deleteCharacters(x, y) --> string deleteString(subString) --> string deleteBefore(beforeString) --> string deleteAfter(afterString) --> string deleteBetween(beforeThis, afterThis) --> string deleteBetweenIncluding(afterThis, beforeThis) --> string keepBefore(beforeString) --> string keepAfter(afterString) --> string keepBetween(beforeThis, afterThis) --> string replaceOneString(thisStr, thatStr) --> string replaceString(subString, withString) --> string replaceStrings(subString, withString) --> string replaceBetween(frontTag, rearTag, newValue) --> string getTokens(delim) --> list (only reads) setTokens(aList, delim) --> string beforeToken(token, delim) --> string (only reads) afterToken(token, delim) --> string (only reads) getTokenRange(startIndex, endIndex, delim) --> list (only reads) deleteTokenRange(startIndex, endIndex, delim) --> string keepTokenRange(startIndex, endIndex, delim) --> string firstToken(delim) --> string (only reads) deleteFirstToken(delim) --> string keepFirstToken(delim) --> string lastToken(delim) --> string (only reads) deleteLastToken(delim) --> string keepLastToken(delim) --> string NthToken(n, delim) --> string (only reads) deleteNthToken(n, delim) --> string keepNthToken(n, delim) --> string smartenQuotes() --> string trimWhitespace() --> string -- these use AppleScriptObjC, which requires some support from script runner uppercase() --> string lowercase() --> string titlecase() --> string -- these are pure AppleScript, much more limited, but don't require anything uppercase_pureAS() --> string lowercase_pureAS() --> string titlecase_pureAS() --> string filename() --> string (only reads) dirPath() --> string (only reads) fileExtension() --> string (only reads) fileBasename() --> string (only reads) *) use scripting additions script SmartString use framework "Foundation" property parent : AppleScript property theString : "" on newString(aString) --> script object factory method copy me to anObject anObject's setString(aString) return anObject end newString on getString() --> string (only reads) return theString end getString on setString(aString) --> string set theString to aString as string end setString on setList(aList, seperator) --> string set theString to convertListToString(aList, seperator) end setList on subString(x, y) --> string (only reads) return text x thru y of theString end subString on containsString(aString) set foo to offset of aString in theString return foo > 0 end containsString on beforeString(aString) --> string (only reads) set foo to offset of aString in theString if foo ≤ 1 then return "" else return text 1 thru (foo - 1) of theString end if end beforeString on afterString(aString) --> string (only reads) set foo to offset of aString in theString if foo = 0 then return "" else copy foo + (length of aString) to foo if foo > length of theString then return "" else return text foo thru -1 of theString end if end if end afterString on partition(aString) --return {beforeString(aString), aString, afterString(aString)} set foo to offset of aString in theString if foo = 0 then return {theString, "", ""} else if foo = 1 then set before_str to "" else set before_str to text 1 thru (foo - 1) of theString end if copy foo + (length of aString) to foo if foo > length of theString then set after_str to "" else set after_str to text foo thru -1 of theString end if return {before_str, aString, after_str} end partition on betweenStrings(afterThis, beforeThis) --> string (only reads) set savedString to theString keepAfter(afterThis) keepBefore(beforeThis) set tempString to theString setString(savedString) return tempString end betweenStrings on betweenStringsIncluding(afterThis, beforeThis) --> string (only reads) return (afterThis & betweenStrings(afterThis, beforeThis) & beforeThis) as string end betweenStringsIncluding on appendString(aString) --> string set theString to theString & aString end appendString on prependString(aString) --> string set theString to aString & theString end prependString on replaceOneString(thisStr, thatStr) --> string set theString to beforeString(thisStr) & thatStr & afterString(thisStr) end replaceOneString on replaceString(thisStr, thatStr) -- syntax forgivenness so you don't have to remember if there is or isn't an s replaceStrings(thisStr, thatStr) -- expecting only a single replacement could be considered here, but we're not that subtle yet end replaceString on replaceStrings(thisStr, thatStr) --> string set oldDelim to AppleScript's text item delimiters set AppleScript's text item delimiters to thisStr set theList to text items of theString set AppleScript's text item delimiters to thatStr set theString to theList as string set AppleScript's text item delimiters to oldDelim return theString end replaceStrings on deleteString(aString) --> string set foo to offset of aString in theString if foo ≠ 0 then set theString to beforeString(aString) & afterString(aString) as string end if return theString end deleteString on replaceBetween(frontTag, rearTag, newValue) --> string set t1 to beforeString(frontTag) deleteBefore(frontTag) deleteString(frontTag) deleteBefore(rearTag) set theString to {t1, frontTag, newValue, theString} as string end replaceBetween on insertBefore(beforeStr, thisStr) --> string if theString contains beforeStr then set theString to {beforeString(beforeStr), thisStr, beforeStr, afterString(beforeStr)} as string else error "Could not insert before missing string." end if end insertBefore on insertAfter(afterStr, thisStr) --> string if theString contains afterStr then set theString to {beforeString(afterStr), afterStr, thisStr, afterString(afterStr)} as string else error "Could not insert after missing string." end if end insertAfter on deleteBefore(beforeStr) --> string set theString to beforeStr & afterString(beforeStr) end deleteBefore on deleteAfter(afterStr) --> string set theString to beforeString(afterStr) & afterStr end deleteAfter on deleteBetween(afterThis, beforeThis) --> string set theString to {beforeString(afterThis), afterThis, beforeThis, afterString(beforeThis)} as string end deleteBetween on deleteBetweenIncluding(afterThis, beforeThis) --> string set theString to {beforeString(afterThis), afterString(beforeThis)} as string end deleteBetweenIncluding on keepBefore(beforeStr) --> string set theString to beforeString(beforeStr) end keepBefore on keepAfter(afterStr) --> string set theString to afterString(afterStr) end keepAfter on keepBetween(afterThis, beforeThis) --> string set theString to betweenStrings(afterThis, beforeThis) end keepBetween on deleteCharacters(x, y) --> string if x > 1 then set a to text 1 thru (x - 1) of theString else set a to "" end if if y < length of theString then set b to text (y + 1) thru -1 of theString else set b to "" end if set theString to a & b end deleteCharacters on convertListToString(aList, delim) --> string (only reads) set oldSep to AppleScript's text item delimiters set AppleScript's text item delimiters to delim set aString to aList as string set AppleScript's text item delimiters to oldSep return aString end convertListToString on getTokens(delim) --> list (only reads) set oldDelim to AppleScript's text item delimiters set AppleScript's text item delimiters to delim set theList to text items of theString set AppleScript's text item delimiters to oldDelim return theList end getTokens on setTokens(aList, delim) --> string set theString to convertListToString(aList, delim) end setTokens on firstToken(delim) --> string (only reads) return NthToken(1, delim) end firstToken on lastToken(delim) --> string return NthToken(-1, delim) end lastToken on NthToken(n, delim) --> string if n = 0 then error "Cannot get delim zero" set t to getTokens(delim) if t = {} then return "" set numItems to number of items of t if n > numItems then error "Cannot get delim " & n & ", only " & numItems & " of tokens" return item n of t end NthToken on deleteNthToken(n, delim) --> string if n = 0 then error "Cannot delete token zero" set aList to getTokens(delim) set m to number of items of aList if n < 0 then set n to m + n + 1 if n < 1 then error "Cannot delete tokens before the first token" if n = 1 then set aList to items 2 thru m of aList else if n = m then set aList to items 1 thru (m - 1) of aList else set aList to (items 1 thru (n - 1) of aList) & (items (n + 1) thru m of aList) end if set oldDelim to AppleScript's text item delimiters set AppleScript's text item delimiters to delim try set theString to aList as string on error theMsg number theNum set AppleScript's text item delimiters to oldDelim error theMsg number theNum end try set AppleScript's text item delimiters to oldDelim return theString end deleteNthToken on deleteFirstToken(delim) --> string deleteNthToken(1, delim) end deleteFirstToken on deleteLastToken(delim) --> string deleteNthToken(-1, delim) end deleteLastToken on keepFirstToken(delim) --> string setString(NthToken(1, delim)) end keepFirstToken on keepLastToken(delim) --> string setString(NthToken(-1, delim)) end keepLastToken on keepNthToken(n, delim) --> string setString(NthToken(n, delim)) end keepNthToken on eachTokenContaining(thisString, delim) --> list (only reads) set theList to getTokens(delim) set newList to {} repeat with theItem in theList if contents of theItem contains thisString then copy theItem to end of newList end if end repeat return newList end eachTokenContaining on getTokenRange(startIndex, endIndex, delim) --> list (only reads) set theList to getTokens(delim) -- check parameter constraints set n to number of items of theList if abs(startIndex) > n or startIndex = 0 then error "Bad startIndex in getTokenRange" if abs(endIndex) > n or endIndex = 0 then error "Bad endIndex in getTokenRange" if startIndex > endIndex then error "startIndex > endIndex in getTokenRange" -- return sub-list return items startIndex thru endIndex of theList end getTokenRange on deleteTokenRange(startIndex, endIndex, delim) --> string set theList to getTokens(delim) -- check parameter constraints set n to number of items of theList if abs(startIndex) > n or startIndex = 0 then error "Bad startIndex in deleteTokenRange" if abs(endIndex) > n or endIndex = 0 then error "Bad endIndex in deleteTokenRange" if startIndex > endIndex then error "startIndex > endIndex in deleteTokenRange" if startIndex < 0 then set startIndex to n + startIndex + 1 if endIndex < 0 then set endIndex to n + endIndex + 1 -- return sub-lists if startIndex = 1 then if endIndex = n then set theList to {} else set theList to items (endIndex + 1) thru n of theList end if else if endIndex = n then set theList to items 1 thru (startIndex - 1) of theList else set theList to (items 1 thru (startIndex - 1) of theList) & (items (endIndex + 1) thru n of theList) end if setTokens(theList, delim) end deleteTokenRange on keepTokenRange(startIndex, endIndex, delim) --> string set theList to getTokenRange(startIndex, endIndex, delim) setTokens(theList, delim) end keepTokenRange on beforeToken(token, delim) --> string (only reads) set theList to getTokens(delim) set n to number of items of theList repeat with i from 2 to n if item i of theList = token then set aList to items 1 thru (i - 1) of theList return convertListToString(aList, delim) end if end repeat return "" end beforeToken on afterToken(token, delim) --> string (only reads) set theList to getTokens(delim) set n to number of items of theList repeat with i from 1 to (n - 1) if item i of theList = token then set aList to items (i + 1) thru n of theList return convertListToString(aList, delim) end if end repeat return "" end afterToken on trimWhitespace() --> string set lf to character id 10 set twoReturns to return & return repeat while theString contains twoReturns replaceString(twoReturns, return) end repeat set twoTabs to tab & tab repeat while theString contains twoTabs replaceString(twoTabs, tab) end repeat set twoSpaces to space & space repeat while theString contains twoSpaces replaceString(twoSpaces, space) end repeat if length of theString > 1 then repeat while character 1 of theString = tab or character 1 of theString = space or character 1 of theString = lf set theString to text 2 thru -1 of theString end repeat repeat while last character of theString = tab or last character of theString = space or last character of theString = lf set theString to text 1 thru -2 of theString end repeat end if return theString end trimWhitespace on uppercase() --> string set theString to (current application's NSString's stringWithString:theString)'s uppercaseString as item --set theString to (current application's NSString's stringWithString:theString)'s uppercaseString -- Note that S is an NSString at that point. If you run that in Script Editor and want to see the string contents, add “S as item” to the end of that script. end uppercase on lowercase() --> string set theString to (current application's NSString's stringWithString:theString)'s lowercaseString as item --set theString to (current application's NSString's stringWithString:theString)'s uppercaseString -- Note that S is an NSString at that point. If you run that in Script Editor and want to see the string contents, add “S as item” to the end of that script. end lowercase on titlecase() --> string set theString to (current application's NSString's stringWithString:theString)'s capitalizedString as item --set theString to (current application's NSString's stringWithString:theString)'s uppercaseString -- Note that S is an NSString at that point. If you run that in Script Editor and want to see the string contents, add “S as item” to the end of that script. end titlecase -- this is a list of commonly used accented chars and their uppercase equivalents -- we'll only swap these known pairs as well as the ASCII alphabet property lowers : "œæáàâäãåéèêëóòôöõúùûüíìîïñçøÿπß" property uppers : "ŒÆÁÀÂÄÃÅÉÈÊËÓÒÔÖÕÚÙÛÜÍÌÎÏÑÇØŸ∏ẞ" on uppercase_pureAS() --> string local newString set newString to "" set ncl to number of characters of lowers considering case repeat with c in characters of theString set found to false set a to id of c -- handle ascii case if a > 96 and a < 123 then set a to a - 32 set newString to newString & (character id a) set found to true end if if (not found) and (a > 128) then -- handle known accented characters repeat with lc from 1 to ncl if contents of c = character lc of lowers then set newString to newString & character lc of uppers set found to true exit repeat end if end repeat end if if not found then set newString to newString & c end if end repeat end considering set theString to newString end uppercase_pureAS on lowercase_pureAS() --> string set newString to "" set ncl to number of characters of uppers considering case repeat with c in characters of theString set found to false set a to id of c -- handle ascii case if a > 64 and a < 91 then set a to a + 32 set newString to newString & (character id a) set found to true end if if (not found) and (a > 128) then -- handle known accented characters repeat with lc from 1 to ncl if contents of c = character lc of uppers then set newString to newString & character lc of lowers set found to true exit repeat end if end repeat end if if not found then set newString to newString & c end if end repeat end considering set theString to newString end lowercase_pureAS on titlecase_pureAS() --> string -- Set All Words To Start With Upper Case set newString to "" set ncl to number of characters of uppers set firstChar to true -- start with first char considering case repeat with c in characters of theString set found to false set a to id of c -- tab, vertical tab, space, double quote, single quote, non-breaking space, left single quote, left double quote if a is in {9, 11, 32, 34, 39, 160, 2018, 8220} then set firstChar to true else -- handle ascii case if a > 64 and a < 91 and not firstChar then -- upper to lower set a to a + 32 set newString to newString & (character id a) set found to true else if a > 96 and a < 123 and firstChar then -- lower to upper set a to a - 32 set newString to newString & (character id a) set found to true end if if (not found) and (a > 128) then -- handle known accented characters if not firstChar then -- upper to lower repeat with lc from 1 to ncl if contents of c = character lc of uppers then set newString to newString & character lc of lowers set found to true exit repeat end if end repeat else -- lower to upper repeat with lc from 1 to ncl if contents of c = character lc of lowers then set newString to newString & character lc of uppers set found to true exit repeat end if end repeat end if end if set firstChar to false end if if not found then set newString to newString & c end if end repeat end considering set theString to newString end titlecase_pureAS on abs(n) if n < 0 then return -n else return n end if end abs on isWhiteSpace(c) return c = space or c = tab or c = return or c = linefeed end isWhiteSpace property gLeftApostrophe : character id 8216 property gRightApostrophe : character id 8217 property gLeftQuote : character id 8220 property gRightQuote : character id 8221 property singleQuote : "'" property doubleQuote : "\"" on smartenQuotes() set newString to {} set n to number of characters of theString set s to theString set prevChar to "" repeat with i from 1 to n set theChar to character i of theString if (theChar = singleQuote or theChar = doubleQuote) then if (prevChar = 0 or ¬ prevChar = "(" or prevChar = "[" or prevChar = "{" or ¬ prevChar = "<" or prevChar = 171 or ¬ prevChar = character id 12296 or prevChar = character id 12298 or ¬ (prevChar = gLeftQuote and theChar = singleQuote) or ¬ (prevChar = gLeftApostrophe and theChar = doubleQuote) or ¬ isWhiteSpace(prevChar)) then if theChar = singleQuote then set theChar to gLeftApostrophe else set theChar to gLeftQuote end if else if theChar = singleQuote then set theChar to gRightApostrophe else set theChar to gRightQuote end if end if set prevChar to theChar as string end if copy theChar to end of newString end repeat setString(newString as string) return getString() end smartenQuotes -- file path utilities on filename() return lastToken("/") end filename on dirPath() return beforeString(filename()) end dirPath on fileExtension() tell newString(filename()) return afterString(".") end tell end fileExtension on fileBasename() tell newString(filename()) return beforeString(".") end tell end fileBasename end script