{--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} { GENERIC STRINGS `````````````````````````` A Generic Utilities unit, for use by any program. Copyright © by David Sinclair, 1990 ­ 2001. I am releasing these units to the Pascal community. Feel free to use them in whole or part in your Pascal programs. You are also welcome to modify these units to suit your needs. If you wish to re-distribute the sources with your changes, please clearly indicate that you have changed them. In all cases, you must leave these comments and the copyright notice intact. If you use a significant portion of these units, I would appreciate acknowledgement in your About dialog and/or documentation, e.g. ³Dejal Generic Utilities copyright © by David Sinclair, 1990 - 2001.² I¹d appreciate it if you also e-mail me at if you find these units useful. If you have any questions about these units, you can e-mail me at that address and I will do my best to help, time permitting. However, these units are provided ³as is² and I do not guarantee their reliablity or suitability for any particular purpose. These units have been used extensively in my Dejal shareware and freeware products over the years. Most of the code was written many years ago, and the code and style may not be optimal in all cases, but unless otherwise noted all routines have been used in released software, so should work as described. Please visit and try out Dejal QuickEncrypt and/or my other shareware products. If you want to show your appreciation for these units financially, registrations for my shareware are always welcome! Or you can make a donation to me via my online order form: . I hope you find these units useful, and good luck in your Pascal endeavors! - David Sinclair, Dejal * * * UNIT HISTORY: (Reverse chronology) Start ­ finish dates: Comments / changes: 27 October 2001 Public release as source code. 21 December 1997 Added the StrAbbrevDate and StrAbbrevDateTime routines. 1 June 1997 Added the StrKMb routine. 27 April 1997 Added the StrToOSType routine. 31 March 1997 Added StrShortDate and StrBytesKMb. 23 February 1997 Added StrShowControls, StrNot, StrBoolean, StrNum, StrPoint, StrRect, StrStr and StrDebugPauseForClick. 26 February 1996 Added the StrEqual function. 4 February 1996 Added the StrCharRange function. 27 January 1996 Added the StrToReal function. 13 January 1996 Added the StrChanged function. * * * } { OLD UNIT HISTORY: } { } { Version: Start - finish dates: Comments / changes: } { } { 1.0: 22 September 1990 Routines used all over the place copied into } { this unit and optimised for inter-application } { efficiency. } { 1.0.1: 30 September 1990 Changed the unit name to a Œgen¹ prefix and } { the routines to a Œstr¹ prefix. } { 1.1: 22 October 1990 Added routines to read the vers resource. } { 1.2: 21 November 90 Moved version routines to a separate unit, } { as that is more logical. } { 1.3: 22 November 90 Added strSplitString routine. } { 1.4: 24 November 90 Added strReadScrap routine (but didn¹t test } { it (yet) !). } { 1.5: 28 November 90 Added strEllipsis routine. } { 1.6: 7 December 1990 Updated the key constants to include all } { available keys and added the char constants } { for keys. } { 1.7: 16 March 1991 Added strToNum and strFromNum. } { 1.8: 4 April 1991 Added strListLength function. } { 1.9: 8 April 1991 Moved strListLength to the new } { genResources unit. } { 1.10: 11 April 1991 Added the strSwap procedure. } { 1.11: 21 April 1991 Added the strLineCaps routine. } { 1.12: 11 November 91 Added the strReplace function. } { 1.13: 19 November 91 Added the strCountStrings function. } { 1.14: 4 December 1991 Added the strFindInStrList function. } { 1.15: 5 December 1991 Added the strReplaceInStrList function. } { 1.16: 13 December 91 Added the strSkipLengthByte function. } { 1.17: 16 December 91 Added the strLastPos function. } { 2.0: 20 December 91 First public release, in library form. } { 2.1: 12 January 1992 Added the strPlural function. } { 2.2: 16 January 1992 Added the strNthPos function. } { 2.2.1: 19 January 1992 Fixed repetitive keyword problem in } { strNthPos. } { 2.3: 28 January 1992 Added strWordPosAndLen & strWordCaps. } { 2.4: 14 February 1992 Added strRight & strToDigitsOnly functions. } { 2.5: 25 March 1992 Added the strLeft function. } { 2.5.1: 5 April 1992 Fixed strSplitString to handle splitChar not } { found. } { 2.6: 5 June 1992 Added the strWriteScrap procedure. } { 2.6.1: 28 June 1992 Apostrophies no longer terminate a word. } { 2.7: 6­7 July 1992 Added strCharIsntPos and strSmartQuotes. } { 2.7.1: 8 July 1992 Fixed problem with strReplace etc when the } { keyword is null. } { 2.8: 21 February 1993 Added the strDecPlaces routine. } { 2.9: 19 April 1994 Added the strGetNumAndPos and } { strGetNumber routines. } { 2.10: 20 April 1994 Copied the strDebugFile routine and the } { reporting toggles from genSpeech. } { 2.11: 20 April 1994 Modified strRight and strLeft to allow } { multiple-character padding. } { 2.11.1: 21 April 1994 Fixed strRight and strLeft multi-padding to } { line up the dots nicely when using the new } { constants dottySpacePad and spacedDotPad. } { 2.12: 4 June 1994 Added the strSplitAtCR routine. } { 2.12.1: 6 June 1994 Removed the strSkipLengthByte routine, } { since @theText[1] is much tidier. } { 2.13: 7 June 1994 Added the strReplaceCtrls routine. } { 2.14: 14 June 1994 Added the strDebug routine, and provided } { a default pathname for strDebugFile to use. } { 2.15: 23 June 1994 Added the strSplitAtChar routine. } { 2.15.1: 7 July 1994 Changed the number marker for strPlural } { from Œ¶#¶¹ to Œ€#€¹, as the former caused } { problems. The former is still supported } { though, for historical reasons. } { 2.15.2: 9 July 1994 The strDontReport and strReport routines } { are now only available when the application } { compile-time variable is true (since they use } { a global variable). } { 2.16: 9 July 1994 Added strFindWord, strFindPreciseWord, } { and strFindBasicWord; based on the old } { strWordPosAndLen routine, which has } { now been removed. Also added the comma } { and period constants. } { 2.16.1: 10 July 1994 Fixed strReplaceCtrls by pretty much } { rewriting it. } { 2.17: 18 July 1994 Added strEllipsis, which allows adding text } { after the ellipsis (the old routine has the } { misspelt name ³strElipsis²). } { 2.17.1: 21 October 1994 Added the bullet constant. } { 2.18: 2 November 1994 Added the strLowercase and strReplaceNum } { functions. } { 2.18: 15 November 94 Fixed strLowercase: it was actually } { converting to uppercase! Oops. Added } { strToTE and strFromTE. Removed routine } { descriptions from the interface section. } { 2.19: 16 November 94 Added the strGetAndReplace and } { strGetAndReplaceNum functions. } { 2.20: 22 January 1995 Added the strGetString function. } { 2.21: 26 Nov 1995 Added the strNumToHex function. } { * * * } { N.B: Most of my units require the compile-time variables Œapplication¹ } { and Œdebug¹, both of which are booleans. } { My listings are formatted using Palatino 12 and tabs set at 6 spaces. } {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} UNIT genStrings; INTERFACE USES genToolbox; CONST null = ''; etc = 255; { Use as: e.g. copy(theString, 5, etc); } space = ' '; comma = ','; period = '.'; bullet = '€'; commandSymbol = char(commandMark); { The Command-key symbol } appleSymbol = char(appleMark); { The Apple symbol } checkSymbol = char(checkMark); { The checkmark symbol } nullKey = $00; { Key codesŠ } homeKey = $01; enterKey = $03; endKey = $04; helpKey = $05; bellKey = $07; backspaceKey = $08; tabKey = $09; lineFeedKey = $0A; pageUpKey = $0B; pageDownKey = $0C; returnKey = $0D; XOnKey = $11; XOffKey = $12; clearKey = $1B; escKey = clearKey; leftKey = $1C; rightKey = $1D; upKey = $1E; downKey = $1F; fwdDelKey = $7F; nullChar = Char(nullKey); { CharactersŠ } homeChar = char(homeKey); enterChar = char(enterKey); endChar = char(endKey); helpChar = Char(helpKey); bellChar = Char(bellKey); backspaceChar = char(backspaceKey); tabChar = char(tabKey); lineFeedChar = Char(lineFeedKey); pageUpChar = Char(pageUpKey); pageDownChar = Char(pageDownKey); returnChar = char(returnKey); XOnChar = Char(XOnKey); XOffChar = Char(XOffKey); clearChar = char(clearKey); escChar = char(escKey); leftChar = char(leftKey); rightChar = char(rightKey); upChar = char(upKey); downChar = char(downKey); fwdDelChar = Char(fwdDelKey); defaultDebugFile = ':Desktop Folder:Debug Log'; maxDebugLogSize = 512 * 1024; kChooserNameID = -16096; { Owner Name value from the System } pluralMarker = '¶¶'; numberMarker = '€#€'; oldNumberMarker = '¶#¶'; preciseWordChars = ['0'..'9', 'A'..'Z', 'a'..'z', 'Å'..'ü', 'Æ', 'æ', ' '..'¦', 'ÿ'..'', 'Þ'..'þ', 'Â'..'Ô', 'Ò'..'ž']; basicWordChars = ['!'..'˜']; stdWordLen = 4; noPadding = chr(5); dottySpacePad = '. '; spacedDotPad = ' .'; forwards = 1; backwards = -1; noNumber = -9404; hexByte = 1; hexWord = 2; hexLong = 4; ignoreNull = true; inclNull = false; isRelative = true; notRelative = false; genStringsStringsID = 900; yesterdayString = 1; todayString = 2; tomorrowString = 3; TYPE wordCharsType = SET OF char; Str1 = STRING[1]; Str3 = STRING[3]; Str7 = STRING[7]; Str15 = STRING[15]; Str127 = STRING[127]; {$IFC application} VAR debugReport: Boolean; gPreviouslyDebugged: Boolean; {$ENDC} {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrShowControls (source: Str255): Str255; PROCEDURE StrDebugFile (message, filename: Str255; mode: Integer); PROCEDURE strDebug (message: str255); FUNCTION StrNot (message: Str255; bool: Boolean; thing: Str255): Str255; FUNCTION StrBoolean (message: Str255; bool: Boolean): Str255; FUNCTION StrNum (message: Str255; number: UNIV Longint): Str255; FUNCTION StrPoint (message: Str255; thePoint: Point): Str255; FUNCTION StrRect (message: Str255; theRect: Rect): Str255; FUNCTION StrStr (message, theStr: Str255): Str255; PROCEDURE StrDebugPauseForClick; {$IFC application} PROCEDURE strDontReport; PROCEDURE strReport; {$ENDC} PROCEDURE strSwapCharCase (VAR character: char); FUNCTION strGetIndString (strListID, index: integer): str255; FUNCTION strGetString (strID: integer): str255; PROCEDURE strSplitString (source: str255; splitChar: char; {} VAR beforeCharStr, afterCharStr: str255); FUNCTION strSplitAtChar (source: str255; splitChar: char): str255; FUNCTION strSplitAtCR (source: str255): str255; FUNCTION strReadScrap: str255; PROCEDURE strWriteScrap (theString: str255); FUNCTION strElipsis (source: str255; width: integer): str255; FUNCTION strEllipsis (leftText, rightText: str255; width: integer): str255; FUNCTION strToNum (source: str255): longint; FUNCTION strFromNum (theNumber: longint): str255; PROCEDURE strSwap (VAR firstString, secondString: str255); FUNCTION strLineCaps (source: str255): str255; FUNCTION strReplace (source, keyword, replaceWith: str255): str255; FUNCTION strReplaceCtrls (source: str255): str255; FUNCTION strReplaceNum (source, keyword: str255; replaceWith: longint): str255; FUNCTION strGetAndReplace (strListID, index: integer; {} keyword, replaceWith: str255): str255; FUNCTION strGetAndReplaceNum (strListID, index: integer; keyword: str255; {} replaceWith: longint): str255; FUNCTION strCountStrings (strListID: integer): integer; FUNCTION strFindInStrList (strListID: integer; match: str255): boolean; FUNCTION strReplaceInStrList (source: str255; keyStrListID: integer; {} replaceWith: str255): str255; {€ function strSkipLengthByte (atTheString: ptr): ptr;€} { The above routine has been removed since @theText[1] is much tidier. } FUNCTION strLastPos (source, keyword: str255): integer; FUNCTION strNthPos (source: str255; index: integer; keyword: str255): integer; FUNCTION strPlural (source: str255; number: integer): str255; PROCEDURE strFindWord (source: str255; wordChars: wordCharsType; {} inclApostrophes: boolean; direction: integer; VAR wordPos, wordLen: integer); PROCEDURE strFindPreciseWord (source: str255; inclApostrophes: boolean; {} direction: integer; VAR wordPos, wordLen: integer); PROCEDURE strFindBasicWord (source: str255; direction: integer; {} VAR wordPos, wordLen: integer); FUNCTION strWordCaps (source: str255; minWordLen: integer): str255; FUNCTION strRight (source: str255; noOfChars: integer; padWith: str255): str255; FUNCTION strLeft (source: str255; noOfChars: integer; ellipsisIfTooLong: boolean; {} padWith: str255): str255; FUNCTION strToDigitsOnly (source: str255): longint; FUNCTION strCharIsntPos (source: str255; keyChar: char; direction: integer): integer; FUNCTION strCharRange (source: Str255; charRange: WordCharsType): Str255; FUNCTION strSmartQuotes (source: str255): str255; FUNCTION strDecPlaces (number: real; places: integer): str255; FUNCTION strToReal (source: Str255): Real; FUNCTION strGetNumAndPos (source: str255; VAR numPos, numLen: integer): longint; FUNCTION strGetNumber (source: str255): longint; FUNCTION strLowercase (source: str255): str255; PROCEDURE strToTE (source: str255; hTE: TEHandle); FUNCTION strFromTE (hTE: TEHandle): str255; FUNCTION strNumToHex (theNumber: Longint; sigBytes: Integer): Str255; FUNCTION strChanged (result: Str255; VAR variable: Str255; andNotNull: Boolean): Boolean; FUNCTION strEqual (firstStr, secondStr: Str255; significantLength: Integer): Boolean; FUNCTION StrShortDate (secs: Longint): Str255; FUNCTION StrAbbrevDate (secs: Longint; relative: Boolean): Str255; FUNCTION StrAbbrevDateTime (secs: Longint; separator: Str15; relative: Boolean): Str255; FUNCTION StrBytesKMb (bytes: Longint): Str255; FUNCTION StrKMb (kilobytes: Longint): Str255; FUNCTION StrToOSType(source: Str255): OSType; { ----------------------------------------------------------------------------------------------------------------------------------- } { ----------------------------------------------------------------------------------------------------------------------------------- } IMPLEMENTATION FUNCTION StrShowControls (source: Str255): Str255; {Changes any control characters in the string to printable equivalents, e.g. ³«cr»² and ³«tab»².} VAR ctrlEquiv: Str7; i: Integer; BEGIN i:= 1; REPEAT {Convert control characters to printable sequences} IF (source[i] < Space) | (source[i] = FwdDelChar) THEN BEGIN ctrlEquiv:= Chr(Ord(source[i]) + 64); IF ctrlEquiv = '@' THEN ctrlEquiv:= '«0»' ELSE IF ctrlEquiv = 'I' THEN ctrlEquiv:= '«tab»' ELSE IF ctrlEquiv = 'J' THEN ctrlEquiv:= '«lf»' ELSE IF ctrlEquiv = 'M' THEN ctrlEquiv:= '«cr»' ELSE IF ctrlEquiv = '[' THEN ctrlEquiv:= '«esc»' ELSE IF ctrlEquiv = 'ø' THEN ctrlEquiv:= '«del»' ELSE ctrlEquiv:= Concat('«^', ctrlEquiv, '»'); source:= Concat(Copy(source, 1, i - 1), ctrlEquiv, Copy(source, i + 1, Length(source) - i)); i:= i + Length(ctrlEquiv) - 1; END; i:= i + 1; UNTIL i > Length(source); StrShowControls:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE StrDebugFile (message, filename: Str255; mode: Integer); { Writes the specified message out to a TEXT file with the given name, located in the } { root directory. You can specify a pathname for the file if you want it in a different } { location. The message is appended to the file, which is created if necessary, and } { date-stamped. If you pass Null for the filename, the constant DefaultDebugFile will } { be used as a default (it¹d be easier to simply call the following routine, though). } VAR result: osErr; refNum: integer; secs: Longint; strLen: Longint; dateString, timeString: Str255; BEGIN GetDateTime(secs); IF filename = Null THEN filename:= DefaultDebugFile; message:= StrShowControls(message); result:= Create(filename, -1, 'ttxt', 'TEXT'); result:= FSOpen(filename, -1, refNum); IF result = noErr THEN BEGIN IUDateString(secs, abbrevDate, dateString); IUTimeString(secs, True, timeString); IF gPreviouslyDebugged THEN timeString:= Concat(timeString, ' ') ELSE timeString:= Concat(dateString, ':', ReturnChar, timeString, ' '); result:= GetEOF(refNum, strLen); IF NOT gPreviouslyDebugged & (strLen > MaxDebugLogSize) THEN BEGIN strLen:= 0; result:= SetEOF(refNum, strLen); END; IF NOT gPreviouslyDebugged & (strLen > 0) THEN timeString:= Concat(ReturnChar, '€€€€€€', ReturnChar, ReturnChar, timeString); result:= SetFPos(refNum, fsFromLEOF, 0); strLen:= length(timeString); IF result = noErr THEN result:= FSWrite(refNum, strLen, ptr(ord4(@timeString) + 1)); IF Length(message) < 255 THEN message:= Concat(message, Chr(13)) ELSE message[255]:= Chr(13); strLen:= length(message); IF result = noErr THEN result:= FSWrite(refNum, strLen, ptr(ord4(@message) + 1)); result:= FSClose(refNum); IF mode <> 1 THEN result:= FlushVol(NIL, -1); gPreviouslyDebugged:= True; END; END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strDebug (message: str255); { Writes the specified message out to the default debug file. The message is appended } { to the file, which is created if necessary, and date-stamped. Call the above routine if } { you want to specify a different name or path for the file. } { Written by David Sinclair, 14 June 1994. } BEGIN strDebugFile(message, null, 0) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrNot (message: Str255; bool: Boolean; thing: Str255): Str255; { Returns either ³ ² or ³ not ², depending on the Boolean } { value. If message is Null, just ³² or ³not ² is returned. } BEGIN IF NOT bool THEN thing:= Concat('not ', thing); IF message <> Null THEN thing:= Concat(message, Space, thing); StrNot:= thing END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrBoolean (message: Str255; bool: Boolean): Str255; { Returns the message and true or false concatinated in a form suitable for debugging purposes. } BEGIN IF bool THEN StrBoolean:= Concat(message, ' = true') ELSE StrBoolean:= Concat(message, ' = talse'); END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrNum (message: Str255; number: UNIV Longint): Str255; { Returns the message and number value concatinated in a form suitable for debugging purposes. } BEGIN StrNum:= Concat(message, ' = ', StrFromNum(number)); END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrPoint (message: Str255; thePoint: Point): Str255; {Returns the message and point values concatinated in a form suitable for debugging purposes. } BEGIN StrPoint:= Concat(message, '.v = ', StrFromNum(thePoint.v), ', h = ', StrFromNum(thePoint.h)); END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrRect (message: Str255; theRect: Rect): Str255; { Returns the message and rect values concatinated in a form suitable for debugging purposes. } BEGIN WITH theRect DO StrRect:= Concat(message, '.top = ', StrFromNum(top), ', left = ', StrFromNum(left), ', bottom = ', StrFromNum(bottom), ', right = ', StrFromNum(right)); END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrStr (message, theStr: Str255): Str255; { Returns the message and theStr concatinated in a form suitable for debugging purposes. } BEGIN StrStr:= Concat(message, ' = ³', theStr, '²'); END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE StrDebugPauseForClick; { A routine to assist debugging; you can insert a pause to wait for a mouse click via this routine. } { The cursor will change to a cross while waiting. } VAR mouseEvent: EventRecord; BEGIN IF debugReport THEN BEGIN SetCursor(GetCursor(CrossCursor)^^); WHILE NOT GetNextEvent(mDownMask, mouseEvent) DO ; {Twiddle thumbs until a mouse-down event occurs} InitCursor; END; END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} {$IFC application} PROCEDURE strDontReport; { Reporting via strDebugFile and speechDebugFile will be supressed. } { Note: this is only available for applications, as it uses a global variable. Debug } { reporting is always active in other situations. } { Written by David Sinclair, 20 April 1994. } BEGIN debugReport:= false END; {$ENDC} {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} {$IFC application} PROCEDURE strReport; { Reporting via strDebugFile and speechDebugFile will be enabled. } { Note: this is only available for applications, as it uses a global variable. Debug } { reporting is always active in other situations. } { Written by David Sinclair, 20 April 1994. } BEGIN debugReport:= true END; {$ENDC} {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strSwapCharCase (VAR character: char); { Converts the character into an uppercase letter, if it is a lower case letter, or a } { lowercase if it is an uppercase, otherwise does nothing. Similar to the OS Utilities } { procedure, uprString, but uses chars, and goes both ways as appropriate. } { Written by David Sinclair, 11 August 1990. Intelligence added 19 September 1990. } BEGIN IF character IN ['a'..'z'] THEN character:= chr(ord(character) - 32) ELSE IF character IN ['A'..'Z'] THEN character:= chr(ord(character) + 32) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strGetIndString (strListID, index: integer): str255; { A version of the ToolBox call, but as a function rather than a procedure: makes } { things easier that way! } { Written by David Sinclair, 14 August 1990. } VAR theString: str255; BEGIN getIndString(theString, strListID, index); strGetIndString:= theString END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strGetString (strID: integer): str255; { A function to simplify reading STR resources, instead of calling GetString; returns the string, or } { null if it was missing. } { Written by David Sinclair, 22 January 1995. } VAR stringHndl: stringHandle; theString: str255; BEGIN stringHndl:= GetString(strID); IF stringHndl <> NIL THEN BEGIN theString:= stringHndl^^; ReleaseResource(handle(stringHndl)); END ELSE theString:= null; strGetString:= theString END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strSplitString (source: str255; splitChar: char; VAR beforeCharStr, afterCharStr: str255); { Looks for the specified character in the source string, and if it finds one it returns } { everything before that character in beforeCharStr, and everything after it in } { afterCharStr. If the splitChar is not found, the original string is returned in } { beforeCharStr and null is returned in afterCharStr. } { Written by David Sinclair, 22 November 1990; splitChar not found case handling } { added 5 April 1992. } VAR position: integer; BEGIN position:= pos(splitChar, source); IF position > 0 THEN BEGIN beforeCharStr:= copy(source, 1, position - 1); afterCharStr:= copy(source, position + 1, etc) END ELSE BEGIN beforeCharStr:= source; afterCharStr:= null END END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strSplitAtChar (source: str255; splitChar: char): str255; { Returns the text from the beginning of the string to the first instance of the specified } { character. Useful instead of strSplitString above when you¹re only interested in the } { first part. } { Written by David Sinclair, 23 June 1994, based on strSplitAtCR. } VAR position: integer; result: str255; BEGIN position:= pos(splitChar, source); IF position > 0 THEN result:= copy(source, 1, position - 1) ELSE result:= source; strSplitAtChar:= result END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strSplitAtCR (source: str255): str255; { Returns the text from the beginning of the string to the first return character. } { Useful instead of strSplitString above for extracting a line of text. } { Written by David Sinclair, 4 June 1994, based on strSplitString. } VAR position: integer; result: str255; BEGIN position:= pos(returnChar, source); IF position > 0 THEN result:= copy(source, 1, position - 1) ELSE result:= source; strSplitAtCR:= result END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} {$PUSH} {$R-} FUNCTION strReadScrap: str255; { Returns the contents of the Clipboard, if suitable (i.e. TEXTual), otherwise returns } { the null string (if a picture or other type). Note that this routine does not‹yet‹ } { deal with styled scrap text. Maybe laterŠ. } { Written by David Sinclair, 7 December 1989; genericised 24 November 1990; fixed } { 5 June 1992. } VAR scrapHndl: handle; scrapString: str255; scrapLength, offset: longint; BEGIN strReadScrap:= null; scrapHndl:= newHandle(0); IF scrapHndl <> NIL THEN BEGIN scrapLength:= getScrap(scrapHndl, 'TEXT', offset); IF scrapLength > 0 THEN BEGIN IF scrapLength > 255 THEN scrapLength:= 255; blockMove(scrapHndl^, @scrapString[1], scrapLength); scrapString[0]:= chr(scrapLength); strReadScrap:= scrapString END END END; {$POP} {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strWriteScrap (theString: str255); { Writes the string to the Clipboard. } { Written by David Sinclair, 5 June 1992. } VAR err: longint; BEGIN err:= zeroScrap; err:= putScrap(length(theString), 'TEXT', @theString[1]) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} {$PUSH} {$R-} FUNCTION strElipsis (source: str255; width: integer): str255; { Pass a string and the width you wish it to fit into (in pixels), and this function will } { return a string which is either the original string, if it is short enough, or a } { truncated version of the original with an ellipsis (Š) on the end. Useful for pop-up } { menus and suchlike. If you want to have a close quote or bracket on the end, call } { the following routine instead. } { Genericised by David Sinclair, 28 November 1990; from the Pop-Up Menus sample } { program © Apple Computer, 1988. } VAR newWidth, newLength: integer; BEGIN newWidth:= StringWidth(source); {get current width} IF newWidth > width THEN BEGIN {doesn't fit - truncate it} newLength:= LENGTH(source); {current length in characters} width:= width - CharWidth('Š'); {subtract width of ellipses} REPEAT {until it fits (or we run out of characters)} {drop the last character and its width} newWidth:= newWidth - CharWidth(source[newLength]); newLength:= PRED(newLength); UNTIL (newWidth <= width) | (LENGTH(source) = 0); {add the ellipses character} newLength:= SUCC(newLength); {one more char} source[newLength]:= 'Š'; {it¹s the ellipses} source[0]:= CHR(newLength); {fix the length} END; strElipsis:= source END; {$POP} {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strEllipsis (leftText, rightText: str255; width: integer): str255; { The same as above, plus you can pass some text to add after the ellipsis (e.g. } { a close quote mark or bracket). The resulting string will still be within the } { specified width range. } { Written by David Sinclair, 18 July 1994. } CONST slop = 2; VAR rightWidth: integer; BEGIN rightWidth:= stringWidth(rightText) + slop; leftText:= strElipsis(leftText, width - rightWidth); strEllipsis:= concat(leftText, rightText); END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strToNum (source: str255): longint; { Converts a string into a number, after checking that the string contains valid } { characters. Like the Toolbox procedure StringToNum, but easier to use since it } { is a function. } { Written by David Sinclair, 16 March 1991; improved 12 January 1992. } VAR theNumber: longint; index: integer; okChars: boolean; BEGIN theNumber:= 0; okChars:= false; IF source <> null THEN IF (length(source) = 1) & (source[1] IN ['0'..'9']) THEN okChars:= true ELSE IF source[1] IN ['-', '+', '0'..'9'] THEN FOR index:= 2 TO length(source) DO okChars:= source[index] IN ['0'..'9']; IF okChars THEN stringToNum(source, theNumber); strToNum:= theNumber END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strFromNum (theNumber: longint): str255; { Converts a number into a string (like the Toolbox procedure NumToString, but } { easier to use since it is a function). } { Written by David Sinclair, 16 March 1991. } VAR theString: str255; BEGIN numToString(theNumber, theString); strFromNum:= theString END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strSwap (VAR firstString, secondString: str255); { Simply swaps the two strings over, so if you passed 'Hello' in firstString and } { 'Goodbye' in secondString, it would return 'Goodbye' in firstString and 'Hello' in } { secondString. } { Written by David Sinclair, 11 April 1991. } VAR tempString: str255; BEGIN tempString:= firstString; firstString:= secondString; secondString:= tempString END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strLineCaps (source: str255): str255; { Converts the first character in the string to an uppercase letter, if it is a lowercase } { one. } { Written by David Sinclair, 21 April 1991. } BEGIN IF length(source) >= 1 THEN IF source[1] IN ['a'..'z'] THEN source[1]:= char(ord(source[1]) - 32); strLineCaps:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strReplace (source, keyword, replaceWith: str255): str255; { Replaces every occurance of keyword in source with replaceWith. Ideal for } { ParamText-like operations. } { Written by David Sinclair, 11 November 1991. } VAR position: integer; BEGIN IF keyword <> null THEN REPEAT position:= pos(keyword, source); IF position > 0 THEN source:= concat(copy(source, 1, position - 1), replaceWith, copy(source, position + length(keyword), 255 - ((position - 1) + length(replaceWith)))); UNTIL position <= 0; strReplace:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strReplaceCtrls (source: str255): str255; { Converts all valid ³^L²-type expressions in source to actual control characters. } { Only valid combinations are converted, these being '@', 'A', 'B', ..., 'Z', '[', '\', ']', } { '^', and '_'; capital letters must be used. If you want to leave a certain expression } { intact, you can prefix it with a bullet (³€²)‹the bullet will be deleted, but the } { expression will be left unconverted. } { Written by David Sinclair, 7 June 1994. } CONST escapeChar = '€'; ctrlChars = ['@'..'_']; ctrlDiff = $40; VAR destination: str255; position: integer; BEGIN destination:= null; REPEAT position:= pos('^', source); IF position > 0 THEN BEGIN destination:= concat(destination, copy(source, 1, position - 1)); IF (position > 1) & (source[position - 1] = escapeChar) THEN destination:= concat(copy(destination, 1, length(destination) - 1), copy(source, position, 2)) ELSE IF (position < length(source)) & (source[position + 1] IN ctrlChars) THEN destination:= concat(destination, char(ord(source[position + 1]) - ctrlDiff)); delete(source, 1, position + 1); END UNTIL position <= 0; destination:= concat(destination, source); strReplaceCtrls:= destination; END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strReplaceNum (source, keyword: str255; replaceWith: longint): str255; { Similar to strReplace, except it accepts a number instead of a string in the } { replaceWith parameter. } { Written by David Sinclair, 2 November 1994. } VAR replaceString: str255; BEGIN numToString(replaceWith, replaceString); strReplaceNum:= strReplace(source, keyword, replaceString) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strGetAndReplace (strListID, index: integer; {} keyword, replaceWith: str255): str255; { Similar to strReplace, except it performs the common operation of reading the } { source string from a STR# resource. } { Written by David Sinclair, 16 November 1994. } VAR source: str255; BEGIN getIndString(source, strListID, index); strGetAndReplace:= strReplace(source, keyword, replaceWith) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strGetAndReplaceNum (strListID, index: integer; keyword: str255; {} replaceWith: longint): str255; { Combines the functionallity of strGetAndReplace and strReplaceNum. } { Written by David Sinclair, 16 November 1994. } VAR source, replaceString: str255; BEGIN numToString(replaceWith, replaceString); getIndString(source, strListID, index); strGetAndReplaceNum:= strReplace(source, keyword, replaceString) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strCountStrings (strListID: integer): integer; { Returns the number of strings in the specified string list (STR# resource), or 0 if not } { found. } { Written by David Sinclair, 19 November 1991. } VAR strListHndl: handle; BEGIN strCountStrings:= 0; strListHndl:= getResource('STR#', strListID); IF strListHndl <> NIL THEN strCountStrings:= integerPtr(strListHndl^)^ END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strFindInStrList (strListID: integer; match: str255): boolean; { Searches within the specified STR# resource for a match with the specified string, } { and returns true if found or false if not. } { Written by David Sinclair, 4 December 1991. } VAR matchFound: boolean; index, noOfStrings: integer; BEGIN matchFound:= false; index:= 1; noOfStrings:= strCountStrings(strListID); IF noOfStrings > 0 THEN REPEAT IF strGetIndString(strListID, index) = match THEN matchFound:= true; index:= index + 1 UNTIL (index > noOfStrings) | matchFound; strFindInStrList:= matchFound END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strReplaceInStrList (source: str255; keyStrListID: integer; {} replaceWith: str255): str255; { A combination of strReplace and strFindInStrList. Replaces every occurance of all } { of the words in the specified STR# resource in source with replaceWith. } { Written by David Sinclair, 5 December 1991. } VAR index, noOfStrings: integer; BEGIN index:= 1; noOfStrings:= strCountStrings(keyStrListID); IF noOfStrings > 0 THEN REPEAT source:= strReplace(source, strGetIndString(keyStrListID, index), replaceWith); index:= index + 1 UNTIL index > noOfStrings; strReplaceInStrList:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} {€ function strSkipLengthByte (atTheString: ptr): ptr;€} {€€} {€{ Use this function to increment the pointer returned by @ by one, to skip the length byte. €]} {€{ e.g. to pass some text in theText to TESetText: €]} {€€} {€{ TESetText(strSkipLengthByte(@theText), length(theText), TEHndl); €]} {€€} {€{ Written by David Sinclair, 13 December 1991. €]} {€€} {€ begin€} {€ strSkipLengthByte:= pointer(ord4(atTheString) + 1)€} {€ end;€} { The above routine has been removed since @theText[1] is much tidier. } {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strLastPos (source, keyword: str255): integer; { Returns the position of the last occurance of the keyword string within the source } { string, or 0 if it doesn¹t occur anywhere (i.e. reverse action (and parameters) of the } { built-in function, pos). } { Written by David Sinclair, 16 December 1991. } VAR thePos, nextPos: integer; BEGIN thePos:= 0; nextPos:= 0; IF keyword <> null THEN REPEAT thePos:= thePos + nextPos; nextPos:= pos(keyword, copy(source, thePos + 1, etc)) UNTIL nextPos = 0; strLastPos:= thePos END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strNthPos (source: str255; index: integer; keyword: str255): integer; { Returns the position within the source of the indexth occurance of keyword, } { or 0 if it doesn¹t occur that many times. } { Written by David Sinclair, 16 January 1992; repetitive keyword problem fixed 19 } { January 1992. } VAR n, subPos, totalPos: integer; BEGIN n:= 1; subPos:= 0; totalPos:= 1; IF keyword <> null THEN REPEAT subPos:= pos(keyword, source); totalPos:= totalPos + subPos + length(keyword) - 1; IF subPos <> 0 THEN source:= copy(source, subPos + length(keyword), etc); n:= n + 1 UNTIL (n > index) | (subPos = 0); totalPos:= totalPos - length(keyword); IF subPos = 0 THEN totalPos:= 0; strNthPos:= totalPos END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strPlural (source: str255; number: integer): str255; { Allows correct grammer in strings, easily: pass a string containing a sequence } { enclosed in Œ¶¶¹ characters at each end and the middle, optionally including the } { Œ€#€¹ sequence as a placeholder for the number, e.g. 'There ¶¶is¶¶are¶¶ €#€ } { ¶¶copy¶¶copies¶¶ of that item.', where Œ€#€¹ will be replaced with the number, } { and the text between the first Œ¶¶¹ and the second is the singular case, and the text } { between the second Œ¶¶¹ and the third one is the plural case; resulting in the string } { 'There are 6 copies of that item.' if the value 6 was passed to this function, or 'There } { is 1 copy of that item.' if 1 was passed. Note: a simpler way to express the above } { string would be: 'There ¶¶is 1 copy¶¶are €#€ copies¶¶ of that item.'‹this is also } { legal. Any number of sequences and number markers may be included in the } { source string, but note that all sequences are based on the same number. Either (or } { both) sub-texts may be empty (null), though the markers must still be present, and } { the Œ€#€¹ sequence may be missing altogether. Based on the way System 7 does it, } { which caught my attention as being a really neat trick. } { Written by David Sinclair, 12 January 1992; changed number marker from Œ¶#¶¹ } { to Œ€#€¹ to prevent problems when there isn¹t a space between that and another } { following marker, 7 July 1994. } VAR startPos, middlePos, endPos: integer; selectedText: str255; BEGIN REPEAT startPos:= pos(pluralMarker, source); IF startPos > 0 THEN BEGIN middlePos:= strNthPos(source, 2, pluralMarker); IF middlePos > 0 THEN BEGIN endPos:= strNthPos(source, 3, pluralMarker); IF endPos > 0 THEN BEGIN IF number = 1 THEN selectedText:= copy(source, startPos, middlePos - startPos) ELSE selectedText:= copy(source, middlePos, endPos - middlePos); selectedText:= copy(selectedText, length(pluralMarker) + 1, etc); source:= concat(copy(source, 1, startPos - 1), selectedText, copy(source, endPos + length(pluralMarker), etc)) END END END UNTIL (startPos <= 0) | (middlePos <= 0) | (endPos <= 0); source:= strReplace(source, numberMarker, strFromNum(number)); strPlural:= strReplace(source, oldNumberMarker, strFromNum(number)) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strFindWord (source: str255; wordChars: wordCharsType; {} inclApostrophes: boolean; direction: integer; VAR wordPos, wordLen: integer); { Returns the position and length of the first word found in the specified string } { FOLLOWING the word with the specified position and length. e.g. if you pass ³This } { is a string.² in source and 1 and 4 in wordPos and wordLen respectively, it } { will return 6 in wordPos and 2 in wordLen (for the word ³is²). This allows this } { routine to be called repeatedly. Specify zero for both parameters to get the first word } { in the string. If there are no more words after the specified one, zero is returned for } { both numbers. Pass one of the constants backwards or forwards for direction, } { depending on whether the character at wordPos is in the middle of the word or not, } { respectively. If you specify backwards, you don¹t need to pass any value for } { wordLen, as it is ignored. Pass true for inclApostrophes to avoid breaking words on } { apostrophes (this is not the same as including them in the wordChars, as it won¹t } { include the characters if they¹re instead being used as quote marks). Pass the set of } { characters to consider valid word characters for wordChars (e.g. ³['A'..'Z', 'a'..'z']² if } { you only want letters to count). You could probably just call one of the following } { two routines in most cases. } { Written by David Sinclair, 28 January 1992; apostrophes no longer terminate a word, } { 28 June 1992; added option of specifying a set of word characters and whether or not } { to include apostrophes, 9 July 1994. } VAR foundStart, foundEnd: boolean; sourceLen: integer; BEGIN foundStart:= false; foundEnd:= false; IF direction <> backwards THEN wordPos:= wordPos + wordLen; wordLen:= 1; sourceLen:= length(source); IF (direction = backwards) & (NOT (wordPos IN [1..sourceLen]) | NOT (source[wordPos] IN wordChars)) THEN direction:= forwards; IF direction = backwards THEN { Scan backwards for the first invalid word char } BEGIN WHILE (wordPos > 2) & (source[wordPos - 1] IN wordChars) DO wordPos:= wordPos - 1; foundStart:= true; END ELSE { Scan forwards for the first valid word char } WHILE NOT foundStart & (wordPos < sourceLen) DO BEGIN wordPos:= wordPos + 1; foundStart:= source[wordPos] IN wordChars; END; IF foundStart THEN { Scan forwards for the first invalid word char } WHILE NOT foundEnd & (wordPos + wordLen < sourceLen) DO BEGIN wordLen:= wordLen + 1; foundEnd:= NOT (source[wordPos + wordLen] IN wordChars); IF foundEnd & inclApostrophes THEN foundEnd:= NOT (source[wordPos + wordLen] IN ['''', '¹']) END; IF NOT foundStart THEN BEGIN wordPos:= 0; wordLen:= 0 END ELSE IF NOT foundEnd THEN wordLen:= wordLen + 1 ELSE IF ((source[wordPos + wordLen] = '''') & NOT ('''' IN wordChars)) | ((source[wordPos + wordLen] = '¹') & NOT ('¹' IN wordChars)) THEN wordLen:= wordLen - 1 END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strFindPreciseWord (source: str255; inclApostrophes: boolean; {} direction: integer; VAR wordPos, wordLen: integer); { See the strFindWord routine for a description. This procedure treats all digits and } { letters, including those with diacritical marks, plus the non-breaking space, as valid } { word characters. This is pretty much the normal Macintosh convention when } { double-clicking on text, except this won¹t treat things like ³this€example² as one } { word. } { Written by David Sinclair, 9 July 1994. } BEGIN strFindWord(source, preciseWordChars, inclApostrophes, direction, wordPos, wordLen) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strFindBasicWord (source: str255; direction: integer; {} VAR wordPos, wordLen: integer); { See the strFindWord routine for a description. This procedure treats every } { character as a valid word character, except space, return, tab, and other control } { characters. } { Written by David Sinclair, 9 July 1994. } BEGIN strFindWord(source, basicWordChars, false, direction, wordPos, wordLen) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strWordCaps (source: str255; minWordLen: integer): str255; { Converts the first character of each word with a length of minWordLen or greater } { in the string to an uppercase letter, if it is a lowercase one. Shorter words are not } { touched. For example, if you specify 4 then ³this² will become ³This², but ³but² } { will not be converted. Specify either the number of letters in the shortest word to } { convert, or use the constant stdWordLen to use the default (which is currently 4, } { but may change in the future). Passing an invalid (<1) value for minWordLen will } { use the default. } { Written by David Sinclair, 28 January 1992. } VAR wordPos, wordLen: integer; BEGIN IF minWordLen < 1 THEN minWordLen:= stdWordLen; wordPos:= 0; wordLen:= 0; IF length(source) >= 1 THEN REPEAT strFindPreciseWord(source, true, forwards, wordPos, wordLen); IF wordLen >= minWordLen THEN IF source[wordPos] IN ['a'..'z'] THEN source[wordPos]:= char(ord(source[wordPos]) - 32) UNTIL wordPos = 0; strWordCaps:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION getPadding (left: boolean; noOfChars: integer; padWith: str255): str255; { Returns the correct length of padding; for use by strRight and strLeft, below. } { Pass true in left for strLeft, and false for strRight. noOfChars is the length of } { the padding. } { Written by David Sinclair, 21 April 1994. } VAR padding: str255; index: integer; BEGIN IF (padWith = dottySpacePad) | (padWith = spacedDotPad) THEN left:= NOT left; padding:= padWith; IF noOfChars > 0 THEN FOR index:= 1 TO (noOfChars DIV length(padWith)) + 1 DO padding:= concat(padding, padWith); IF left THEN padding:= copy(padding, 1, noOfChars) ELSE padding:= copy(padding, length(padding) - noOfChars + 1, etc); getPadding:= padding END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strRight (source: str255; noOfChars: integer; padWith: str255): str255; { Returns the specified number of characters from the right-hand side of the string; } { e.g. if you pass 'This is a string.' and 7, it will return 'string.'. If you specify a pad } { string, it is used to make the string up to the specified number of characters if it is } { too short. Pass the constant noPadding if you don¹t want to add any padding, or } { dottySpacePad or spacedDotPad to use ³. ² or ³ .² respectively; if you pass one of } { these two constants, the dots will line up nicely. Normally you¹d probably use just a } { single character for the padding, but it is permissable to use two or more (the string } { will still be the requested length). } { Written by David Sinclair, 14 February 1992; added multiple-character padding, } { 20 April 1994; added dottySpacePad and spacedDotPad, 21 April 1994. } BEGIN IF (length(source) < noOfChars) & (padWith <> noPadding) THEN source:= concat(getPadding(false, noOfChars - length(source), padWith), source); { Add padding } IF length(source) > noOfChars THEN { Shorten if too long } source:= copy(source, length(source) - noOfChars, etc); strRight:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strLeft (source: str255; noOfChars: integer; ellipsisIfTooLong: boolean; {} padWith: str255): str255; { Returns the specified number of characters from the left-hand side of the string; e.g. } { if you pass 'This is a string.' and 7, it will return 'This is'. If you pass true for } { ellipsisIfTooLong, a 'Š' character will be added to the end of the string if it is too } { long and has to be shortened (it will be added so that the final length = noOfChars). } { If you specify a pad string, it is used to make the string up to the specified number of } { characters if it is too short. Pass the constant noPadding if you don¹t want to add } { any padding, or dottySpacePad or spacedDotPad to use ³. ² or ³ .² respectively; if you } { pass one of these two constants, the dots will line up nicely. Normally you¹d } { probably use just a single character for the padding, but it is permissable to use two } { or more (the string will still be the requested length). Note: you should use the } { built-in copy function instead of this one unless you want to use the padding or } { elipsis features, since if you don¹t use these features, calling ³copy(source, 1, } { noOfChars);² would have exactly the same effect, but save some overhead. } { Written by David Sinclair, 25 March 1992, strongly based on strRight, above; } { added multiple-character padding, 20 April 1994; added dottySpacePad and } { spacedDotPad, 21 April 1994. } BEGIN IF noOfChars > 0 THEN BEGIN IF (length(source) < noOfChars) & (padWith <> noPadding) THEN source:= concat(source, getPadding(true, noOfChars - length(source), padWith)); { Add the padding } IF length(source) > noOfChars THEN { Shorten if too long } IF ellipsisIfTooLong THEN source:= concat(copy(source, 1, noOfChars - 1), 'Š') ELSE source:= copy(source, 1, noOfChars) END; strLeft:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strToDigitsOnly (source: str255): longint; { Same as strToNum except ignores any sign characters present. } { Written by David Sinclair, 14 February 1992. } BEGIN IF source <> null THEN IF source[1] IN ['+', '-'] THEN source:= copy(source, 2, etc); strToDigitsOnly:= strToNum(source) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strCharIsntPos (source: str255; keyChar: char; direction: integer): integer; { Finds either the first or the last position in the line where the keyChar isn¹t, } { depending on what you pass for the direction. Pass the constant forward to start } { from the beginning and work forwards, or pass backward to start from the end and } { work backwards. It will search in the specified direction and return the position of } { the first character it finds which doesn¹t match the keyChar. If the source is } { null or consists of only keyChars, this function returns 0. If you really want to, you } { can specify a greater number for direction, e.g. 2 would look at every second } { character, starting from the beginning, or -3 would look at every third character } { from the end. } { Written by David Sinclair, 6 July 1992. } VAR currentPos: integer; BEGIN strCharIsntPos:= 0; IF (source <> null) & (direction <> 0) THEN BEGIN IF direction > 0 THEN currentPos:= 1 ELSE currentPos:= length(source); WHILE (currentPos > 0) & (currentPos <= length(source)) & (source[currentPos] = keyChar) DO { Need to use Œ&¹s in the above, and in that order, to prevent a range error } currentPos:= currentPos + direction; IF currentPos <= length(source) THEN strCharIsntPos:= currentPos END END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strCharRange (source: Str255; charRange: WordCharsType): Str255; { Returns the source string with only those characters indicated by the charRange; e.g. if you } { pass ³123:456abc!?² and ³['?','1..9','a'..'z']², it will return ³123456abc?², eliminating the illegal } { characters ³:² and ³!². } { Written by David Sinclair, 4 February 1996. } VAR index: Integer; BEGIN index:= 1; WHILE index <= Length(source) DO IF NOT (source[index] IN charRange) THEN Delete(source, index, 1) ELSE index:= index + 1; strCharRange:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strSmartQuotes (source: str255): str255; { Converts all "dumb" quotes in the source string to ³smart² quotes. Handles } { apostrophies correctly, too, in most cases: things like ³Fish 'n' chips² and ³I bought } { my Mac in '89² won¹t use the correct appostrophie though. } { Written by David Sinclair, 6­7 July 1992. } VAR quotePos: integer; BEGIN IF source <> null THEN BEGIN IF source[1] = '"' THEN source:= concat('³', copy(source, 2, etc)) ELSE IF source[1] = '''' THEN source:= concat('Œ', copy(source, 2, etc)); IF length(source) > 1 THEN REPEAT quotePos:= pos('"', source); IF quotePos = 0 THEN quotePos:= pos('''', source); IF quotePos > 0 THEN BEGIN IF source[quotePos - 1] IN [space, returnChar] THEN IF source[quotePos] = '"' THEN source[quotePos]:= '³' ELSE source[quotePos]:= 'Œ' ELSE IF source[quotePos] = '"' THEN source[quotePos]:= '²' ELSE source[quotePos]:= '¹' END UNTIL quotePos = 0 END; strSmartQuotes:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strDecPlaces (number: real; places: integer): str255; { Takes a real number and the number of decimal places required, and returns } { the appropriate string; e.g. passing 123.456789 and 2 will return '123.46'. } { Notice that it will round up the last digit if appropriate. You can specify 0 } { places to get just the integer equiv (which simply calls the round function), } { or a negative places value to get less significant figures, e.g. passing } { 987654.321 and -3 will return '988000'; again, notice that rounding is } { performed. } { Written by David Sinclair, 21 February 1993; fixed strange glitch when occasionally there } { would be one too many decimal places (usually zero), 28 January 1996. } VAR leftStr, rightStr: str255; working: longint; index: integer; BEGIN IF places > 0 THEN BEGIN working:= RIntToL((number - trunc(number)) * exp(ln(10) * places)); numToString(working, rightStr); IF length(rightStr) < places THEN FOR index:= length(rightStr) TO places - 1 DO rightStr:= concat(rightStr, '0') ELSE IF Length(rightStr) > places THEN rightStr:= Copy(rightStr, 1, places); working:= RIntToL(number) END ELSE IF places = 0 THEN working:= RIntToL(number) ELSE IF places < 0 THEN working:= RIntToL(round(number) * exp(ln(10) * places)); numToString(working, leftStr); IF places > 0 THEN strDecPlaces:= concat(leftStr, period, rightStr) ELSE BEGIN IF (places < 0) & (leftStr <> '0') THEN FOR index:= -1 DOWNTO places DO leftStr:= concat(leftStr, '0'); strDecPlaces:= leftStr END END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strToReal (source: Str255): Real; { Given a string (which may contain a decimal point), returns the corresponding real number. } VAR theValue: Real; decPos: Integer; divisor: Longint; BEGIN decPos:= Pos('.', source); IF decPos > 0 THEN BEGIN divisor:= strToNum(strLeft('1', Length(source) - decPos + 1, false, '0')); Delete(source, decPos, 1); END; theValue:= strToNum(source); IF decPos > 0 THEN theValue:= theValue / divisor; strToReal:= theValue END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strGetNumAndPos (source: str255; VAR numPos, numLen: integer): longint; { Finds and returns the first number within the specified string, starting from } { numPos. The number could contain several digits, and possibly a sign, though } { decimal places are ignored since the return type is a longint. Pass 1 for numPos to } { find the first number in the string; numLen can be left uninitialised. If no number } { is found, the routine returns the constant noNumber, and the two var parameters } { are zero, otherwise the number is returned and those parameters are filled in with } { the position and length of the number‹if you wished, you could call the routine } { again, passing the sum of the two numbers in numPos, to get the next number in } { the string, if any. If you just want the first number, and don¹t care about it¹s position } { or length, call the following routine instead. } { Written by David Sinclair, 19 April 1994. } VAR result: longint; BEGIN result:= noNumber; IF numPos < 1 THEN numPos:= 1; WHILE (numPos <= length(source)) & NOT (source[numPos] IN ['0'..'9']) DO numPos:= numPos + 1; { Look for the first digit, if any } IF numPos <= length(source) THEN BEGIN numLen:= numPos + 1; { numLen is actually the end } { position at this point } IF (numPos > 1) & (source[numPos - 1] IN ['+', '-']) THEN { Include the sign, if any } numPos:= numPos - 1; WHILE (numLen <= length(source)) & (source[numLen] IN ['0'..'9']) DO numLen:= numLen + 1; { Look for the end of the number } numLen:= numLen - numPos; { Convert the end position into the length } stringToNum(copy(source, numPos, numLen), result) END; IF result = noNumber THEN BEGIN numPos:= 0; numLen:= 0 END; strGetNumAndPos:= result END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strGetNumber (source: str255): longint; { Finds and returns the first number within the specified string (which could contain } { several digits, and possibly a sign, though decimal places are ignored since the } { return type is a longint). If no number is found, the routine returns the constant } { noNumber. To also find out the position and length of the number, or be able to } { find any further numbers in the string, call the previous routine instead. } { Written by David Sinclair, 19 April 1994. } VAR numPos, numLen: integer; BEGIN numPos:= 1; strGetNumber:= strGetNumAndPos(source, numPos, numLen) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strLowercase (source: str255): str255; { Converts the string to all lowercase characters; useful when you want to ignore the } { case of a string. } { Written by David Sinclair, 2 November 1994; fixed 15 November 1994. } VAR index: integer; BEGIN IF source <> null THEN FOR index:= 1 TO length(source) DO IF source[index] IN ['A'..'Z'] THEN source[index]:= char(ord(source[index]) + 32); strLowercase:= source END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} PROCEDURE strToTE (source: str255; hTE: TEHandle); { Converts a str255 string into the specified TE record, which must have been allocated } { beforehand. } { Taken from THINK Reference by David Sinclair, 15 November 1994. } BEGIN IF (source <> null) & (hTE <> NIL) THEN TESetText(@source[1], length(source), hTE) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strFromTE (hTE: TEHandle): str255; { Converts the text of the specified TE record into a str255 string. } { Taken from THINK Reference by David Sinclair, 15 November 1994. } VAR theText: str255; BEGIN IF hTE <> NIL THEN GetDialogItemText(hTE^^.hText, theText); strFromTE:= theText END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strNumToHex (theNumber: Longint; sigBytes: Integer): Str255; { Given a number (up to 32-bits in size) and the number of bytes that are significant (i.e. a value } { in the range 1..4: the constants hexByte, hexWord and hexLong are available), this function } { returns the number expressed in hex digits. } { Written by David Sinclair, 26 November 1995. } TYPE LongAsByteArray = PACKED ARRAY[1..4] OF SignedByte; VAR hexStr: Str255; index: Integer; theNybble: SignedByte; BEGIN hexStr:= null; IF sigBytes > 4 THEN sigBytes:= 4; IF sigBytes > 0 THEN FOR index:= sigBytes DOWNTO 1 DO BEGIN theNybble:= BAND(theNumber, $0F); IF theNybble < 10 THEN hexStr:= Concat(Chr(theNybble + 48), hexStr) ELSE hexStr:= Concat(Chr(theNybble + 55), hexStr); theNybble:= BSR(BAND(theNumber, $F0), 4); IF theNybble < 10 THEN hexStr:= Concat(Chr(theNybble + 48), hexStr) ELSE hexStr:= Concat(Chr(theNybble + 55), hexStr); END; strNumToHex:= hexStr END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strChanged (result: Str255; VAR variable: Str255; andNotNull: Boolean): Boolean; { Given a string (which might be the result of a function) and a variable, returns whether or not } { the two strings are different, and if so updates the variable to the result. If andNotNull is true, } { a null result is considered no change. The constants ignoreNull and inclNull are available. } { Written by David Sinclair, 13 January 1996. } VAR changed: Boolean; BEGIN changed:= (result <> variable); IF andNotNull & (result = null) THEN changed:= false; IF changed THEN variable:= result; strChanged:= changed END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION strEqual (firstStr, secondStr: Str255; significantLength: Integer): Boolean; { Given two strings, returns whether or not they are the same to the specified significant length. } { For example, if you passed ³This string², ³This other string² and 5, it would return true, but if } { you instead passed 6, it would return false. If you pass zero for the length, the length of the } { second string is used. } { Written by David Sinclair, 26 February 1996. } BEGIN IF significantLength <= 0 THEN significantLength:= Length(secondStr); strEqual:= (Copy(firstStr, 1, significantLength) = Copy(secondStr, 1, significantLength)) END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrShortDate (secs: Longint): Str255; {Given a date in the normal Mac format, returns a string in the form ³31/3/97², using the International Utilities routine.} {Written by David Sinclair, 31 March 1997.} VAR dateStr: Str255; BEGIN IUDateString(secs,shortDate,dateStr); StrShortDate:= dateStr END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION GetAbbrevDate(secs: Longint): Str255; {Only called by StrAbbrevDate, below.} VAR dateStr: Str255; BEGIN IUDateString(secs,abbrevDate,dateStr); GetAbbrevDate:= dateStr END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrAbbrevDate (secs: Longint; relative: Boolean): Str255; {Given a date in the normal Mac format, returns a string in the form ³Sun, 21 Dec 1997², using the International Utilities routine. Pass isRelative or notRelative for relative; if the former, returns a string in the form ³Yesterday², ³Today² or ³Tomorrow², if appropriate.} {Written by David Sinclair, 21 December 1997.} CONST kSecsInDay = 60*60*24; VAR dateStr: Str255; relativeStr: Str255; today: Longint; yesterday: Longint; tomorrow: Longint; BEGIN GetDateTime(today); yesterday:= today-kSecsInDay; tomorrow:= today+kSecsInDay; dateStr:= GetAbbrevDate(secs); relativeStr:= null; IF relative & (secs>=(yesterday-kSecsInDay)) & (secs<=(tomorrow+kSecsInDay)) THEN BEGIN IF dateStr=GetAbbrevDate(yesterday) THEN GetIndString(relativeStr,genStringsStringsID,yesterdayString) ELSE IF dateStr=GetAbbrevDate(today) THEN GetIndString(relativeStr,genStringsStringsID,todayString) ELSE IF dateStr=GetAbbrevDate(tomorrow) THEN GetIndString(relativeStr,genStringsStringsID,tomorrowString); IF relativeStr<>null THEN dateStr:= relativeStr; END; StrAbbrevDate:= dateStr END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrAbbrevDateTime (secs: Longint; separator: Str15; relative: Boolean): Str255; {Given a date in the normal Mac format, returns a string in the form ³Sun, 21 Dec 1997, 1:38 PM², using the International Utilities routine, with ³, ² used as the separator if it is null. Pass isRelative or notRelative for relative; if the former, returns a string in the form ³Yesterday, 1:38 PM², ³Today, 1:38 PM² or ³Tomorrow, 1:38 PM², if appropriate.} {Written by David Sinclair, 21 December 1997.} VAR dateStr: Str255; BEGIN IF separator=null THEN separator:= ', '; IUTimeString(secs,True,dateStr); dateStr:= Concat(StrAbbrevDate(secs,relative),separator,dateStr); StrAbbrevDateTime:= dateStr END; {--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------} FUNCTION StrBytesKMb (bytes: Longint): Str255; {Given a value in bytes, returns it as a string with the suffix ³ bytes², ³K² or ³ Mb², as appropriate.} {Written by David Sinclair, 31 March 1997.} VAR megabyte: Longint; theStr: Str255; BEGIN IF bytes = 0 THEN BEGIN theStr:= 'zero K'; END ELSE BEGIN megabyte:= 1024; { Working around a crazy problem in THINK Pascal } megabyte:= megabyte * 1024; IF bytes >= megabyte THEN theStr:= Concat(StrDecPlaces(bytes / megabyte, 1), ' Mb') ELSE IF bytes >= 1024 THEN theStr:= Concat(StrFromNum(bytes DIV 1024), 'K') ELSE theStr:= Concat(StrFromNum(bytes), ' bytes'); END; StrBytesKMb:= theStr END; { ----------------------------------------------------------------------------------------------------------------------------------- } FUNCTION StrKMb (kilobytes: Longint): Str255; {Given a value in kilobytes, returns it as a string with the suffix ³K² or ³ Mb², as appropriate.} {Written by David Sinclair, 1 June 1997.} VAR theStr: Str255; BEGIN IF kilobytes = 0 THEN BEGIN theStr:= 'zero K'; END ELSE BEGIN IF kilobytes >= 1024 THEN theStr:= Concat(StrDecPlaces(kilobytes / 1024, 1), ' Mb') ELSE theStr:= Concat(StrFromNum(kilobytes), 'K'); END; StrKMb:= theStr END; { ----------------------------------------------------------------------------------------------------------------------------------- } FUNCTION StrToOSType(source: Str255): OSType; {Given a string, returns an OSType from the first four characters (padding with spaces if necessary).} {Written by David Sinclair, 27 April 1997.} VAR theOSType: OSType; BEGIN IF Length(source) < 4 THEN source:= Concat(source,' '); BlockMove(@source[1],@theOSType,4); StrToOSType:= theOSType END; { ----------------------------------------------------------------------------------------------------------------------------------- } { ----------------------------------------------------------------------------------------------------------------------------------- } END.