{--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------}
{--------------------------------|--------------------------------------------------------------------|---------------------------------------------------------|------------------------}
{
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: 67 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, 67 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.