{**************************************************************************************************} { } { Project JEDI Code Library (JCL) } { } { The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); } { you may not use this file except in compliance with the License. You may obtain a copy of the } { License at http://www.mozilla.org/MPL/ } { } { Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF } { ANY KIND, either express or implied. See the License for the specific language governing rights } { and limitations under the License. } { } { The Original Code is JclStrings.pas. } { } { The Initial Developer of the Original Code is Marcel van Brakel. } { Portions created by Marcel van Brakel are Copyright (C) Marcel van Brakel. All rights reserved. } { } { Contributor(s): } { Alexander Radchenko } { Andreas Hausladen (ahuser) } { Anthony Steele } { Azret Botash } { Barry Kelly } { Huanlin Tsai } { Jack N.A. Bakker } { Jean-Fabien Connault (cycocrew) } { John C Molyneux } { Leonard Wennekers } { Martin Kimmings } { Martin Kubecka } { Massimo Maria Ghisalberti } { Matthias Thoma (mthoma) } { Michael Winter } { Nick Hodges } { Olivier Sannier (obones) } { Patrick Kolla } { Pelle F. S. Liljendal } { Petr Vones (pvones) } { Robert Lee } { Robert Marquardt (marquardt) } { Robert Rossmair (rrossmair) } { Andreas Schmidt } { } {**************************************************************************************************} { } { Various character and string routines (searching, testing and transforming) } { } {**************************************************************************************************} { } { Last modified: $Date:: $ } { Revision: $Rev:: $ } { Author: $Author:: $ } { } {**************************************************************************************************} unit JclAnsiStrings; {$I jcl.inc} interface uses {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} {$IFDEF HAS_UNITSCOPE} {$IFDEF MSWINDOWS} Winapi.Windows, {$ENDIF MSWINDOWS} System.Classes, System.SysUtils, {$IFDEF HAS_UNIT_ANSISTRINGS} System.AnsiStrings, {$ENDIF HAS_UNIT_ANSISTRINGS} {$ELSE ~HAS_UNITSCOPE} {$IFDEF MSWINDOWS} Windows, {$ENDIF MSWINDOWS} Classes, SysUtils, {$IFDEF HAS_UNIT_ANSISTRINGS} AnsiStrings, {$ENDIF HAS_UNIT_ANSISTRINGS} {$ENDIF ~HAS_UNITSCOPE} JclBase; // Ansi types type {$IFDEF SUPPORTS_UNICODE} TJclAnsiStringList = class; // Codegear should be the one providing this class, in the AnsiStrings unit. // It has been requested in QC 65630 but this was closed as "won't do". // So we are providing here a very light implementation that is designed // to provide the basics, and in no way be a "copy/paste" of what is in the RTL. TJclAnsiStrings = class(TPersistent) private FDelimiter: AnsiChar; FNameValueSeparator: AnsiChar; FStrictDelimiter: Boolean; FQuoteChar: AnsiChar; FUpdateCount: Integer; function GetText: AnsiString; procedure SetText(const Value: AnsiString); function GetCommaText: AnsiString; {$IFDEF SUPPORTS_INLINE}inline;{$ENDIF} procedure SetCommaText(const Value: AnsiString); {$IFDEF SUPPORTS_INLINE}inline;{$ENDIF} function GetDelimitedText: AnsiString; overload; {$IFDEF SUPPORTS_INLINE}inline;{$ENDIF} function GetDelimitedText(const ADelimiter: AnsiString; AQuoteChar: AnsiChar): AnsiString; overload; procedure SetDelimitedText(const Value: AnsiString); overload; {$IFDEF SUPPORTS_INLINE}inline;{$ENDIF} procedure SetDelimitedText(const Value, ADelimiter: AnsiString; AQuoteChar: AnsiChar); overload; function ExtractName(const S: AnsiString): AnsiString; function GetName(Index: Integer): AnsiString; function GetValue(const Name: AnsiString): AnsiString; procedure SetValue(const Name, Value: AnsiString); function GetValueFromIndex(Index: Integer): AnsiString; procedure SetValueFromIndex(Index: Integer; const Value: AnsiString); protected procedure AssignTo(Dest: TPersistent); override; procedure Error(const Msg: string; Data: Integer); overload; procedure Error(Msg: PResStringRec; Data: Integer); overload; function GetString(Index: Integer): AnsiString; virtual; abstract; procedure SetString(Index: Integer; const Value: AnsiString); virtual; abstract; function GetObject(Index: Integer): TObject; virtual; abstract; procedure SetObject(Index: Integer; AObject: TObject); virtual; abstract; function GetCapacity: Integer; virtual; procedure SetCapacity(const Value: Integer); virtual; function GetCount: Integer; virtual; abstract; function CompareStrings(const S1, S2: AnsiString): Integer; virtual; procedure SetUpdateState(Updating: Boolean); virtual; property UpdateCount: Integer read FUpdateCount; public constructor Create; procedure Assign(Source: TPersistent); override; function Add(const S: AnsiString): Integer; virtual; function AddObject(const S: AnsiString; AObject: TObject): Integer; virtual; abstract; procedure AddStrings(Strings: TJclAnsiStrings); virtual; procedure Insert(Index: Integer; const S: AnsiString); virtual; procedure InsertObject(Index: Integer; const S: AnsiString; AObject: TObject); virtual; abstract; procedure Delete(Index: Integer); virtual; abstract; procedure Clear; virtual; abstract; procedure LoadFromFile(const FileName: TFileName); virtual; procedure LoadFromStream(Stream: TStream); virtual; procedure SaveToFile(const FileName: TFileName); virtual; procedure SaveToStream(Stream: TStream); virtual; procedure BeginUpdate; procedure EndUpdate; function IndexOf(const S: AnsiString): Integer; virtual; function IndexOfName(const Name: AnsiString): Integer; virtual; function IndexOfObject(AObject: TObject): Integer; virtual; procedure Exchange(Index1, Index2: Integer); virtual; property Delimiter: AnsiChar read FDelimiter write FDelimiter; property DelimitedText: AnsiString read GetDelimitedText write SetDelimitedText; property CommaText: AnsiString read GetCommaText write SetCommaText; property StrictDelimiter: Boolean read FStrictDelimiter write FStrictDelimiter; property QuoteChar: AnsiChar read FQuoteChar write FQuoteChar; property Strings[Index: Integer]: AnsiString read GetString write SetString; default; property Objects[Index: Integer]: TObject read GetObject write SetObject; property Text: AnsiString read GetText write SetText; property Count: Integer read GetCount; property Capacity: Integer read GetCapacity write SetCapacity; property Names[Index: Integer]: AnsiString read GetName; property Values[const Name: AnsiString]: AnsiString read GetValue write SetValue; property ValueFromIndex[Index: Integer]: AnsiString read GetValueFromIndex write SetValueFromIndex; property NameValueSeparator: AnsiChar read FNameValueSeparator write FNameValueSeparator; end; TJclAnsiStringListSortCompare = function(List: TJclAnsiStringList; Index1, Index2: Integer): Integer; TJclAnsiStringObjectHolder = record Str: AnsiString; Obj: TObject; end; TJclAnsiStringList = class(TJclAnsiStrings) private FStrings: array of TJclAnsiStringObjectHolder; FCount: Integer; FDuplicates: TDuplicates; FSorted: Boolean; FCaseSensitive: Boolean; FOnChange: TNotifyEvent; FOnChanging: TNotifyEvent; procedure Grow; procedure QuickSort(L, R: Integer; SCompare: TJclAnsiStringListSortCompare); procedure SetSorted(Value: Boolean); protected procedure AssignTo(Dest: TPersistent); override; function GetString(Index: Integer): AnsiString; override; procedure SetString(Index: Integer; const Value: AnsiString); override; function GetObject(Index: Integer): TObject; override; procedure SetObject(Index: Integer; AObject: TObject); override; function GetCapacity: Integer; override; procedure SetCapacity(const Value: Integer); override; function GetCount: Integer; override; function CompareStrings(const S1, S2: AnsiString): Integer; override; procedure SetUpdateState(Updating: Boolean); override; procedure Changed; virtual; procedure Changing; virtual; public constructor Create; destructor Destroy; override; function AddObject(const S: AnsiString; AObject: TObject): Integer; override; procedure Assign(Source: TPersistent); override; procedure InsertObject(Index: Integer; const S: AnsiString; AObject: TObject); override; procedure Delete(Index: Integer); override; function Find(const S: AnsiString; var Index: Integer): Boolean; virtual; procedure CustomSort(Compare: TJclAnsiStringListSortCompare); virtual; procedure Sort; virtual; procedure Clear; override; property CaseSensitive: Boolean read FCaseSensitive write FCaseSensitive; property Duplicates: TDuplicates read FDuplicates write FDuplicates; property Sorted: Boolean read FSorted write SetSorted; property OnChange: TNotifyEvent read FOnChange write FOnChange; property OnChanging: TNotifyEvent read FOnChanging write FOnChanging; end; {$ELSE ~SUPPORTS_UNICODE} TJclAnsiStrings = Classes.TStrings; TJclAnsiStringList = Classes.TStringList; {$ENDIF ~SUPPORTS_UNICODE} TAnsiStrings = TJclAnsiStrings; TAnsiStringList = TJclAnsiStringList; // Exceptions type EJclAnsiStringError = class(EJclError); EJclAnsiStringListError = class(EJclAnsiStringError); // Character constants and sets const // Misc. often used character definitions AnsiNull = AnsiChar(#0); AnsiSoh = AnsiChar(#1); AnsiStx = AnsiChar(#2); AnsiEtx = AnsiChar(#3); AnsiEot = AnsiChar(#4); AnsiEnq = AnsiChar(#5); AnsiAck = AnsiChar(#6); AnsiBell = AnsiChar(#7); AnsiBackspace = AnsiChar(#8); AnsiTab = AnsiChar(#9); AnsiLineFeed = AnsiChar(#10); AnsiVerticalTab = AnsiChar(#11); AnsiFormFeed = AnsiChar(#12); AnsiCarriageReturn = AnsiChar(#13); AnsiCrLf = AnsiString(#13#10); AnsiSo = AnsiChar(#14); AnsiSi = AnsiChar(#15); AnsiDle = AnsiChar(#16); AnsiDc1 = AnsiChar(#17); AnsiDc2 = AnsiChar(#18); AnsiDc3 = AnsiChar(#19); AnsiDc4 = AnsiChar(#20); AnsiNak = AnsiChar(#21); AnsiSyn = AnsiChar(#22); AnsiEtb = AnsiChar(#23); AnsiCan = AnsiChar(#24); AnsiEm = AnsiChar(#25); AnsiEndOfFile = AnsiChar(#26); AnsiEscape = AnsiChar(#27); AnsiFs = AnsiChar(#28); AnsiGs = AnsiChar(#29); AnsiRs = AnsiChar(#30); AnsiUs = AnsiChar(#31); AnsiSpace = AnsiChar(' '); AnsiComma = AnsiChar(','); AnsiBackslash = AnsiChar('\'); AnsiForwardSlash = AnsiChar('/'); AnsiDoubleQuote = AnsiChar('"'); AnsiSingleQuote = AnsiChar(''''); {$IFDEF MSWINDOWS} AnsiLineBreak = AnsiCrLf; {$ENDIF MSWINDOWS} {$IFDEF UNIX} AnsiLineBreak = AnsiLineFeed; {$ENDIF UNIX} AnsiSignMinus = AnsiChar('-'); AnsiSignPlus = AnsiChar('+'); // Misc. character sets AnsiWhiteSpace = [AnsiTab, AnsiLineFeed, AnsiVerticalTab, AnsiFormFeed, AnsiCarriageReturn, AnsiSpace]; AnsiSigns = [AnsiSignMinus, AnsiSignPlus]; AnsiUppercaseLetters = ['A'..'Z']; AnsiLowercaseLetters = ['a'..'z']; AnsiLetters = ['A'..'Z', 'a'..'z']; AnsiDecDigits = ['0'..'9']; AnsiOctDigits = ['0'..'7']; AnsiHexDigits = ['0'..'9', 'A'..'F', 'a'..'f']; AnsiValidIdentifierLetters = ['0'..'9', 'A'..'Z', 'a'..'z', '_']; const // CharType return values C1_UPPER = $0001; // Uppercase C1_LOWER = $0002; // Lowercase C1_DIGIT = $0004; // Decimal digits C1_SPACE = $0008; // Space characters C1_PUNCT = $0010; // Punctuation C1_CNTRL = $0020; // Control characters C1_BLANK = $0040; // Blank characters C1_XDIGIT = $0080; // Hexadecimal digits C1_ALPHA = $0100; // Any linguistic character: alphabetic, syllabary, or ideographic {$IFDEF MSWINDOWS} {$IFDEF SUPPORTS_EXTSYM} {$EXTERNALSYM C1_UPPER} {$EXTERNALSYM C1_LOWER} {$EXTERNALSYM C1_DIGIT} {$EXTERNALSYM C1_SPACE} {$EXTERNALSYM C1_PUNCT} {$EXTERNALSYM C1_CNTRL} {$EXTERNALSYM C1_BLANK} {$EXTERNALSYM C1_XDIGIT} {$EXTERNALSYM C1_ALPHA} {$ENDIF SUPPORTS_EXTSYM} {$ENDIF MSWINDOWS} // String Test Routines function StrIsAlpha(const S: AnsiString): Boolean; function StrIsAlphaNum(const S: AnsiString): Boolean; function StrIsAlphaNumUnderscore(const S: AnsiString): Boolean; function StrContainsChars(const S: AnsiString; Chars: TSysCharSet; CheckAll: Boolean): Boolean; function StrConsistsOfNumberChars(const S: AnsiString): Boolean; function StrIsDigit(const S: AnsiString): Boolean; function StrIsSubset(const S: AnsiString; const ValidChars: TSysCharSet): Boolean; function StrSame(const S1, S2: AnsiString): Boolean; // String Transformation Routines function StrCenter(const S: AnsiString; L: SizeInt; C: AnsiChar = ' '): AnsiString; function StrCharPosLower(const S: AnsiString; CharPos: SizeInt): AnsiString; function StrCharPosUpper(const S: AnsiString; CharPos: SizeInt): AnsiString; function StrDoubleQuote(const S: AnsiString): AnsiString; function StrEnsureNoPrefix(const Prefix, Text: AnsiString): AnsiString; function StrEnsureNoSuffix(const Suffix, Text: AnsiString): AnsiString; function StrEnsurePrefix(const Prefix, Text: AnsiString): AnsiString; function StrEnsureSuffix(const Suffix, Text: AnsiString): AnsiString; function StrEscapedToString(const S: AnsiString): AnsiString; function StrLower(const S: AnsiString): AnsiString; procedure StrLowerInPlace(var S: AnsiString); procedure StrLowerBuff(S: PAnsiChar); procedure StrMove(var Dest: AnsiString; const Source: AnsiString; const ToIndex, FromIndex, Count: SizeInt); function StrPadLeft(const S: AnsiString; Len: SizeInt; C: AnsiChar = AnsiSpace): AnsiString; function StrPadRight(const S: AnsiString; Len: SizeInt; C: AnsiChar = AnsiSpace): AnsiString; function StrProper(const S: AnsiString): AnsiString; procedure StrProperBuff(S: PAnsiChar); function StrQuote(const S: AnsiString; C: AnsiChar): AnsiString; function StrRemoveChars(const S: AnsiString; const Chars: TSysCharSet): AnsiString; function StrKeepChars(const S: AnsiString; const Chars: TSysCharSet): AnsiString; procedure StrReplace(var S: AnsiString; const Search, Replace: AnsiString; Flags: TReplaceFlags = []); function StrReplaceChar(const S: AnsiString; const Source, Replace: AnsiChar): AnsiString; function StrReplaceChars(const S: AnsiString; const Chars: TSysCharSet; Replace: AnsiChar): AnsiString; function StrReplaceButChars(const S: AnsiString; const Chars: TSysCharSet; Replace: AnsiChar): AnsiString; function StrRepeat(const S: AnsiString; Count: SizeInt): AnsiString; function StrRepeatLength(const S: AnsiString; const L: SizeInt): AnsiString; function StrReverse(const S: AnsiString): AnsiString; procedure StrReverseInPlace(var S: AnsiString); function StrSingleQuote(const S: AnsiString): AnsiString; procedure StrSkipChars(var S: PAnsiChar; const Chars: TSysCharSet); overload; procedure StrSkipChars(const S: AnsiString; var Index: SizeInt; const Chars: TSysCharSet); overload; function StrSmartCase(const S: AnsiString; Delimiters: TSysCharSet): AnsiString; function StrStringToEscaped(const S: AnsiString): AnsiString; function StrStripNonNumberChars(const S: AnsiString): AnsiString; function StrToHex(const Source: AnsiString): AnsiString; function StrTrimCharLeft(const S: AnsiString; C: AnsiChar): AnsiString; function StrTrimCharsLeft(const S: AnsiString; const Chars: TSysCharSet): AnsiString; function StrTrimCharRight(const S: AnsiString; C: AnsiChar): AnsiString; function StrTrimCharsRight(const S: AnsiString; const Chars: TSysCharSet): AnsiString; function StrTrimQuotes(const S: AnsiString): AnsiString; overload; function StrTrimQuotes(const S: AnsiString; QuoteChar: AnsiChar): AnsiString; overload; function StrUpper(const S: AnsiString): AnsiString; procedure StrUpperInPlace(var S: AnsiString); procedure StrUpperBuff(S: PAnsiChar); {$IFDEF MSWINDOWS} function StrOemToAnsi(const S: AnsiString): AnsiString; function StrAnsiToOem(const S: AnsiString): AnsiString; {$ENDIF MSWINDOWS} // String Management procedure StrAddRef(var S: AnsiString); procedure StrDecRef(var S: AnsiString); function StrLength(const S: AnsiString): Longint; function StrRefCount(const S: AnsiString): Longint; procedure StrResetLength(var S: AnsiString); // String Search and Replace Routines function StrCharCount(const S: AnsiString; C: AnsiChar): SizeInt; function StrCharsCount(const S: AnsiString; Chars: TSysCharSet): SizeInt; function StrStrCount(const S, SubS: AnsiString): SizeInt; function StrCompare(const S1, S2: AnsiString; CaseSensitive: Boolean = False): SizeInt; function StrCompareRangeEx(const S1, S2: AnsiString; Index, Count: SizeInt; CaseSensitive: Boolean = False): SizeInt; function StrCompareRange(const S1, S2: AnsiString; Index, Count: SizeInt; CaseSensitive: Boolean = True): SizeInt; function StrRepeatChar(C: AnsiChar; Count: SizeInt): AnsiString; function StrFind(const Substr, S: AnsiString; const Index: SizeInt = 1): SizeInt; function StrHasPrefix(const S: AnsiString; const Prefixes: array of AnsiString): Boolean; function StrHasSuffix(const S: AnsiString; const Suffixes: array of AnsiString): Boolean; function StrIHasPrefix(const S: AnsiString; const Prefixes: array of AnsiString): Boolean; function StrIHasSuffix(const S: AnsiString; const Suffixes: array of AnsiString): Boolean; function StrIndex(const S: AnsiString; const List: array of AnsiString; CaseSensitive: Boolean = False): SizeInt; function StrILastPos(const SubStr, S: AnsiString): SizeInt; function StrIPos(const SubStr, S: AnsiString): SizeInt; function StrIPrefixIndex(const S: AnsiString; const Prefixes: array of AnsiString): SizeInt; function StrIsOneOf(const S: AnsiString; const List: array of AnsiString): Boolean; function StrISuffixIndex(const S: AnsiString; const Suffixes: array of AnsiString): SizeInt; function StrLastPos(const SubStr, S: AnsiString): SizeInt; function StrMatch(const Substr, S: AnsiString; Index: SizeInt = 1): SizeInt; function StrMatches(const Substr, S: AnsiString; const Index: SizeInt = 1): Boolean; function StrNIPos(const S, SubStr: AnsiString; N: SizeInt): SizeInt; function StrNPos(const S, SubStr: AnsiString; N: SizeInt): SizeInt; function StrPrefixIndex(const S: AnsiString; const Prefixes: array of AnsiString): SizeInt; function StrSearch(const Substr, S: AnsiString; const Index: SizeInt = 1): SizeInt; function StrSuffixIndex(const S: AnsiString; const Suffixes: array of AnsiString): SizeInt; // String Extraction // String Extraction // Returns the String before SubStr function StrAfter(const SubStr, S: AnsiString): AnsiString; /// Returns the AnsiString after SubStr function StrBefore(const SubStr, S: AnsiString): AnsiString; /// Splits a AnsiString at SubStr, returns true when SubStr is found, Left contains the /// AnsiString before the SubStr and Rigth the AnsiString behind SubStr function StrSplit(const SubStr, S: AnsiString;var Left, Right : AnsiString): boolean; /// Returns the AnsiString between Start and Stop function StrBetween(const S: AnsiString; const Start, Stop: AnsiChar): AnsiString; /// Returns the left N characters of the AnsiString function StrChopRight(const S: AnsiString; N: SizeInt): AnsiString; /// Returns the left Count characters of the AnsiString function StrLeft(const S: AnsiString; Count: SizeInt): AnsiString; /// Returns the AnsiString starting from position Start for the Count Characters function StrMid(const S: AnsiString; Start, Count: SizeInt): AnsiString; /// Returns the AnsiString starting from position N to the end function StrRestOf(const S: AnsiString; N: SizeInt): AnsiString; /// Returns the right Count characters of the AnsiString function StrRight(const S: AnsiString; Count: SizeInt): AnsiString; // Character Test Routines function CharEqualNoCase(const C1, C2: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsAlpha(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsAlphaNum(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsBlank(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsControl(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsDelete(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsDigit(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsFracDigit(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsHexDigit(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsLower(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsNumberChar(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsNumber(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsPrintable(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsPunctuation(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsReturn(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsSpace(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsUpper(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsValidIdentifierLetter(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsWhiteSpace(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsWildcard(const C: AnsiChar): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharType(const C: AnsiChar): Word; // Character Transformation Routines function CharHex(const C: AnsiChar): Byte; function CharLower(const C: AnsiChar): AnsiChar; function CharUpper(const C: AnsiChar): AnsiChar; function CharToggleCase(const C: AnsiChar): AnsiChar; // Character Search and Replace function CharPos(const S: AnsiString; const C: AnsiChar; const Index: SizeInt = 1): SizeInt; function CharLastPos(const S: AnsiString; const C: AnsiChar; const Index: SizeInt = 1): SizeInt; function CharIPos(const S: AnsiString; C: AnsiChar; const Index: SizeInt = 1): SizeInt; function CharReplace(var S: AnsiString; const Search, Replace: AnsiChar): SizeInt; // PCharVector type PAnsiCharVector = ^PAnsiChar; function StringsToPCharVector(var Dest: PAnsiCharVector; const Source: TJclAnsiStrings): PAnsiCharVector; function PCharVectorCount(Source: PAnsiCharVector): SizeInt; procedure PCharVectorToStrings(const Dest: TJclAnsiStrings; Source: PAnsiCharVector); procedure FreePCharVector(var Dest: PAnsiCharVector); // MultiSz Routines type PAnsiMultiSz = PAnsiChar; function StringsToMultiSz(var Dest: PAnsiMultiSz; const Source: TJclAnsiStrings): PAnsiMultiSz; procedure MultiSzToStrings(const Dest: TJclAnsiStrings; const Source: PAnsiMultiSz); function MultiSzLength(const Source: PAnsiMultiSz): SizeInt; procedure AllocateMultiSz(var Dest: PAnsiMultiSz; Len: SizeInt); procedure FreeMultiSz(var Dest: PAnsiMultiSz); function MultiSzDup(const Source: PAnsiMultiSz): PAnsiMultiSz; // TJclAnsiStrings Manipulation procedure StrIToStrings(S, Sep: AnsiString; const List: TJclAnsiStrings; const AllowEmptyString: Boolean = True); procedure StrToStrings(S, Sep: AnsiString; const List: TJclAnsiStrings; const AllowEmptyString: Boolean = True); function StringsToStr(const List: TJclAnsiStrings; const Sep: AnsiString; const AllowEmptyString: Boolean = True): AnsiString; procedure TrimStrings(const List: TJclAnsiStrings; DeleteIfEmpty: Boolean = True); procedure TrimStringsRight(const List: TJclAnsiStrings; DeleteIfEmpty: Boolean = True); procedure TrimStringsLeft(const List: TJclAnsiStrings; DeleteIfEmpty: Boolean = True); function AddStringToStrings(const S: AnsiString; Strings: TJclAnsiStrings; const Unique: Boolean): Boolean; // Miscellaneous // (OF) moved to JclSysUtils //function BooleanToStr(B: Boolean): AnsiString; function FileToString(const FileName: TFileName): AnsiString; procedure StringToFile(const FileName: TFileName; const Contents: AnsiString; Append: Boolean = False); function StrToken(var S: AnsiString; Separator: AnsiChar): AnsiString; procedure StrTokens(const S: AnsiString; const List: TJclAnsiStrings); procedure StrTokenToStrings(S: AnsiString; Separator: AnsiChar; const List: TJclAnsiStrings); function StrWord(const S: AnsiString; var Index: SizeInt; out Word: AnsiString): Boolean; overload; function StrWord(var S: PAnsiChar; out Word: AnsiString): Boolean; overload; function StrIdent(const S: AnsiString; var Index: SizeInt; out Ident: AnsiString): Boolean; overload; function StrIdent(var S: PAnsiChar; out Ident: AnsiString): Boolean; overload; function StrToFloatSafe(const S: AnsiString): Float; function StrToIntSafe(const S: AnsiString): Integer; {$IFNDEF WINSCP} procedure StrNormIndex(const StrLen: SizeInt; var Index: SizeInt; var Count: SizeInt); overload; {$ENDIF} function ArrayOf(List: TJclAnsiStrings): TDynStringArray; overload; function AnsiCompareNaturalStr(const S1, S2: AnsiString): SizeInt; function AnsiCompareNaturalText(const S1, S2: AnsiString): SizeInt; // Explicit ANSI version of former/deprecated SysUtils PAnsiChar functions {$IFNDEF DEPRECATED_SYSUTILS_ANSISTRINGS} {$IFDEF SUPPORTS_INLINE} {$DEFINE ANSI_INLINE} // inline if the functions are in SysUtils but don't force the user to include System.AnsiStrings {$ENDIF SUPPORTS_INLINE} {$ENDIF ~DEPRECATED_SYSUTILS_ANSISTRINGS} function StrNewA(const Str: PAnsiChar): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} procedure StrDisposeA(Str: PAnsiChar); {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrLenA(S: PAnsiChar): Integer; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrEndA(const Str: PAnsiChar): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrPosA(const Str1, Str2: PAnsiChar): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrPasA(const Str: PAnsiChar): AnsiString; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrCopyA(Dest: PAnsiChar; const Source: PAnsiChar): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrLCopyA(Dest: PAnsiChar; const Source: PAnsiChar; MaxLen: Cardinal): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrPCopyA(Dest: PAnsiChar; const Source: AnsiString): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrPLCopyA(Dest: PAnsiChar; const Source: AnsiString; MaxLen: Cardinal): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrECopyA(Dest: PAnsiChar; const Source: PAnsiChar): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrCatA(Dest: PAnsiChar; const Source: PAnsiChar): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrLCatA(Dest: PAnsiChar; const Source: PAnsiChar; MaxLen: Cardinal): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrCompA(const Str1, Str2: PAnsiChar): Integer; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrLCompA(const Str1, Str2: PAnsiChar; MaxLen: Cardinal): Integer; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrICompA(const Str1, Str2: PAnsiChar): Integer; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrLICompA(const Str1, Str2: PAnsiChar; MaxLen: Cardinal): Integer; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function StrFmtA(Buffer, Format: PAnsiChar; const Args: array of const): PAnsiChar; function AnsiStrPosA(const Str1, Str2: PAnsiChar): PAnsiChar; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function AnsiStrLICompA(S1, S2: PAnsiChar; MaxLen: Cardinal): Integer; {$IFDEF ANSI_INLINE}inline;{$ENDIF} function AnsiStrLCompA(S1, S2: PAnsiChar; MaxLen: Cardinal): Integer; {$IFDEF ANSI_INLINE}inline;{$ENDIF} // internal structures published to make function inlining working const AnsiCharCount = Ord(High(AnsiChar)) + 1; // # of chars in one set AnsiLoOffset = AnsiCharCount * 0; // offset to lower case chars AnsiUpOffset = AnsiCharCount * 1; // offset to upper case chars AnsiReOffset = AnsiCharCount * 2; // offset to reverse case chars AnsiCaseMapSize = AnsiCharCount * 3; // # of chars is a table var AnsiCaseMap: array [0..AnsiCaseMapSize - 1] of AnsiChar; // case mappings AnsiCaseMapReady: Boolean = False; // true if case map exists AnsiCharTypes: array [AnsiChar] of Word; {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL$'; Revision: '$Revision$'; Date: '$Date$'; LogPath: 'JCL\source\common'; Extra: ''; Data: nil ); {$ENDIF UNITVERSIONING} implementation uses {$IFDEF HAS_UNIT_LIBC} Libc, {$ENDIF HAS_UNIT_LIBC} {$IFDEF SUPPORTS_UNICODE} {$IFDEF HAS_UNIT_RTLCONSTS} {$IFDEF HAS_UNITSCOPE} System.RTLConsts, {$ELSE ~HAS_UNITSCOPE} RtlConsts, {$ENDIF} {$ENDIF HAS_UNIT_RTLCONSTS} {$ENDIF SUPPORTS_UNICODE} {$IFNDEF WINSCP}JclLogic,{$ENDIF WINSCP} JclResources, JclStreams, JclSynch, JclSysUtils; //=== Internal =============================================================== type TAnsiStrRec = packed record RefCount: Integer; Length: Integer; end; PAnsiStrRec = ^TAnsiStrRec; const AnsiStrRecSize = SizeOf(TAnsiStrRec); // size of the AnsiString header rec procedure LoadCharTypes; var CurrChar: AnsiChar; CurrType: Word; begin for CurrChar := Low(AnsiChar) to High(AnsiChar) do begin {$IFDEF MSWINDOWS} CurrType := 0; GetStringTypeExA(LOCALE_USER_DEFAULT, CT_CTYPE1, @CurrChar, SizeOf(AnsiChar), CurrType); {$DEFINE CHAR_TYPES_INITIALIZED} {$ENDIF MSWINDOWS} {$IFDEF LINUX} CurrType := 0; if isupper(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_UPPER; if islower(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_LOWER; if isdigit(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_DIGIT; if isspace(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_SPACE; if ispunct(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_PUNCT; if iscntrl(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_CNTRL; if isblank(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_BLANK; if isxdigit(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_XDIGIT; if isalpha(Byte(CurrChar)) <> 0 then CurrType := CurrType or C1_ALPHA; {$DEFINE CHAR_TYPES_INITIALIZED} {$ENDIF LINUX} AnsiCharTypes[CurrChar] := CurrType; {$IFNDEF CHAR_TYPES_INITIALIZED} Implement case map initialization here {$ENDIF ~CHAR_TYPES_INITIALIZED} end; end; procedure LoadCaseMap; var CurrChar, UpCaseChar, LoCaseChar, ReCaseChar: AnsiChar; begin if not AnsiCaseMapReady then begin for CurrChar := Low(AnsiChar) to High(AnsiChar) do begin {$IFDEF MSWINDOWS} LoCaseChar := CurrChar; UpCaseChar := CurrChar; {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CharLowerBuffA(@LoCaseChar, 1); {$IFDEF HAS_UNITSCOPE}Winapi.{$ENDIF}Windows.CharUpperBuffA(@UpCaseChar, 1); {$DEFINE CASE_MAP_INITIALIZED} {$ENDIF MSWINDOWS} {$IFDEF LINUX} LoCaseChar := AnsiChar(tolower(Byte(CurrChar))); UpCaseChar := AnsiChar(toupper(Byte(CurrChar))); {$DEFINE CASE_MAP_INITIALIZED} {$ENDIF LINUX} {$IFNDEF CASE_MAP_INITIALIZED} Implement case map initialization here {$ENDIF ~CASE_MAP_INITIALIZED} if CharIsUpper(CurrChar) then ReCaseChar := LoCaseChar else if CharIsLower(CurrChar) then ReCaseChar := UpCaseChar else ReCaseChar := CurrChar; AnsiCaseMap[Ord(CurrChar) + AnsiLoOffset] := LoCaseChar; AnsiCaseMap[Ord(CurrChar) + AnsiUpOffset] := UpCaseChar; AnsiCaseMap[Ord(CurrChar) + AnsiReOffset] := ReCaseChar; end; AnsiCaseMapReady := True; end; end; // Uppercases or Lowercases a give AnsiString depending on the // passed offset. (UpOffset or LoOffset) procedure StrCase(var Str: AnsiString; const Offset: SizeInt); var P: PAnsiChar; I, L: SizeInt; begin if Str <> '' then begin UniqueString(Str); P := PAnsiChar(Str); L := Length(Str); for I := 1 to L do begin P^ := AnsiCaseMap[Offset + Ord(P^)]; Inc(P); end; end; end; // Internal utility function // Uppercases or Lowercases a give null terminated string depending on the // passed offset. (UpOffset or LoOffset) procedure StrCaseBuff(S: PAnsiChar; const Offset: SizeInt); begin if (S <> nil) and (S^ <> #0) then begin repeat S^ := AnsiCaseMap[Offset + Ord(S^)]; Inc(S); until S^ = #0; end; end; {$IFDEF SUPPORTS_UNICODE} //=== { TJclAnsiStrings } ==================================================== constructor TJclAnsiStrings.Create; begin inherited Create; FDelimiter := ','; FNameValueSeparator := '='; FQuoteChar := '"'; FStrictDelimiter := False; end; procedure TJclAnsiStrings.Assign(Source: TPersistent); var StringsSource: TStrings; I: Integer; begin if Source is TStrings then begin StringsSource := TStrings(Source); BeginUpdate; try Clear; FDelimiter := AnsiChar(StringsSource.Delimiter); FNameValueSeparator := AnsiChar(StringsSource.NameValueSeparator); for I := 0 to StringsSource.Count - 1 do AddObject(AnsiString(StringsSource.Strings[I]), StringsSource.Objects[I]); finally EndUpdate; end; end else inherited Assign(Source); end; procedure TJclAnsiStrings.AssignTo(Dest: TPersistent); var StringsDest: TStrings; AnsiStringsDest: TJclAnsiStrings; I: Integer; begin if Dest is TStrings then begin StringsDest := TStrings(Dest); StringsDest.BeginUpdate; try StringsDest.Clear; StringsDest.Delimiter := Char(Delimiter); StringsDest.NameValueSeparator := Char(NameValueSeparator); for I := 0 to Count - 1 do StringsDest.AddObject(string(Strings[I]), Objects[I]); finally StringsDest.EndUpdate; end; end else if Dest is TJclAnsiStrings then begin AnsiStringsDest := TJclAnsiStrings(Dest); AnsiStringsDest.BeginUpdate; try AnsiStringsDest.Clear; AnsiStringsDest.FNameValueSeparator := FNameValueSeparator; AnsiStringsDest.FDelimiter := FDelimiter; for I := 0 to Count - 1 do AnsiStringsDest.AddObject(Strings[I], Objects[I]); finally AnsiStringsDest.EndUpdate; end; end else inherited AssignTo(Dest); end; function TJclAnsiStrings.Add(const S: AnsiString): Integer; begin Result := AddObject(S, nil); end; procedure TJclAnsiStrings.AddStrings(Strings: TJclAnsiStrings); var I: Integer; begin for I := 0 to Strings.Count - 1 do Add(Strings.Strings[I]); end; procedure TJclAnsiStrings.Error(const Msg: string; Data: Integer); begin raise EJclAnsiStringListError.CreateFmt(Msg, [Data]); end; procedure TJclAnsiStrings.Error(Msg: PResStringRec; Data: Integer); begin Error(LoadResString(Msg), Data); end; function TJclAnsiStrings.CompareStrings(const S1, S2: AnsiString): Integer; begin Result := CompareStr(S1, S2); end; procedure TJclAnsiStrings.SetUpdateState(Updating: Boolean); begin end; function TJclAnsiStrings.IndexOf(const S: AnsiString): Integer; begin for Result := 0 to Count - 1 do if CompareStrings(Strings[Result], S) = 0 then Exit; Result := -1; end; function TJclAnsiStrings.IndexOfName(const Name: AnsiString): Integer; var P: Integer; S: AnsiString; begin for Result := 0 to Count - 1 do begin S := Strings[Result]; P := AnsiPos(NameValueSeparator, S); if (P > 0) and (CompareStrings(Copy(S, 1, P - 1), Name) = 0) then Exit; end; Result := -1; end; function TJclAnsiStrings.IndexOfObject(AObject: TObject): Integer; begin for Result := 0 to Count - 1 do if Objects[Result] = AObject then Exit; Result := -1; end; procedure TJclAnsiStrings.Exchange(Index1, Index2: Integer); var TempString: AnsiString; TempObject: TObject; begin BeginUpdate; try TempString := Strings[Index1]; TempObject := Objects[Index1]; Strings[Index1] := Strings[Index2]; Objects[Index1] := Objects[Index2]; Strings[Index2] := TempString; Objects[Index2] := TempObject; finally EndUpdate; end; end; function TJclAnsiStrings.GetCommaText: AnsiString; begin Result := GetDelimitedText(AnsiComma, AnsiDoubleQuote); end; function TJclAnsiStrings.GetDelimitedText: AnsiString; begin Result := GetDelimitedText(Delimiter, QuoteChar); end; function TJclAnsiStrings.GetDelimitedText(const ADelimiter: AnsiString; AQuoteChar: AnsiChar): AnsiString; function Quoted(Item: AnsiString): AnsiString; begin if (not StrictDelimiter) and ((Pos(AnsiSpace, Item) > 0) or (Pos(FQuoteChar, Item) > 0)) then begin Result := AnsiQuotedStr(Item, AQuoteChar); end else Result := Item; end; var I: Integer; begin Result := ''; for I := 0 to Count - 2 do Result := Result + Quoted(Strings[I]) + ADelimiter; if Count > 0 then Result := Result + Quoted(Strings[Count - 1]); end; procedure TJclAnsiStrings.Insert(Index: Integer; const S: AnsiString); begin InsertObject(Index, S, nil); end; procedure TJclAnsiStrings.SetCommaText(const Value: AnsiString); begin SetDelimitedText(Value, AnsiComma, AnsiDoubleQuote); end; procedure TJclAnsiStrings.SetDelimitedText(const Value: AnsiString); begin SetDelimitedText(Value, Delimiter, QuoteChar); end; procedure TJclAnsiStrings.SetDelimitedText(const Value, ADelimiter: AnsiString; AQuoteChar: AnsiChar); procedure InternalAdd(Item: AnsiString); begin Item := StrTrimQuotes(Item, AQuoteChar); StrReplace(Item, AQuoteChar + AQuoteChar, AQuoteChar, [rfReplaceAll]); Add(Item); end; var ValueLength, LastStart, Index, QuoteCharCount: Integer; ValueChar: AnsiChar; begin Clear; LastStart := 1; QuoteCharCount := 0; ValueLength := Length(Value); for Index := 1 to ValueLength do begin ValueChar := Value[Index]; if ValueChar = AQuoteChar then Inc(QuoteCharCount); if ((ValueChar = ADelimiter) or ((ValueChar = ' ') and (not StrictDelimiter))) and ((not Odd(QuoteCharCount) or (QuoteCharCount = 0))) then begin if StrictDelimiter then Add(Copy(Value, LastStart, Index - LastStart)) else InternalAdd(Copy(Value, LastStart, Index - LastStart)); QuoteCharCount := 0; LastStart := Index + 1; end; if (Index = ValueLength) and (LastStart <= ValueLength) then begin if StrictDelimiter then Add(Copy(Value, LastStart, ValueLength - LastStart + 1)) else InternalAdd(Copy(Value, LastStart, Index - LastStart + 1)); end; end; end; function TJclAnsiStrings.GetText: AnsiString; var I: Integer; begin Result := ''; for I := 0 to Count - 2 do Result := Result + Strings[I] + AnsiLineBreak; if Count > 0 then Result := Result + Strings[Count - 1] + AnsiLineBreak; end; procedure TJclAnsiStrings.SetText(const Value: AnsiString); var Index, Start, Len: Integer; S: AnsiString; begin Clear; Len := Length(Value); Index := 1; while Index <= Len do begin Start := Index; while (Index <= Len) and not CharIsReturn(Value[Index]) do Inc(Index); S := Copy(Value, Start, Index - Start); Add(S); if (Index <= Len) and (Value[Index] = AnsiCarriageReturn) then Inc(Index); if (Index <= Len) and (Value[Index] = AnsiLineFeed) then Inc(Index); end; end; function TJclAnsiStrings.GetCapacity: Integer; begin Result := Count; // Might be overridden in derived classes end; procedure TJclAnsiStrings.SetCapacity(const Value: Integer); begin // Nothing at this level end; procedure TJclAnsiStrings.BeginUpdate; begin if FUpdateCount = 0 then SetUpdateState(True); Inc(FUpdateCount); end; procedure TJclAnsiStrings.EndUpdate; begin Dec(FUpdateCount); if FUpdateCount = 0 then SetUpdateState(False); end; procedure TJclAnsiStrings.LoadFromFile(const FileName: TFileName); var Stream: TStream; begin Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try LoadFromStream(Stream); finally Stream.Free; end; end; procedure TJclAnsiStrings.LoadFromStream(Stream: TStream); var Size: Integer; S: AnsiString; begin BeginUpdate; try Size := Stream.Size - Stream.Position; System.SetString(S, nil, Size); Stream.Read(PAnsiChar(S)^, Size); SetText(S); finally EndUpdate; end; end; procedure TJclAnsiStrings.SaveToFile(const FileName: TFileName); var Stream: TStream; begin Stream := TFileStream.Create(FileName, fmCreate); try SaveToStream(Stream); finally Stream.Free; end; end; procedure TJclAnsiStrings.SaveToStream(Stream: TStream); var S: AnsiString; begin S := GetText; Stream.WriteBuffer(PAnsiChar(S)^, Length(S)); end; function TJclAnsiStrings.ExtractName(const S: AnsiString): AnsiString; var P: Integer; begin Result := S; P := AnsiPos(NameValueSeparator, Result); if P > 0 then SetLength(Result, P - 1) else SetLength(Result, 0); end; function TJclAnsiStrings.GetName(Index: Integer): AnsiString; begin Result := ExtractName(Strings[Index]); end; function TJclAnsiStrings.GetValue(const Name: AnsiString): AnsiString; var I: Integer; begin I := IndexOfName(Name); if I >= 0 then Result := Copy(GetString(I), Length(Name) + 2, MaxInt) else Result := ''; end; procedure TJclAnsiStrings.SetValue(const Name, Value: AnsiString); var I: Integer; begin I := IndexOfName(Name); if Value <> '' then begin if I < 0 then I := Add(''); SetString(I, Name + NameValueSeparator + Value); end else begin if I >= 0 then Delete(I); end; end; function TJclAnsiStrings.GetValueFromIndex(Index: Integer): AnsiString; var S: AnsiString; P: Integer; begin if Index >= 0 then begin S := Strings[Index]; P := AnsiPos(NameValueSeparator, S); if P > 0 then Result := Copy(S, P + 1, Length(S) - P) else Result := ''; end else Result := ''; end; procedure TJclAnsiStrings.SetValueFromIndex(Index: Integer; const Value: AnsiString); begin if Value <> '' then begin if Index < 0 then Index := Add(''); SetString(Index, Names[Index] + NameValueSeparator + Value); end else begin if Index >= 0 then Delete(Index); end; end; //=== { TJclAnsiStringList } ================================================= constructor TJclAnsiStringList.Create; begin inherited Create; FCaseSensitive := True; end; destructor TJclAnsiStringList.Destroy; begin FOnChange := nil; FOnChanging := nil; inherited Destroy; end; procedure TJclAnsiStringList.Assign(Source: TPersistent); var StringListSource: TStringList; begin if Source is TStringList then begin StringListSource := TStringList(Source); FDuplicates := StringListSource.Duplicates; FSorted := StringListSource.Sorted; FCaseSensitive := StringListSource.CaseSensitive; end; inherited Assign(Source); end; procedure TJclAnsiStringList.AssignTo(Dest: TPersistent); var StringListDest: TStringList; AnsiStringListDest: TJclAnsiStringList; begin if Dest is TStringList then begin StringListDest := TStringList(Dest); StringListDest.Clear; // make following assignments a lot faster StringListDest.Duplicates := FDuplicates; StringListDest.Sorted := FSorted; StringListDest.CaseSensitive := FCaseSensitive; end else if Dest is TJclAnsiStringList then begin AnsiStringListDest := TJclAnsiStringList(Dest); AnsiStringListDest.Clear; AnsiStringListDest.FDuplicates := FDuplicates; AnsiStringListDest.FSorted := FSorted; AnsiStringListDest.FCaseSensitive := FCaseSensitive; end; inherited AssignTo(Dest); end; function TJclAnsiStringList.CompareStrings(const S1: AnsiString; const S2: AnsiString): Integer; begin if FCaseSensitive then Result := CompareStr(S1, S2) else Result := CompareText(S1, S2); end; procedure TJclAnsiStringList.SetUpdateState(Updating: Boolean); begin if Updating then Changing else Changed; end; procedure TJclAnsiStringList.Changed; begin if (FUpdateCount = 0) and Assigned(FOnChange) then FOnChange(Self); end; procedure TJclAnsiStringList.Changing; begin if (FUpdateCount = 0) and Assigned(FOnChanging) then FOnChanging(Self); end; procedure TJclAnsiStringList.Grow; var Delta: Integer; begin if Capacity > 64 then Delta := Capacity div 4 else if Capacity > 8 then Delta := 16 else Delta := 4; SetCapacity(Capacity + Delta); end; function TJclAnsiStringList.GetString(Index: Integer): AnsiString; begin if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); Result := FStrings[Index].Str; end; procedure TJclAnsiStringList.SetString(Index: Integer; const Value: AnsiString); begin if Sorted then Error(@SSortedListError, 0); if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); FStrings[Index].Str := Value; end; function TJclAnsiStringList.GetObject(Index: Integer): TObject; begin if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); Result := FStrings[Index].Obj; end; procedure TJclAnsiStringList.SetObject(Index: Integer; AObject: TObject); begin if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); FStrings[Index].Obj := AObject; end; function TJclAnsiStringList.GetCapacity: Integer; begin Result := Length(FStrings); end; procedure TJclAnsiStringList.SetCapacity(const Value: Integer); begin if (Value < FCount) then Error(@SListCapacityError, Value); if Value <> Capacity then SetLength(FStrings, Value); end; function TJclAnsiStringList.GetCount: Integer; begin Result := FCount; end; procedure TJclAnsiStringList.InsertObject(Index: Integer; const S: AnsiString; AObject: TObject); var I: Integer; begin if Count = Capacity then Grow; for I := Count - 1 downto Index do FStrings[I + 1] := FStrings[I]; FStrings[Index].Str := S; FStrings[Index].Obj := AObject; Inc(FCount); end; function TJclAnsiStringList.AddObject(const S: AnsiString; AObject: TObject): Integer; var Found: Boolean; begin if not Sorted then Result := Count else begin Found := Find(S, Result); case Duplicates of dupAccept: ; dupIgnore: if Found then Exit; dupError: if Found then Error(@SDuplicateString, 0); end; end; InsertObject(Result, S, AObject); end; procedure TJclAnsiStringList.Delete(Index: Integer); var I: Integer; begin if (Index < 0) or (Index >= FCount) then Error(@SListIndexError, Index); for I := Index to Count - 2 do FStrings[I] := FStrings[I + 1]; FStrings[FCount - 1].Str := ''; // the last string is no longer useful Dec(FCount); end; procedure TJclAnsiStringList.Clear; var I: Integer; begin FCount := 0; for I := 0 to Length(FStrings) - 1 do begin FStrings[I].Str := ''; FStrings[I].Obj := nil; end; end; function TJclAnsiStringList.Find(const S: AnsiString; var Index: Integer): Boolean; var L, H, I, C: Integer; begin Result := False; L := 0; H := FCount - 1; while L <= H do begin I := (L + H) shr 1; C := CompareStrings(FStrings[I].Str, S); if C < 0 then L := I + 1 else begin H := I - 1; if C = 0 then begin Result := True; if Duplicates <> dupAccept then L := I; end; end; end; Index := L; end; function AnsiStringListCompareStrings(List: TJclAnsiStringList; Index1, Index2: Integer): Integer; begin Result := List.CompareStrings(List.FStrings[Index1].Str, List.FStrings[Index2].Str); end; procedure TJclAnsiStringList.Sort; begin CustomSort(AnsiStringListCompareStrings); end; procedure TJclAnsiStringList.CustomSort(Compare: TJclAnsiStringListSortCompare); begin if not Sorted and (FCount > 1) then QuickSort(0, FCount - 1, Compare); end; procedure TJclAnsiStringList.QuickSort(L, R: Integer; SCompare: TJclAnsiStringListSortCompare); var I, J, P: Integer; begin repeat I := L; J := R; P := (L + R) shr 1; repeat while SCompare(Self, I, P) < 0 do Inc(I); while SCompare(Self, J, P) > 0 do Dec(J); if I <= J then begin if I <> J then Exchange(I, J); if P = I then P := J else if P = J then P := I; Inc(I); Dec(J); end; until I > J; if L < J then QuickSort(L, J, SCompare); L := I; until I >= R; end; procedure TJclAnsiStringList.SetSorted(Value: Boolean); begin if FSorted <> Value then begin if Value then Sort; FSorted := Value; end; end; {$ENDIF SUPPORTS_UNICODE} // String Test Routines function StrIsAlpha(const S: AnsiString): Boolean; var I: SizeInt; begin Result := S <> ''; for I := 1 to Length(S) do begin if not CharIsAlpha(S[I]) then begin Result := False; Exit; end; end; end; function StrIsAlphaNum(const S: AnsiString): Boolean; var I: SizeInt; begin Result := S <> ''; for I := 1 to Length(S) do begin if not CharIsAlphaNum(S[I]) then begin Result := False; Exit; end; end; end; function StrConsistsofNumberChars(const S: AnsiString): Boolean; var I: SizeInt; begin Result := S <> ''; for I := 1 to Length(S) do begin if not CharIsNumberChar(S[I]) then begin Result := False; Exit; end; end; end; function StrContainsChars(const S: AnsiString; Chars: TSysCharSet; CheckAll: Boolean): Boolean; var I: SizeInt; C: AnsiChar; begin Result := Chars = []; if not Result then begin if CheckAll then begin for I := 1 to Length(S) do begin C := S[I]; if C in Chars then begin Chars := Chars - [C]; if Chars = [] then Break; end; end; Result := (Chars = []); end else begin for I := 1 to Length(S) do if S[I] in Chars then begin Result := True; Break; end; end; end; end; function StrIsAlphaNumUnderscore(const S: AnsiString): Boolean; var I: SizeInt; C: AnsiChar; begin for i := 1 to Length(s) do begin C := S[I]; if not (CharIsAlphaNum(C) or (C = '_')) then begin Result := False; Exit; end; end; Result := True and (Length(S) > 0); end; function StrIsDigit(const S: AnsiString): Boolean; var I: SizeInt; begin Result := S <> ''; for I := 1 to Length(S) do begin if not CharIsDigit(S[I]) then begin Result := False; Exit; end; end; end; function StrIsSubset(const S: AnsiString; const ValidChars: TSysCharSet): Boolean; var I: SizeInt; begin for I := 1 to Length(S) do begin if not (S[I] in ValidChars) then begin Result := False; Exit; end; end; Result := True and (Length(S) > 0); end; function StrSame(const S1, S2: AnsiString): Boolean; begin Result := StrCompare(S1, S2) = 0; end; //=== String Transformation Routines ========================================= function StrCenter(const S: AnsiString; L: SizeInt; C: AnsiChar = ' '): AnsiString; begin if Length(S) < L then begin Result := StringOfChar(C, (L - Length(S)) div 2) + S; Result := Result + StringOfChar(C, L - Length(Result)); end else Result := S; end; function StrCharPosLower(const S: AnsiString; CharPos: SizeInt): AnsiString; begin Result := S; if (CharPos > 0) and (CharPos <= Length(S)) then Result[CharPos] := CharLower(Result[CharPos]); end; function StrCharPosUpper(const S: AnsiString; CharPos: SizeInt): AnsiString; begin Result := S; if (CharPos > 0) and (CharPos <= Length(S)) then Result[CharPos] := CharUpper(Result[CharPos]); end; function StrDoubleQuote(const S: AnsiString): AnsiString; begin Result := AnsiDoubleQuote + S + AnsiDoubleQuote; end; function StrEnsureNoPrefix(const Prefix, Text: AnsiString): AnsiString; var PrefixLen: SizeInt; begin PrefixLen := Length(Prefix); if Copy(Text, 1, PrefixLen) = Prefix then Result := Copy(Text, PrefixLen + 1, Length(Text)) else Result := Text; end; function StrEnsureNoSuffix(const Suffix, Text: AnsiString): AnsiString; var SuffixLen: SizeInt; StrLength: SizeInt; begin SuffixLen := Length(Suffix); StrLength := Length(Text); if Copy(Text, StrLength - SuffixLen + 1, SuffixLen) = Suffix then Result := Copy(Text, 1, StrLength - SuffixLen) else Result := Text; end; function StrEnsurePrefix(const Prefix, Text: AnsiString): AnsiString; var PrefixLen: SizeInt; begin PrefixLen := Length(Prefix); if Copy(Text, 1, PrefixLen) = Prefix then Result := Text else Result := Prefix + Text; end; function StrEnsureSuffix(const Suffix, Text: AnsiString): AnsiString; var SuffixLen: SizeInt; begin SuffixLen := Length(Suffix); if Copy(Text, Length(Text) - SuffixLen + 1, SuffixLen) = Suffix then Result := Text else Result := Text + Suffix; end; function StrEscapedToString(const S: AnsiString): AnsiString; procedure HandleHexEscapeSeq(const S: AnsiString; var I: SizeInt; Len: SizeInt; var Dest: AnsiString); const HexDigits = AnsiString('0123456789abcdefABCDEF'); var StartI, Val, N: SizeInt; begin StartI := I; N := Pos(S[I + 1], HexDigits) - 1; if N < 0 then // '\x' without hex digit following is not escape sequence Dest := Dest + '\x' else begin Inc(I); // Jump over x if N >= 16 then N := N - 6; Val := N; // Same for second digit if I < Len then begin N := Pos(S[I + 1], HexDigits) - 1; if N >= 0 then begin Inc(I); // Jump over first digit if N >= 16 then N := N - 6; Val := Val * 16 + N; end; end; if Val > Ord(High(AnsiChar)) then raise EJclAnsiStringError.CreateResFmt(@RsNumericConstantTooLarge, [Val, StartI]); Dest := Dest + AnsiChar(Val); end; end; procedure HandleOctEscapeSeq(const S: AnsiString; var I: SizeInt; Len: SizeInt; var Dest: AnsiString); const OctDigits = AnsiString('01234567'); var StartI, Val, N: SizeInt; begin StartI := I; // first digit Val := Pos(S[I], OctDigits) - 1; if I < Len then begin N := Pos(S[I + 1], OctDigits) - 1; if N >= 0 then begin Inc(I); Val := Val * 8 + N; end; if I < Len then begin N := Pos(S[I + 1], OctDigits) - 1; if N >= 0 then begin Inc(I); Val := Val * 8 + N; end; end; end; if Val > Ord(High(AnsiChar)) then raise EJclAnsiStringError.CreateResFmt(@RsNumericConstantTooLarge, [Val, StartI]); Dest := Dest + AnsiChar(Val); end; var I, Len: SizeInt; begin Result := ''; I := 1; Len := Length(S); while I <= Len do begin if not ((S[I] = '\') and (I < Len)) then Result := Result + S[I] else begin Inc(I); // Jump over escape character case S[I] of 'a': Result := Result + AnsiBell; 'b': Result := Result + AnsiBackspace; 'f': Result := Result + AnsiFormFeed; 'n': Result := Result + AnsiLineFeed; 'r': Result := Result + AnsiCarriageReturn; 't': Result := Result + AnsiTab; 'v': Result := Result + AnsiVerticalTab; '\': Result := Result + '\'; '"': Result := Result + '"'; '''': Result := Result + ''''; // Optionally escaped '?': Result := Result + '?'; // Optionally escaped 'x': if I < Len then // Start of hex escape sequence HandleHexEscapeSeq(S, I, Len, Result) else // '\x' at end of AnsiString is not escape sequence Result := Result + '\x'; '0'..'7': // start of octal escape sequence HandleOctEscapeSeq(S, I, Len, Result); else // no escape sequence Result := Result + '\' + S[I]; end; end; Inc(I); end; end; function StrLower(const S: AnsiString): AnsiString; begin Result := S; StrLowerInPlace(Result); end; procedure StrLowerInPlace(var S: AnsiString); begin StrCase(S, AnsiLoOffset); end; procedure StrLowerBuff(S: PAnsiChar); begin StrCaseBuff(S, AnsiLoOffset); end; procedure StrMove(var Dest: AnsiString; const Source: AnsiString; const ToIndex, FromIndex, Count: SizeInt); begin // Check strings if (Source = '') or (Length(Dest) = 0) then Exit; // Check FromIndex if (FromIndex <= 0) or (FromIndex > Length(Source)) or (ToIndex <= 0) or (ToIndex > Length(Dest)) or ((FromIndex + Count - 1) > Length(Source)) or ((ToIndex + Count - 1) > Length(Dest)) then { TODO : Is failure without notice the proper thing to do here? } Exit; // Move Move(Source[FromIndex], Dest[ToIndex], Count); end; function StrPadLeft(const S: AnsiString; Len: SizeInt; C: AnsiChar): AnsiString; var L: SizeInt; begin L := Length(S); if L < Len then Result := StringOfChar(C, Len - L) + S else Result := S; end; function StrPadRight(const S: AnsiString; Len: SizeInt; C: AnsiChar): AnsiString; var L: SizeInt; begin L := Length(S); if L < Len then Result := S + StringOfChar(C, Len - L) else Result := S; end; function StrProper(const S: AnsiString): AnsiString; begin Result := StrLower(S); if Result <> '' then Result[1] := UpCase(Result[1]); end; procedure StrProperBuff(S: PAnsiChar); begin if (S <> nil) and (S^ <> #0) then begin StrLowerBuff(S); S^ := CharUpper(S^); end; end; function StrQuote(const S: AnsiString; C: AnsiChar): AnsiString; var L: SizeInt; begin L := Length(S); Result := S; if L > 0 then begin if Result[1] <> C then begin Result := C + Result; Inc(L); end; if Result[L] <> C then Result := Result + C; end; end; function StrRemoveChars(const S: AnsiString; const Chars: TSysCharSet): AnsiString; var Source, Dest: PAnsiChar; Index, Len: SizeInt; begin Len := Length(S); SetLength(Result, Len); UniqueString(Result); Source := PAnsiChar(S); Dest := PAnsiChar(Result); for Index := 0 to Len - 1 do begin if not (Source^ in Chars) then begin Dest^ := Source^; Inc(Dest); end; Inc(Source); end; SetLength(Result, Dest - PAnsiChar(Result)); end; function StrKeepChars(const S: AnsiString; const Chars: TSysCharSet): AnsiString; var Source, Dest: PAnsiChar; Index, Len: SizeInt; begin Len := Length(S); SetLength(Result, Len); UniqueString(Result); Source := PAnsiChar(S); Dest := PAnsiChar(Result); for Index := 0 to Len - 1 do begin if Source^ in Chars then begin Dest^ := Source^; Inc(Dest); end; Inc(Source); end; SetLength(Result, Dest - PAnsiChar(Result)); end; function StrRepeat(const S: AnsiString; Count: SizeInt): AnsiString; var L: SizeInt; P: PAnsiChar; begin L := Length(S); SetLength(Result, Count * L); P := Pointer(Result); if P <> nil then begin while Count > 0 do begin Move(Pointer(S)^, P^, L); P := P + L; Dec(Count); end; end; end; function StrRepeatLength(const S: AnsiString; const L: SizeInt): AnsiString; var Count: SizeInt; LenS: SizeInt; P: PAnsiChar; begin Result := ''; LenS := Length(S); if (LenS > 0) and (S <> '') then begin Count := L div LenS; if Count * LenS < L then Inc(Count); SetLength(Result, Count * LenS); P := Pointer(Result); while Count > 0 do begin Move(Pointer(S)^, P^, LenS); P := P + LenS; Dec(Count); end; if Length(S) > L then SetLength(Result, L); end; end; procedure StrReplace(var S: AnsiString; const Search, Replace: AnsiString; Flags: TReplaceFlags); var SearchStr: AnsiString; ResultStr: AnsiString; { result string } SourcePtr: PAnsiChar; { pointer into S of character under examination } SourceMatchPtr: PAnsiChar; { pointers into S and Search when first character has } SearchMatchPtr: PAnsiChar; { been matched and we're probing for a complete match } ResultPtr: PAnsiChar; { pointer into Result of character being written } ResultIndex: SizeInt; SearchLength: SizeInt; { length of search string } ReplaceLength: SizeInt; { length of replace string } BufferLength: SizeInt; { length of temporary result buffer } ResultLength: SizeInt; { length of result string } C: AnsiChar; { first character of search string } IgnoreCase: Boolean; begin if Search = '' then begin if S = '' then begin S := Replace; Exit; end else raise EJclAnsiStringError.CreateRes(@RsBlankSearchString); end; if S <> '' then begin IgnoreCase := rfIgnoreCase in Flags; if IgnoreCase then SearchStr := StrUpper(Search) else SearchStr := Search; { avoid having to call Length() within the loop } SearchLength := Length(Search); ReplaceLength := Length(Replace); ResultLength := Length(S); BufferLength := ResultLength; SetLength(ResultStr, BufferLength); { get pointers to begin of source and result } ResultPtr := PAnsiChar(ResultStr); SourcePtr := PAnsiChar(S); C := SearchStr[1]; { while we haven't reached the end of the string } while True do begin { copy characters until we find the first character of the search string } if IgnoreCase then while (CharUpper(SourcePtr^) <> C) and (SourcePtr^ <> #0) do begin ResultPtr^ := SourcePtr^; Inc(ResultPtr); Inc(SourcePtr); end else while (SourcePtr^ <> C) and (SourcePtr^ <> #0) do begin ResultPtr^ := SourcePtr^; Inc(ResultPtr); Inc(SourcePtr); end; { did we find that first character or did we hit the end of the string? } if SourcePtr^ = #0 then Break else begin { continue comparing, +1 because first character was matched already } SourceMatchPtr := SourcePtr + 1; SearchMatchPtr := PAnsiChar(SearchStr) + 1; if IgnoreCase then while (CharUpper(SourceMatchPtr^) = SearchMatchPtr^) and (SearchMatchPtr^ <> #0) do begin Inc(SourceMatchPtr); Inc(SearchMatchPtr); end else while (SourceMatchPtr^ = SearchMatchPtr^) and (SearchMatchPtr^ <> #0) do begin Inc(SourceMatchPtr); Inc(SearchMatchPtr); end; { did we find a complete match? } if SearchMatchPtr^ = #0 then begin // keep track of result length Inc(ResultLength, ReplaceLength - SearchLength); if ReplaceLength > 0 then begin // increase buffer size if required if ResultLength > BufferLength then begin BufferLength := ResultLength * 2; ResultIndex := ResultPtr - PAnsiChar(ResultStr) + 1; SetLength(ResultStr, BufferLength); ResultPtr := @ResultStr[ResultIndex]; end; { append replace to result and move past the search string in source } Move((@Replace[1])^, ResultPtr^, ReplaceLength); end; Inc(SourcePtr, SearchLength); Inc(ResultPtr, ReplaceLength); { replace all instances or just one? } if not (rfReplaceAll in Flags) then begin { just one, copy until end of source and break out of loop } while SourcePtr^ <> #0 do begin ResultPtr^ := SourcePtr^; Inc(ResultPtr); Inc(SourcePtr); end; Break; end; end else begin { copy current character and start over with the next } ResultPtr^ := SourcePtr^; Inc(ResultPtr); Inc(SourcePtr); end; end; end; { set result length and copy result into S } SetLength(ResultStr, ResultLength); S := ResultStr; end; end; function StrReplaceChar(const S: AnsiString; const Source, Replace: AnsiChar): AnsiString; var I: SizeInt; begin Result := S; for I := 1 to Length(S) do if Result[I] = Source then Result[I] := Replace; end; function StrReplaceChars(const S: AnsiString; const Chars: TSysCharSet; Replace: AnsiChar): AnsiString; var I: SizeInt; begin Result := S; for I := 1 to Length(S) do if Result[I] in Chars then Result[I] := Replace; end; function StrReplaceButChars(const S: AnsiString; const Chars: TSysCharSet; Replace: AnsiChar): AnsiString; var I: SizeInt; begin Result := S; for I := 1 to Length(S) do if not (Result[I] in Chars) then Result[I] := Replace; end; function StrReverse(const S: AnsiString): AnsiString; begin Result := S; StrReverseInplace(Result); end; procedure StrReverseInPlace(var S: AnsiString); var P1, P2: PAnsiChar; C: AnsiChar; begin UniqueString(S); P1 := PAnsiChar(S); P2 := P1 + SizeOf(AnsiChar) * (Length(S) - 1); while P1 < P2 do begin C := P1^; P1^ := P2^; P2^ := C; Inc(P1); Dec(P2); end; end; function StrSingleQuote(const S: AnsiString): AnsiString; begin Result := AnsiSingleQuote + S + AnsiSingleQuote; end; procedure StrSkipChars(var S: PAnsiChar; const Chars: TSysCharSet); begin while S^ in Chars do Inc(S); end; procedure StrSkipChars(const S: AnsiString; var Index: SizeInt; const Chars: TSysCharSet); begin while S[Index] in Chars do Inc(Index); end; function StrSmartCase(const S: AnsiString; Delimiters: TSysCharSet): AnsiString; var Source, Dest: PAnsiChar; Index, Len: SizeInt; begin Result := ''; if Delimiters = [] then Include(Delimiters, AnsiSpace); if S <> '' then begin Result := S; UniqueString(Result); Len := Length(S); Source := PAnsiChar(S); Dest := PAnsiChar(Result); Inc(Dest); for Index := 2 to Len do begin if (Source^ in Delimiters) then Dest^ := CharUpper(Dest^); Inc(Dest); Inc(Source); end; Result[1] := CharUpper(Result[1]); end; end; function StrStringToEscaped(const S: AnsiString): AnsiString; var I: SizeInt; begin Result := ''; for I := 1 to Length(S) do begin case S[I] of AnsiBackspace: Result := Result + '\b'; AnsiBell: Result := Result + '\a'; AnsiCarriageReturn: Result := Result + '\r'; AnsiFormFeed: Result := Result + '\f'; AnsiLineFeed: Result := Result + '\n'; AnsiTab: Result := Result + '\t'; AnsiVerticalTab: Result := Result + '\v'; '\': Result := Result + '\\'; '"': Result := Result + '\"'; else // Characters < ' ' are escaped with hex sequence if S[I] < #32 then Result := Result + AnsiString(Format('\x%.2x', [SizeInt(S[I])])) else Result := Result + S[I]; end; end; end; function StrStripNonNumberChars(const S: AnsiString): AnsiString; var I: SizeInt; C: AnsiChar; begin Result := ''; for I := 1 to Length(S) do begin C := S[I]; if CharIsNumberChar(C) then Result := Result + C; end; end; function StrToHex(const Source: AnsiString): AnsiString; var Index: SizeInt; C, L, N: SizeInt; BL, BH: Byte; S: AnsiString; begin Result := ''; if Source <> '' then begin S := Source; L := Length(S); if Odd(L) then begin S := '0' + S; Inc(L); end; Index := 1; SetLength(Result, L div 2); C := 1; N := 1; while C <= L do begin BH := CharHex(S[Index]); Inc(Index); BL := CharHex(S[Index]); Inc(Index); Inc(C, 2); if (BH = $FF) or (BL = $FF) then begin Result := ''; Exit; end; Result[N] := AnsiChar((Cardinal(BH) shl 4) or Cardinal(BL)); Inc(N); end; end; end; function StrTrimCharLeft(const S: AnsiString; C: AnsiChar): AnsiString; var I, L: SizeInt; begin I := 1; L := Length(S); while (I <= L) and (S[I] = C) do Inc(I); Result := Copy(S, I, L - I + 1); end; function StrTrimCharsLeft(const S: AnsiString; const Chars: TSysCharSet): AnsiString; var I, L: SizeInt; begin I := 1; L := Length(S); while (I <= L) and (S[I] in Chars) do Inc(I); Result := Copy(S, I, L - I + 1); end; function StrTrimCharsRight(const S: AnsiString; const Chars: TSysCharSet): AnsiString; var I: SizeInt; begin I := Length(S); while (I >= 1) and (S[I] in Chars) do Dec(I); Result := Copy(S, 1, I); end; function StrTrimCharRight(const S: AnsiString; C: AnsiChar): AnsiString; var I: SizeInt; begin I := Length(S); while (I >= 1) and (S[I] = C) do Dec(I); Result := Copy(S, 1, I); end; function StrTrimQuotes(const S: AnsiString): AnsiString; var First, Last: AnsiChar; L: SizeInt; begin L := Length(S); if L > 1 then begin First := S[1]; Last := S[L]; if (First = Last) and ((First = AnsiSingleQuote) or (First = AnsiDoubleQuote)) then Result := Copy(S, 2, L - 2) else Result := S; end else Result := S; end; function StrTrimQuotes(const S: AnsiString; QuoteChar: AnsiChar): AnsiString; var First, Last: AnsiChar; L: SizeInt; begin L := Length(S); if L > 1 then begin First := S[1]; Last := S[L]; if (First = Last) and (First = QuoteChar) then Result := Copy(S, 2, L - 2) else Result := S; end else Result := S; end; function StrUpper(const S: AnsiString): AnsiString; begin Result := S; StrUpperInPlace(Result); end; procedure StrUpperInPlace(var S: AnsiString); begin StrCase(S, AnsiUpOffset); end; procedure StrUpperBuff(S: PAnsiChar); begin StrCaseBuff(S, AnsiUpOffset); end; {$IFDEF MSWINDOWS} function StrOemToAnsi(const S: AnsiString): AnsiString; begin SetLength(Result, Length(S)); OemToAnsiBuff(PAnsiChar(S), PAnsiChar(Result), Length(S)); end; function StrAnsiToOem(const S: AnsiString): AnsiString; begin SetLength(Result, Length(S)); AnsiToOemBuff(PAnsiChar(S), PAnsiChar(Result), Length(S)); end; {$ENDIF MSWINDOWS} //=== String Management ====================================================== procedure StrAddRef(var S: AnsiString); var P: PAnsiStrRec; begin P := Pointer(S); if P <> nil then begin Dec(P); if P^.RefCount = -1 then UniqueString(S) else LockedInc(P^.RefCount); end; end; procedure StrDecRef(var S: AnsiString); var P: PAnsiStrRec; begin P := Pointer(S); if P <> nil then begin Dec(P); case P^.RefCount of -1, 0: { nothing } ; 1: begin Finalize(S); Pointer(S) := nil; end; else LockedDec(P^.RefCount); end; end; end; function StrLength(const S: AnsiString): Longint; var P: PAnsiStrRec; begin Result := 0; P := Pointer(S); if P <> nil then begin Dec(P); Result := P^.Length and (not $80000000 shr 1); end; end; function StrRefCount(const S: AnsiString): Longint; var P: PAnsiStrRec; begin Result := 0; P := Pointer(S); if P <> nil then begin Dec(P); Result := P^.RefCount; end; end; procedure StrResetLength(var S: AnsiString); var I: SizeInt; begin for I := 0 to Length(S) - 1 do if S[I + 1] = #0 then begin SetLength(S, I); Exit; end; end; //=== String Search and Replace Routines ===================================== function StrCharCount(const S: AnsiString; C: AnsiChar): SizeInt; var I: SizeInt; begin Result := 0; for I := 1 to Length(S) do if S[I] = C then Inc(Result); end; function StrCharsCount(const S: AnsiString; Chars: TSysCharSet): SizeInt; var I: SizeInt; begin Result := 0; for I := 1 to Length(S) do if S[I] in Chars then Inc(Result); end; function StrStrCount(const S, SubS: AnsiString): SizeInt; var I: SizeInt; begin Result := 0; if (Length(SubS) > Length(S)) or (Length(SubS) = 0) or (Length(S) = 0) then Exit; if Length(SubS) = 1 then begin Result := StrCharCount(S, SubS[1]); Exit; end; I := StrSearch(SubS, S, 1); if I > 0 then Inc(Result); while (I > 0) and (Length(S) > I + Length(SubS)) do begin I := StrSearch(SubS, S, I + 1); if I > 0 then Inc(Result); end; end; (* { 1} Test(StrCompareRange('', '', 1, 5), 0); { 2} Test(StrCompareRange('A', '', 1, 5), -1); { 3} Test(StrCompareRange('AB', '', 1, 5), -1); { 4} Test(StrCompareRange('ABC', '', 1, 5), -1); { 5} Test(StrCompareRange('', 'A', 1, 5), -1); { 6} Test(StrCompareRange('', 'AB', 1, 5), -1); { 7} Test(StrCompareRange('', 'ABC', 1, 5), -1); { 8} Test(StrCompareRange('A', 'a', 1, 5), -2); { 9} Test(StrCompareRange('A', 'a', 1, 1), -32); {10} Test(StrCompareRange('aA', 'aB', 1, 1), 0); {11} Test(StrCompareRange('aA', 'aB', 1, 2), -1); {12} Test(StrCompareRange('aB', 'aA', 1, 2), 1); {13} Test(StrCompareRange('aA', 'aa', 1, 2), -32); {14} Test(StrCompareRange('aa', 'aA', 1, 2), 32); {15} Test(StrCompareRange('', '', 1, 0), 0); {16} Test(StrCompareRange('A', 'A', 1, 0), -2); {17} Test(StrCompareRange('Aa', 'A', 1, 0), -2); {18} Test(StrCompareRange('Aa', 'Aa', 1, 2), 0); {19} Test(StrCompareRange('Aa', 'A', 1, 2), 0); {20} Test(StrCompareRange('Ba', 'A', 1, 2), 1); *) function StrCompareRangeEx(const S1, S2: AnsiString; Index, Count: SizeInt; CaseSensitive: Boolean): SizeInt; var Len1, Len2: SizeInt; I: SizeInt; C1, C2: AnsiChar; begin if Pointer(S1) = Pointer(S2) then begin if (Count <= 0) and (S1 <> '') then Result := -2 // no work else Result := 0; end else if (S1 = '') or (S2 = '') then Result := -1 // null string else if Count <= 0 then Result := -2 // no work else begin Len1 := Length(S1); Len2 := Length(S2); if (Index - 1) + Count > Len1 then Result := -2 else begin if (Index - 1) + Count > Len2 then // strange behaviour, but the assembler code does it Count := Len2 - (Index - 1); if CaseSensitive then begin for I := 0 to Count - 1 do begin C1 := S1[Index + I]; C2 := S2[Index + I]; if C1 <> C2 then begin Result := Ord(C1) - Ord(C2); Exit; end; end; end else begin for I := 0 to Count - 1 do begin C1 := S1[Index + I]; C2 := S2[Index + I]; if C1 <> C2 then begin C1 := CharLower(C1); C2 := CharLower(C2); if C1 <> C2 then begin Result := Ord(C1) - Ord(C2); Exit; end; end; end; end; Result := 0; end; end; end; function StrCompare(const S1, S2: AnsiString; CaseSensitive: Boolean): SizeInt; var Len1, Len2: SizeInt; begin if Pointer(S1) = Pointer(S2) then Result := 0 else begin Len1 := Length(S1); Len2 := Length(S2); Result := Len1 - Len2; if Result = 0 then Result := StrCompareRangeEx(S1, S2, 1, Len1, CaseSensitive); end; end; function StrCompareRange(const S1, S2: AnsiString; Index, Count: SizeInt; CaseSensitive: Boolean): SizeInt; begin Result := StrCompareRangeEx(S1, S2, Index, Count, CaseSensitive); end; function StrRepeatChar(C: AnsiChar; Count: SizeInt): AnsiString; begin SetLength(Result, Count); if Count > 0 then FillChar(Result[1], Count, C); end; function StrFind(const Substr, S: AnsiString; const Index: SizeInt): SizeInt; var pos: SizeInt; begin if (SubStr <> '') and (S <> '') then begin pos := StrIPos(Substr, Copy(S, Index, Length(S) - Index + 1)); if pos = 0 then Result := 0 else Result := Index + Pos - 1; end else Result := 0; end; function StrHasPrefix(const S: AnsiString; const Prefixes: array of AnsiString): Boolean; begin Result := StrPrefixIndex(S, Prefixes) > -1; end; function StrHasSuffix(const S: AnsiString; const Suffixes: array of AnsiString): Boolean; begin Result := StrSuffixIndex(S, Suffixes) > -1; end; function StrIHasPrefix(const S: AnsiString; const Prefixes: array of AnsiString): Boolean; begin Result := StrIPrefixIndex(S, Prefixes) > -1; end; function StrIHasSuffix(const S: AnsiString; const Suffixes: array of AnsiString): Boolean; begin Result := StrISuffixIndex(S, Suffixes) > -1; end; function StrIndex(const S: AnsiString; const List: array of AnsiString; CaseSensitive: Boolean): SizeInt; var I: SizeInt; begin Result := -1; for I := Low(List) to High(List) do begin if StrCompare(S, List[I], CaseSensitive) = 0 then begin Result := I; Break; end; end; end; function StrILastPos(const SubStr, S: AnsiString): SizeInt; begin Result := StrLastPos(StrUpper(SubStr), StrUpper(S)); end; function StrIPos(const SubStr, S: AnsiString): SizeInt; begin Result := Pos(StrUpper(SubStr), StrUpper(S)); end; function StrIPrefixIndex(const S: AnsiString; const Prefixes: array of AnsiString): SizeInt; var I: SizeInt; Test: AnsiString; begin Result := -1; for I := Low(Prefixes) to High(Prefixes) do begin Test := StrLeft(S, Length(Prefixes[I])); if CompareText(Test, Prefixes[I]) = 0 then begin Result := I; Break; end; end; end; function StrIsOneOf(const S: AnsiString; const List: array of AnsiString): Boolean; begin Result := StrIndex(S, List) > -1; end; function StrISuffixIndex(const S: AnsiString; const Suffixes: array of AnsiString): SizeInt; var I: SizeInt; Test: AnsiString; begin Result := -1; for I := Low(Suffixes) to High(Suffixes) do begin Test := StrRight(S, Length(Suffixes[I])); if CompareText(Test, Suffixes[I]) = 0 then begin Result := I; Break; end; end; end; function StrLastPos(const SubStr, S: AnsiString): SizeInt; var Last, Current: PAnsiChar; begin Result := 0; Last := nil; Current := PAnsiChar(S); while (Current <> nil) and (Current^ <> #0) do begin Current := AnsiStrPosA(PAnsiChar(Current), PAnsiChar(SubStr)); if Current <> nil then begin Last := Current; Inc(Current); end; end; if Last <> nil then Result := Abs(PAnsiChar(S) - Last) + 1; end; // IMPORTANT NOTE: The StrMatch function does currently not work with the Asterix (*) // (*) acts like (?) function StrMatch(const Substr, S: AnsiString; Index: SizeInt): SizeInt; var SI, SubI, SLen, SubLen: SizeInt; SubC: AnsiChar; begin SLen := Length(S); SubLen := Length(Substr); Result := 0; if (Index > SLen) or (SubLen = 0) then Exit; while Index <= SLen do begin SubI := 1; SI := Index; while (SI <= SLen) and (SubI <= SubLen) do begin SubC := Substr[SubI]; if (SubC = '*') or (SubC = '?') or (SubC = S[SI]) then begin Inc(SI); Inc(SubI); end else Break; end; if SubI > SubLen then begin Result := Index; Break; end; Inc(Index); end; end; // Derived from "Like" by Michael Winter function StrMatches(const Substr, S: AnsiString; const Index: SizeInt): Boolean; var StringPtr: PAnsiChar; PatternPtr: PAnsiChar; StringRes: PAnsiChar; PatternRes: PAnsiChar; begin if SubStr = '' then raise EJclAnsiStringError.CreateRes(@RsBlankSearchString); Result := SubStr = '*'; if Result or (S = '') then Exit; if (Index <= 0) or (Index > Length(S)) then raise EJclAnsiStringError.CreateRes(@RsArgumentOutOfRange); StringPtr := PAnsiChar(@S[Index]); PatternPtr := PAnsiChar(SubStr); StringRes := nil; PatternRes := nil; repeat repeat case PatternPtr^ of #0: begin Result := StringPtr^ = #0; if Result or (StringRes = nil) or (PatternRes = nil) then Exit; StringPtr := StringRes; PatternPtr := PatternRes; Break; end; '*': begin Inc(PatternPtr); PatternRes := PatternPtr; Break; end; '?': begin if StringPtr^ = #0 then Exit; Inc(StringPtr); Inc(PatternPtr); end; else begin if StringPtr^ = #0 then Exit; if StringPtr^ <> PatternPtr^ then begin if (StringRes = nil) or (PatternRes = nil) then Exit; StringPtr := StringRes; PatternPtr := PatternRes; Break; end else begin Inc(StringPtr); Inc(PatternPtr); end; end; end; until False; repeat case PatternPtr^ of #0: begin Result := True; Exit; end; '*': begin Inc(PatternPtr); PatternRes := PatternPtr; end; '?': begin if StringPtr^ = #0 then Exit; Inc(StringPtr); Inc(PatternPtr); end; else begin repeat if StringPtr^ = #0 then Exit; if StringPtr^ = PatternPtr^ then Break; Inc(StringPtr); until False; Inc(StringPtr); StringRes := StringPtr; Inc(PatternPtr); Break; end; end; until False; until False; end; function StrNPos(const S, SubStr: AnsiString; N: SizeInt): SizeInt; var I, P: SizeInt; begin if N < 1 then begin Result := 0; Exit; end; Result := StrSearch(SubStr, S, 1); I := 1; while I < N do begin P := StrSearch(SubStr, S, Result + 1); if P = 0 then begin Result := 0; Break; end else begin Result := P; Inc(I); end; end; end; function StrNIPos(const S, SubStr: AnsiString; N: SizeInt): SizeInt; var I, P: SizeInt; begin if N < 1 then begin Result := 0; Exit; end; Result := StrFind(SubStr, S, 1); I := 1; while I < N do begin P := StrFind(SubStr, S, Result + 1); if P = 0 then begin Result := 0; Break; end else begin Result := P; Inc(I); end; end; end; function StrPrefixIndex(const S: AnsiString; const Prefixes: array of AnsiString): SizeInt; var I: SizeInt; Test: AnsiString; begin Result := -1; for I := Low(Prefixes) to High(Prefixes) do begin Test := StrLeft(S, Length(Prefixes[I])); if CompareStr(Test, Prefixes[I]) = 0 then begin Result := I; Break; end; end; end; function StrSearch(const Substr, S: AnsiString; const Index: SizeInt): SizeInt; var SP, SPI, SubP: PAnsiChar; SLen: SizeInt; begin SLen := Length(S); if Index <= SLen then begin SP := PAnsiChar(S); SubP := PAnsiChar(Substr); SPI := SP; Inc(SPI, Index); Dec(SPI); SPI := StrPosA(SPI, SubP); if SPI <> nil then Result := SPI - SP + 1 else Result := 0; end else Result := 0; end; function StrSuffixIndex(const S: AnsiString; const Suffixes: array of AnsiString): SizeInt; var I: SizeInt; Test: AnsiString; begin Result := -1; for I := Low(Suffixes) to High(Suffixes) do begin Test := StrRight(S, Length(Suffixes[I])); if CompareStr(Test, Suffixes[I]) = 0 then begin Result := I; Break; end; end; end; //=== String Extraction ====================================================== function StrAfter(const SubStr, S: AnsiString): AnsiString; var P: SizeInt; begin P := StrFind(SubStr, S, 1); // StrFind is case-insensitive pos if P <= 0 then Result := '' // substr not found -> nothing after it else Result := StrRestOf(S, P + Length(SubStr)); end; function StrBefore(const SubStr, S: AnsiString): AnsiString; var P: SizeInt; begin P := StrFind(SubStr, S, 1); if P <= 0 then Result := S else Result := StrLeft(S, P - 1); end; function StrSplit(const SubStr, S: AnsiString;var Left, Right : AnsiString): boolean; var P: SizeInt; begin P := StrFind(SubStr, S, 1); Result:= p > 0; if Result then begin Left := StrLeft(S, P - 1); Right := StrRestOf(S, P + Length(SubStr)); end else begin Left := ''; Right := ''; end; end; function StrBetween(const S: AnsiString; const Start, Stop: AnsiChar): AnsiString; var PosStart, PosEnd: SizeInt; L: SizeInt; begin PosStart := Pos(Start, S); PosEnd := StrSearch(Stop, S, PosStart + 1); // PosEnd has to be after PosStart. if (PosStart > 0) and (PosEnd > PosStart) then begin L := PosEnd - PosStart; Result := Copy(S, PosStart + 1, L - 1); end else Result := ''; end; function StrChopRight(const S: AnsiString; N: SizeInt): AnsiString; begin Result := Copy(S, 1, Length(S) - N); end; function StrLeft(const S: AnsiString; Count: SizeInt): AnsiString; begin Result := Copy(S, 1, Count); end; function StrMid(const S: AnsiString; Start, Count: SizeInt): AnsiString; begin Result := Copy(S, Start, Count); end; function StrRestOf(const S: AnsiString; N: SizeInt): AnsiString; begin Result := Copy(S, N, (Length(S) - N + 1)); end; function StrRight(const S: AnsiString; Count: SizeInt): AnsiString; begin Result := Copy(S, Length(S) - Count + 1, Count); end; //=== Character (do we have it ;) ============================================ function CharEqualNoCase(const C1, C2: AnsiChar): Boolean; begin // if they are not equal chars, may be same letter different case Result := (C1 = C2) or (CharIsAlpha(C1) and CharIsAlpha(C2) and (CharLower(C1) = CharLower(C2))); end; function CharIsAlpha(const C: AnsiChar): Boolean; begin Result := (AnsiCharTypes[C] and C1_ALPHA) <> 0; end; function CharIsAlphaNum(const C: AnsiChar): Boolean; begin Result := ((AnsiCharTypes[C] and C1_ALPHA) <> 0) or ((AnsiCharTypes[C] and C1_DIGIT) <> 0); end; function CharIsBlank(const C: AnsiChar): Boolean; begin Result := ((AnsiCharTypes[C] and C1_BLANK) <> 0); end; function CharIsControl(const C: AnsiChar): Boolean; begin Result := (AnsiCharTypes[C] and C1_CNTRL) <> 0; end; function CharIsDelete(const C: AnsiChar): Boolean; begin Result := (C = #8); end; function CharIsDigit(const C: AnsiChar): Boolean; begin Result := (AnsiCharTypes[C] and C1_DIGIT) <> 0; end; function CharIsFracDigit(const C: AnsiChar): Boolean; begin Result := (C = '.') or ((AnsiCharTypes[C] and C1_DIGIT) <> 0); end; function CharIsHexDigit(const C: AnsiChar): Boolean; begin case C of 'A'..'F', 'a'..'f': Result := True; else Result := ((AnsiCharTypes[C] and C1_DIGIT) <> 0); end; end; function CharIsLower(const C: AnsiChar): Boolean; begin Result := (AnsiCharTypes[C] and C1_LOWER) <> 0; end; // JclSysUtils.TJclFormatSettings.GetDecimalSeparator is manually inlined in the 2 following functions // this fixes compiler warnings about functions not being inlined function CharIsNumberChar(const C: AnsiChar): Boolean; begin Result := ((AnsiCharTypes[C] and C1_DIGIT) <> 0) or (C = AnsiSignMinus) or (C = AnsiSignPlus) or (Char(C) = {$IFDEF RTL220_UP}FormatSettings.DecimalSeparator{$ELSE}SysUtils.DecimalSeparator{$ENDIF}); end; function CharIsNumber(const C: AnsiChar): Boolean; begin Result := ((AnsiCharTypes[C] and C1_DIGIT) <> 0) or (Char(C) = {$IFDEF RTL220_UP}FormatSettings.DecimalSeparator{$ELSE}SysUtils.DecimalSeparator{$ENDIF}); end; function CharIsPrintable(const C: AnsiChar): Boolean; begin Result := not CharIsControl(C); end; function CharIsPunctuation(const C: AnsiChar): Boolean; begin Result := ((AnsiCharTypes[C] and C1_PUNCT) <> 0); end; function CharIsReturn(const C: AnsiChar): Boolean; begin Result := (C = AnsiLineFeed) or (C = AnsiCarriageReturn); end; function CharIsSpace(const C: AnsiChar): Boolean; begin Result := (AnsiCharTypes[C] and C1_SPACE) <> 0; end; function CharIsUpper(const C: AnsiChar): Boolean; begin Result := (AnsiCharTypes[C] and C1_UPPER) <> 0; end; function CharIsValidIdentifierLetter(const C: AnsiChar): Boolean; begin case C of '0'..'9', 'A'..'Z', 'a'..'z', '_': Result := True; else Result := False; end; end; function CharIsWhiteSpace(const C: AnsiChar): Boolean; begin Result := (C = AnsiTab) or (C = AnsiLineFeed) or (C = AnsiVerticalTab) or (C = AnsiFormFeed) or (C = AnsiCarriageReturn) or (C =AnsiSpace) or ((AnsiCharTypes[C] and C1_SPACE) <> 0); end; function CharIsWildcard(const C: AnsiChar): Boolean; begin case C of '*', '?': Result := True; else Result := False; end; end; function CharType(const C: AnsiChar): Word; begin Result := AnsiCharTypes[C]; end; //=== PCharVector ============================================================ function StringsToPCharVector(var Dest: PAnsiCharVector; const Source: TJclAnsiStrings): PAnsiCharVector; var I: SizeInt; S: AnsiString; List: array of PAnsiChar; begin Assert(Source <> nil); Dest := AllocMem((Source.Count + SizeOf(AnsiChar)) * SizeOf(PAnsiChar)); SetLength(List, Source.Count + SizeOf(AnsiChar)); for I := 0 to Source.Count - 1 do begin S := Source[I]; {$IFDEF SUPPORTS_UNICODE} List[I] := AnsiStrAlloc(Length(S) + SizeOf(AnsiChar)); {$ELSE ~SUPPORTS_UNICODE} List[I] := StrAlloc(Length(S) + SizeOf(AnsiChar)); {$ENDIF ~SUPPORTS_UNICODE} StrPCopyA(List[I], S); end; List[Source.Count] := nil; Move(List[0], Dest^, (Source.Count + 1) * SizeOf(PAnsiChar)); Result := Dest; end; function PCharVectorCount(Source: PAnsiCharVector): SizeInt; begin Result := 0; if Source <> nil then while Source^ <> nil do begin Inc(Source); Inc(Result); end; end; procedure PCharVectorToStrings(const Dest: TJclAnsiStrings; Source: PAnsiCharVector); var I, Count: SizeInt; List: array of PAnsiChar; begin Assert(Dest <> nil); if Source <> nil then begin Count := PCharVectorCount(Source); SetLength(List, Count); Move(Source^, List[0], Count * SizeOf(PAnsiChar)); Dest.BeginUpdate; try Dest.Clear; for I := 0 to Count - 1 do Dest.Add(List[I]); finally Dest.EndUpdate; end; end; end; procedure FreePCharVector(var Dest: PAnsiCharVector); var I, Count: SizeInt; List: array of PAnsiChar; begin if Dest <> nil then begin Count := PCharVectorCount(Dest); SetLength(List, Count); Move(Dest^, List[0], Count * SizeOf(PAnsiChar)); for I := 0 to Count - 1 do StrDisposeA(List[I]); FreeMem(Dest, (Count + 1) * SizeOf(PAnsiChar)); Dest := nil; end; end; //=== Character Transformation Routines ====================================== function CharHex(const C: AnsiChar): Byte; begin case C of '0'..'9': Result := Ord(C) - Ord('0'); 'a'..'f': Result := Ord(C) - Ord('a') + 10; 'A'..'F': Result := Ord(C) - Ord('A') + 10; else Result := $FF; end; end; function CharLower(const C: AnsiChar): AnsiChar; begin Result := AnsiCaseMap[Ord(C) + AnsiLoOffset]; end; function CharToggleCase(const C: AnsiChar): AnsiChar; begin Result := AnsiCaseMap[Ord(C) + AnsiReOffset]; end; function CharUpper(const C: AnsiChar): AnsiChar; begin Result := AnsiCaseMap[Ord(C) + AnsiUpOffset]; end; //=== Character Search and Replace =========================================== function CharLastPos(const S: AnsiString; const C: AnsiChar; const Index: SizeInt): SizeInt; begin if (Index > 0) and (Index <= Length(S)) then for Result := Length(S) downto Index do if S[Result] = C then Exit; Result := 0; end; function CharPos(const S: AnsiString; const C: AnsiChar; const Index: SizeInt): SizeInt; begin if (Index > 0) and (Index <= Length(S)) then for Result := Index to Length(S) do if S[Result] = C then Exit; Result := 0; end; function CharIPos(const S: AnsiString; C: AnsiChar; const Index: SizeInt): SizeInt; begin if (Index > 0) and (Index <= Length(S)) then begin C := CharUpper(C); for Result := Index to Length(S) do if AnsiCaseMap[Ord(S[Result]) + AnsiUpOffset] = C then Exit; end; Result := 0; end; function CharReplace(var S: AnsiString; const Search, Replace: AnsiChar): SizeInt; var P: PAnsiChar; Index, Len: SizeInt; begin Result := 0; if Search <> Replace then begin UniqueString(S); Len := Length(S); P := PAnsiChar(S); for Index := 0 to Len - 1 do begin if P^ = Search then begin P^ := Replace; Inc(Result); end; Inc(P); end; end; end; //=== MultiSz ================================================================ function StringsToMultiSz(var Dest: PAnsiMultiSz; const Source: TJclAnsiStrings): PAnsiMultiSz; var I, TotalLength: SizeInt; P: PAnsiMultiSz; begin Assert(Source <> nil); TotalLength := 1; for I := 0 to Source.Count - 1 do if Source[I] = '' then raise EJclAnsiStringError.CreateRes(@RsInvalidEmptyStringItem) else Inc(TotalLength, StrLenA(PAnsiChar(AnsiString(Source[I]))) + 1); AllocateMultiSz(Dest, TotalLength); P := Dest; for I := 0 to Source.Count - 1 do begin P := StrECopyA(P, PAnsiChar(AnsiString(Source[I]))); Inc(P); end; P^ := #0; Result := Dest; end; procedure MultiSzToStrings(const Dest: TJclAnsiStrings; const Source: PAnsiMultiSz); var P: PAnsiMultiSz; begin Assert(Dest <> nil); Dest.BeginUpdate; try Dest.Clear; if Source <> nil then begin P := Source; while P^ <> #0 do begin Dest.Add(P); P := StrEndA(P); Inc(P); end; end; finally Dest.EndUpdate; end; end; function MultiSzLength(const Source: PAnsiMultiSz): SizeInt; var P: PAnsiMultiSz; begin Result := 0; if Source <> nil then begin P := Source; repeat Inc(Result, StrLenA(P) + 1); P := StrEndA(P); Inc(P); until P^ = #0; Inc(Result); end; end; procedure AllocateMultiSz(var Dest: PAnsiMultiSz; Len: SizeInt); begin if Len > 0 then GetMem(Dest, Len * SizeOf(AnsiChar)) else Dest := nil; end; procedure FreeMultiSz(var Dest: PAnsiMultiSz); begin if Dest <> nil then FreeMem(Dest); Dest := nil; end; function MultiSzDup(const Source: PAnsiMultiSz): PAnsiMultiSz; var Len: SizeInt; begin if Source <> nil then begin Len := MultiSzLength(Source); Result := nil; AllocateMultiSz(Result, Len); Move(Source^, Result^, Len * SizeOf(AnsiChar)); end else Result := nil; end; //=== TJclAnsiStrings Manipulation =============================================== procedure StrToStrings(S, Sep: AnsiString; const List: TJclAnsiStrings; const AllowEmptyString: Boolean = True); var I, L: SizeInt; Left: AnsiString; begin Assert(List <> nil); List.BeginUpdate; try List.Clear; L := Length(Sep); I := Pos(Sep, S); while I > 0 do begin Left := StrLeft(S, I - 1); if (Left <> '') or AllowEmptyString then List.Add(Left); Delete(S, 1, I + L - 1); I := Pos(Sep, S); end; if (S <> '') or AllowEmptyString then List.Add(S); // Ignore empty strings at the end (only if AllowEmptyString = False). finally List.EndUpdate; end; end; procedure StrIToStrings(S, Sep: AnsiString; const List: TJclAnsiStrings; const AllowEmptyString: Boolean = True); var I, L: SizeInt; LowerCaseStr: AnsiString; Left: AnsiString; begin Assert(List <> nil); LowerCaseStr := StrLower(S); Sep := StrLower(Sep); L := Length(Sep); I := Pos(Sep, LowerCaseStr); List.BeginUpdate; try List.Clear; while I > 0 do begin Left := StrLeft(S, I - 1); if (Left <> '') or AllowEmptyString then List.Add(Left); Delete(S, 1, I + L - 1); Delete(LowerCaseStr, 1, I + L - 1); I := Pos(Sep, LowerCaseStr); end; if (S <> '') or AllowEmptyString then List.Add(S); // Ignore empty strings at the end (only if AllowEmptyString = False). finally List.EndUpdate; end; end; function StringsToStr(const List: TJclAnsiStrings; const Sep: AnsiString; const AllowEmptyString: Boolean): AnsiString; var I, L: SizeInt; begin Result := ''; for I := 0 to List.Count - 1 do begin if (List[I] <> '') or AllowEmptyString then begin // don't combine these into one addition, somehow it hurts performance Result := Result + List[I]; Result := Result + Sep; end; end; // remove terminating separator if List.Count <> 0 then begin L := Length(Sep); Delete(Result, Length(Result) - L + 1, L); end; end; procedure TrimStrings(const List: TJclAnsiStrings; DeleteIfEmpty: Boolean); var I: SizeInt; begin Assert(List <> nil); List.BeginUpdate; try for I := List.Count - 1 downto 0 do begin List[I] := Trim(List[I]); if (List[I] = '') and DeleteIfEmpty then List.Delete(I); end; finally List.EndUpdate; end; end; procedure TrimStringsRight(const List: TJclAnsiStrings; DeleteIfEmpty: Boolean); var I: SizeInt; begin Assert(List <> nil); List.BeginUpdate; try for I := List.Count - 1 downto 0 do begin List[I] := TrimRight(List[I]); if (List[I] = '') and DeleteIfEmpty then List.Delete(I); end; finally List.EndUpdate; end; end; procedure TrimStringsLeft(const List: TJclAnsiStrings; DeleteIfEmpty: Boolean); var I: SizeInt; begin Assert(List <> nil); List.BeginUpdate; try for I := List.Count - 1 downto 0 do begin List[I] := TrimLeft(List[I]); if (List[I] = '') and DeleteIfEmpty then List.Delete(I); end; finally List.EndUpdate; end; end; function AddStringToStrings(const S: AnsiString; Strings: TJclAnsiStrings; const Unique: Boolean): Boolean; begin Assert(Strings <> nil); Result := Unique and (Strings.IndexOf(S) <> -1); if not Result then Result := Strings.Add(S) > -1; end; //=== Miscellaneous ========================================================== function FileToString(const FileName: TFileName): AnsiString; var FS: TFileStream; Len: SizeInt; begin FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try Len := FS.Size; SetLength(Result, Len); if Len > 0 then FS.ReadBuffer(Result[1], Len); finally FS.Free; end; end; procedure StringToFile(const FileName: TFileName; const Contents: AnsiString; Append: Boolean); var FS: TFileStream; Len: SizeInt; begin if Append and FileExists(FileName) then FS := TFileStream.Create(FileName, fmOpenReadWrite or fmShareDenyWrite) else FS := TFileStream.Create(FileName, fmCreate); try if Append then FS.Seek(0, soEnd); // faster than .Position := .Size Len := Length(Contents); if Len > 0 then FS.WriteBuffer(Contents[1], Len); finally FS.Free; end; end; function StrToken(var S: AnsiString; Separator: AnsiChar): AnsiString; var I: SizeInt; begin I := Pos(Separator, S); if I <> 0 then begin Result := Copy(S, 1, I - 1); Delete(S, 1, I); end else begin Result := S; S := ''; end; end; procedure StrTokens(const S: AnsiString; const List: TJclAnsiStrings); var Start: PAnsiChar; Token: AnsiString; Done: Boolean; begin Assert(List <> nil); if List = nil then Exit; List.BeginUpdate; try List.Clear; Start := Pointer(S); repeat Done := StrWord(Start, Token); if Token <> '' then List.Add(Token); until Done; finally List.EndUpdate; end; end; procedure StrTokenToStrings(S: AnsiString; Separator: AnsiChar; const List: TJclAnsiStrings); var Token: AnsiString; begin Assert(List <> nil); if List = nil then Exit; List.BeginUpdate; try List.Clear; while S <> '' do begin Token := StrToken(S, Separator); List.Add(Token); end; finally List.EndUpdate; end; end; function StrWord(const S: AnsiString; var Index: SizeInt; out Word: AnsiString): Boolean; var Start: SizeInt; C: AnsiChar; begin Word := ''; if (S = '') then begin Result := True; Exit; end; Start := Index; Result := False; while True do begin C := S[Index]; case C of #0: begin if Start <> 0 then Word := Copy(S, Start, Index - Start); Result := True; Exit; end; AnsiSpace, AnsiLineFeed, AnsiCarriageReturn: begin if Start <> 0 then begin Word := Copy(S, Start, Index - Start); Exit; end else begin while CharIsWhiteSpace(C) do begin Inc(Index); C := S[Index]; end; end; end; else if Start = 0 then Start := Index; Inc(Index); end; end; end; function StrWord(var S: PAnsiChar; out Word: AnsiString): Boolean; var Start: PAnsiChar; begin Word := ''; if S = nil then begin Result := True; Exit; end; Start := nil; Result := False; while True do begin case S^ of #0: begin if Start <> nil then SetString(Word, Start, S - Start); Result := True; Exit; end; AnsiSpace, AnsiLineFeed, AnsiCarriageReturn: begin if Start <> nil then begin SetString(Word, Start, S - Start); Exit; end else while CharIsWhiteSpace(S^) do Inc(S); end; else if Start = nil then Start := S; Inc(S); end; end; end; function StrIdent(const S: AnsiString; var Index: SizeInt; out Ident: AnsiString): Boolean; var Start: SizeInt; C: AnsiChar; begin Ident := ''; if (S = '') then begin Result := True; Exit; end; Start := Index; Result := False; while True do begin C := S[Index]; if CharIsValidIdentifierLetter(C) then begin if Start = 0 then Start := Index; end else if C = #0 then begin if Start <> 0 then Ident := Copy(S, Start, Index - Start); Result := True; Exit; end else begin if Start <> 0 then begin Ident := Copy(S, Start, Index - Start); Exit; end; end; Inc(Index); end; end; function StrIdent(var S: PAnsiChar; out Ident: AnsiString): Boolean; var Start: PAnsiChar; C: AnsiChar; begin Ident := ''; if S = nil then begin Result := True; Exit; end; Start := nil; Result := False; while True do begin C := S^; if CharIsValidIdentifierLetter(C) then begin if Start = nil then Start := S; end else if C = #0 then begin if Start <> nil then SetString(Ident, Start, S - Start); Result := True; Exit; end else begin if Start <> nil then begin SetString(Ident, Start, S - Start); Exit; end end; Inc(S); end; end; function StrToFloatSafe(const S: AnsiString): Float; var Temp: AnsiString; I, J, K: SizeInt; SwapSeparators, IsNegative: Boolean; DecSep: AnsiChar; ThouSep: AnsiChar; begin DecSep := AnsiChar(JclFormatSettings.DecimalSeparator); ThouSep := AnsiChar(JclFormatSettings.ThousandSeparator); Temp := S; SwapSeparators := False; IsNegative := False; J := 0; for I := 1 to Length(Temp) do begin if Temp[I] = '-' then IsNegative := not IsNegative else if not (Temp[I] in [' ', '(', '+']) then begin // if it appears prior to any digit, it has to be a decimal separator SwapSeparators := Temp[I] = ThouSep; J := I; Break; end; end; if not SwapSeparators then begin K := CharPos(Temp, DecSep); SwapSeparators := // if it appears prior to any digit, it has to be a decimal separator (K > J) and // if it appears multiple times, it has to be a thousand separator ((StrCharCount(Temp, DecSep) > 1) or // we assume (consistent with Windows Platform SDK documentation), // that thousand separators appear only to the left of the decimal (K < CharPos(Temp, ThouSep))); end; if SwapSeparators then begin // assume a numerical string from a different locale, // where DecimalSeparator and ThousandSeparator are exchanged for I := 1 to Length(Temp) do if Temp[I] = DecSep then Temp[I] := ThouSep else if Temp[I] = ThouSep then Temp[I] := DecSep; end; Temp := StrKeepChars(Temp, AnsiDecDigits + [DecSep]); if Length(Temp) > 0 then begin if Temp[1] = DecSep then Temp := '0' + Temp; if Temp[Length(Temp)] = DecSep then Temp := Temp + '0'; Result := StrToFloat(string(Temp)); if IsNegative then Result := -Result; end else Result := 0.0; end; function StrToIntSafe(const S: AnsiString): Integer; begin Result := Trunc(StrToFloatSafe(S)); end; {$IFNDEF WINSCP} procedure StrNormIndex(const StrLen: SizeInt; var Index: SizeInt; var Count: SizeInt); overload; begin Index := Max(1, Min(Index, StrLen + 1)); Count := Max(0, Min(Count, StrLen + 1 - Index)); end; {$ENDIF WINSCP} function ArrayOf(List: TJclAnsiStrings): TDynStringArray; var I: SizeInt; begin if List <> nil then begin SetLength(Result, List.Count); for I := 0 to List.Count - 1 do Result[I] := string(List[I]); end else Result := nil; end; function AnsiCompareNatural(const S1, S2: AnsiString; CaseInsensitive: Boolean): SizeInt; var Cur1, Len1, Cur2, Len2: SizeInt; procedure NumberCompare; var IsReallyNumber: Boolean; FirstDiffBreaks: Boolean; Val1, Val2: SizeInt; begin Result := 0; IsReallyNumber := False; // count leading spaces in S1 while CharIsWhiteSpace(S1[Cur1]) do begin Dec(Result); Inc(Cur1); end; // count leading spaces in S2 (canceling them out against the ones in S1) while CharIsWhiteSpace(S2[Cur2]) do begin Inc(Result); Inc(Cur2); end; // if spaces match, or both strings are actually followed by a numeric character, continue the checks if (Result = 0) or (CharIsNumberChar(S1[Cur1])) and (CharIsNumberChar(S2[Cur2])) then begin // Check signed number if (S1[Cur1] = '-') and (S2[Cur2] <> '-') then Result := 1 else if (S2[Cur2] = '-') and (S1[Cur1] <> '-') then Result := -1 else Result := 0; if (S1[Cur1] = '-') or (S1[Cur1] = '+') then Inc(Cur1); if (S2[Cur2] = '-') or (S2[Cur2] = '+') then Inc(Cur2); FirstDiffBreaks := (S1[Cur1] = '0') or (S2[Cur2] = '0'); while CharIsDigit(S1[Cur1]) and CharIsDigit(S2[Cur2]) do begin IsReallyNumber := True; Val1 := StrToInt(string(S1[Cur1])); Val2 := StrToInt(string(S2[Cur2])); if (Result = 0) and (Val1 < Val2) then Result := -1 else if (Result = 0) and (Val1 > Val2) then Result := 1; if FirstDiffBreaks and (Result <> 0) then Break; Inc(Cur1); Inc(Cur2); end; if IsReallyNumber then begin if not FirstDiffBreaks then begin if CharIsDigit(S1[Cur1]) then Result := 1 else if CharIsDigit(S2[Cur2]) then Result := -1; end; end; end; end; begin Cur1 := 1; Len1 := Length(S1); Cur2 := 1; Len2 := Length(S2); Result := 0; while (Result = 0) do begin if (Cur1 = Len1) and (Cur2 = Len2) then Break else if (S1[Cur1] = '-') and CharIsNumberChar(S2[Cur2]) and (S2[Cur2] <> '-') then Result := -1 else if (S2[Cur2] = '-') and CharIsNumberChar(S1[Cur1]) and (S1[Cur1] <> '-') then Result := 1 else if CharIsNumberChar(S1[Cur1]) and CharIsNumberChar(S2[Cur2]) then NumberCompare else if (Cur1 = Len1) and (Cur2 < Len2) then Result := -1 else if (Cur1 < Len1) and (Cur2 = Len2) then Result := 1 else begin Result := StrCompare(S1,S2); if CaseInsensitive then Result := AnsiStrLICompA(PAnsiChar(@S1[Cur1]), PAnsiChar(@S2[Cur2]), 1) else Result := AnsiStrLCompA(PAnsiChar(@S1[Cur1]), PAnsiChar(@S2[Cur2]), 1); Inc(Cur1); Inc(Cur2); end; end; end; function AnsiCompareNaturalStr(const S1, S2: AnsiString): SizeInt; overload; begin Result := AnsiCompareNatural(S1, S2, False); end; function AnsiCompareNaturalText(const S1, S2: AnsiString): SizeInt; overload; begin Result := AnsiCompareNatural(S1, S2, True); end; function StrNewA(const Str: PAnsiChar): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrNew(Str); end; procedure StrDisposeA(Str: PAnsiChar); begin {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrDispose(Str); end; function StrLenA(S: PAnsiChar): Integer; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrLen(S); end; function StrEndA(const Str: PAnsiChar): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrEnd(Str); end; function StrPosA(const Str1, Str2: PAnsiChar): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrPos(Str1, Str2); end; function StrPasA(const Str: PAnsiChar): AnsiString; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrPas(Str); end; function StrCopyA(Dest: PAnsiChar; const Source: PAnsiChar): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrCopy(Dest, Source); end; function StrLCopyA(Dest: PAnsiChar; const Source: PAnsiChar; MaxLen: Cardinal): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrLCopy(Dest, Source, MaxLen); end; function StrPCopyA(Dest: PAnsiChar; const Source: AnsiString): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrPCopy(Dest, Source); end; function StrPLCopyA(Dest: PAnsiChar; const Source: AnsiString; MaxLen: Cardinal): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrPLCopy(Dest, Source, MaxLen); end; function StrECopyA(Dest: PAnsiChar; const Source: PAnsiChar): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrECopy(Dest, Source); end; function StrCatA(Dest: PAnsiChar; const Source: PAnsiChar): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrCat(Dest, Source); end; function StrLCatA(Dest: PAnsiChar; const Source: PAnsiChar; MaxLen: Cardinal): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrLCat(Dest, Source, MaxLen); end; function StrCompA(const Str1, Str2: PAnsiChar): Integer; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrComp(Str1, Str2); end; function StrLCompA(const Str1, Str2: PAnsiChar; MaxLen: Cardinal): Integer; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrLComp(Str1, Str2, MaxLen); end; function StrICompA(const Str1, Str2: PAnsiChar): Integer; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrIComp(Str1, Str2); end; function StrLICompA(const Str1, Str2: PAnsiChar; MaxLen: Cardinal): Integer; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrIComp(Str1, Str2); end; function StrFmtA(Buffer, Format: PAnsiChar; const Args: array of const): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}StrFmt(Buffer, Format, Args); end; function AnsiStrPosA(const Str1, Str2: PAnsiChar): PAnsiChar; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}AnsiStrPos(Str1, Str2); end; function AnsiStrLICompA(S1, S2: PAnsiChar; MaxLen: Cardinal): Integer; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}AnsiStrLIComp(S1, S2, MaxLen); end; function AnsiStrLCompA(S1, S2: PAnsiChar; MaxLen: Cardinal): Integer; begin Result := {$IFDEF DEPRECATED_SYSUTILS_ANSISTRINGS}System.AnsiStrings.{$ENDIF}AnsiStrLComp(S1, S2, MaxLen); end; initialization LoadCharTypes; // this table first LoadCaseMap; // or this function does not work {$IFDEF UNITVERSIONING} RegisterUnitVersion(HInstance, UnitVersioning); finalization UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} end.