{**************************************************************************************************} { } { 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 } { Marcel Bestebroer } { Martin Kimmings } { Martin Kubecka } { Massimo Maria Ghisalberti } { Matthias Thoma (mthoma) } { Michael Winter } { Nick Hodges } { Olivier Sannier (obones) } { Pelle F. S. Liljendal } { Petr Vones (pvones) } { Rik Barker (rikbarker) } { Robert Lee } { Robert Marquardt (marquardt) } { Robert Rossmair (rrossmair) } { Andreas Schmidt } { Sean Farrow (sfarrow) } { } {**************************************************************************************************} { } { Various character and string routines (searching, testing and transforming) } { } {**************************************************************************************************} { } { Last modified: $Date:: 2009-01-07 20:12:30 +0100 (mer., 07 janv. 2009) $ } { Revision: $Rev:: 2582 $ } { Author: $Author:: ahuser $ } { } {**************************************************************************************************} unit JclStrings; {$I jcl.inc} interface uses {$IFDEF UNITVERSIONING} JclUnitVersioning, {$ENDIF UNITVERSIONING} {$IFDEF MSWINDOWS} Windows, {$ENDIF MSWINDOWS} Classes, SysUtils, {$IFDEF CLR} System.Text, System.IO, {$ELSE} JclAnsiStrings, JclWideStrings, {$ENDIF CLR} JclBase, JclSysUtils; // Exceptions type EJclStringError = EJclError; // Character constants and sets const // Misc. often used character definitions NativeNull = Char(#0); NativeSoh = Char(#1); NativeStx = Char(#2); NativeEtx = Char(#3); NativeEot = Char(#4); NativeEnq = Char(#5); NativeAck = Char(#6); NativeBell = Char(#7); NativeBackspace = Char(#8); NativeTab = Char(#9); NativeLineFeed = JclBase.NativeLineFeed; NativeVerticalTab = Char(#11); NativeFormFeed = Char(#12); NativeCarriageReturn = JclBase.NativeCarriageReturn; NativeCrLf = JclBase.NativeCrLf; NativeSo = Char(#14); NativeSi = Char(#15); NativeDle = Char(#16); NativeDc1 = Char(#17); NativeDc2 = Char(#18); NativeDc3 = Char(#19); NativeDc4 = Char(#20); NativeNak = Char(#21); NativeSyn = Char(#22); NativeEtb = Char(#23); NativeCan = Char(#24); NativeEm = Char(#25); NativeEndOfFile = Char(#26); NativeEscape = Char(#27); NativeFs = Char(#28); NativeGs = Char(#29); NativeRs = Char(#30); NativeUs = Char(#31); NativeSpace = Char(' '); NativeComma = Char(','); NativeBackslash = Char('\'); NativeForwardSlash = Char('/'); NativeDoubleQuote = Char('"'); NativeSingleQuote = Char(''''); NativeLineBreak = JclBase.NativeLineBreak; 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} type TCharValidator = function(const C: Char): Boolean; function ArrayContainsChar(const Chars: array of Char; const C: Char): Boolean; // String Test Routines function StrIsAlpha(const S: string): Boolean; function StrIsAlphaNum(const S: string): Boolean; function StrIsAlphaNumUnderscore(const S: string): Boolean; function StrContainsChars(const S: string; const Chars: TCharValidator; CheckAll: Boolean): Boolean; overload; function StrContainsChars(const S: string; const Chars: array of Char; CheckAll: Boolean): Boolean; overload; function StrConsistsOfNumberChars(const S: string): Boolean; function StrIsDigit(const S: string): Boolean; function StrIsSubset(const S: string; const ValidChars: TCharValidator): Boolean; overload; function StrIsSubset(const S: string; const ValidChars: array of Char): Boolean; overload; function StrSame(const S1, S2: string): Boolean; // String Transformation Routines function StrCenter(const S: string; L: Integer; C: Char = ' '): string; function StrCharPosLower(const S: string; CharPos: Integer): string; function StrCharPosUpper(const S: string; CharPos: Integer): string; function StrDoubleQuote(const S: string): string; function StrEnsureNoPrefix(const Prefix, Text: string): string; function StrEnsureNoSuffix(const Suffix, Text: string): string; function StrEnsurePrefix(const Prefix, Text: string): string; function StrEnsureSuffix(const Suffix, Text: string): string; function StrEscapedToString(const S: string): string; function StrLower(const S: string): string; procedure StrLowerInPlace(var S: string); {$IFNDEF CLR} procedure StrLowerBuff(S: PChar); {$ENDIF ~CLR} procedure StrMove(var Dest: string; const Source: string; const ToIndex, FromIndex, Count: Integer); function StrPadLeft(const S: string; Len: Integer; C: Char = NativeSpace): string; function StrPadRight(const S: string; Len: Integer; C: Char = NativeSpace): string; function StrProper(const S: string): string; {$IFNDEF CLR} procedure StrProperBuff(S: PChar); {$ENDIF ~CLR} function StrQuote(const S: string; C: Char): string; function StrRemoveChars(const S: string; const Chars: TCharValidator): string; overload; function StrRemoveChars(const S: string; const Chars: array of Char): string; overload; function StrRemoveEndChars(const S: string; const Chars: TCharValidator): string; overload; function StrRemoveEndChars(const S: string; const Chars: array of Char): string; overload; function StrKeepChars(const S: string; const Chars: TCharValidator): string; overload; function StrKeepChars(const S: string; const Chars: array of Char): string; overload; procedure StrReplace(var S: string; const Search, Replace: string; Flags: TReplaceFlags = []); function StrReplaceChar(const S: string; const Source, Replace: Char): string; function StrReplaceChars(const S: string; const Chars: TCharValidator; Replace: Char): string; overload; function StrReplaceChars(const S: string; const Chars: array of Char; Replace: Char): string; overload; function StrReplaceButChars(const S: string; const Chars: TCharValidator; Replace: Char): string; overload; function StrReplaceButChars(const S: string; const Chars: array of Char; Replace: Char): string; overload; function StrRepeat(const S: string; Count: Integer): string; function StrRepeatLength(const S: string; L: Integer): string; function StrReverse(const S: string): string; procedure StrReverseInPlace(var S: string); function StrSingleQuote(const S: string): string; function StrSmartCase(const S: string; const Delimiters: TCharValidator): string; overload; function StrSmartCase(const S: string; const Delimiters: array of Char): string; overload; function StrStringToEscaped(const S: string): string; function StrStripNonNumberChars(const S: string): string; function StrToHex(const Source: string): string; function StrTrimCharLeft(const S: string; C: Char): string; function StrTrimCharsLeft(const S: string; const Chars: TCharValidator): string; overload; function StrTrimCharsLeft(const S: string; const Chars: array of Char): string; overload; function StrTrimCharRight(const S: string; C: Char): string; function StrTrimCharsRight(const S: string; const Chars: TCharValidator): string; overload; function StrTrimCharsRight(const S: string; const Chars: array of Char): string; overload; function StrTrimQuotes(const S: string): string; function StrUpper(const S: string): string; procedure StrUpperInPlace(var S: string); {$IFNDEF CLR} procedure StrUpperBuff(S: PChar); {$ENDIF ~CLR} {$IFNDEF CLR} {$IFNDEF SUPPORTS_UNICODE} {$IFDEF KEEP_DEPRECATED} // String Management procedure StrAddRef(var S: string); {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF} function StrAllocSize(const S: string): Longint; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF} procedure StrDecRef(var S: string); {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF} function StrLength(const S: string): Longint; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF} function StrRefCount(const S: string): Longint; {$IFDEF SUPPORTS_DEPRECATED} deprecated; {$ENDIF} {$ENDIF KEEP_DEPRECATED} {$ENDIF ~SUPPORTS_UNICODE} {$ENDIF ~CLR} // String Search and Replace Routines function StrCharCount(const S: string; C: Char): Integer; overload; function StrCharsCount(const S: string; const Chars: TCharValidator): Integer; overload; function StrCharsCount(const S: string; const Chars: array of Char): Integer; overload; function StrStrCount(const S, SubS: string): Integer; function StrCompare(const S1, S2: string): Integer; function StrCompareRange(const S1, S2: string; const Index, Count: Integer): Integer; {$IFNDEF CLR} procedure StrFillChar(var S; Count: Integer; C: Char); {$ENDIF ~CLR} function StrRepeatChar(C: Char; Count: Integer): string; function StrFind(const Substr, S: string; const Index: Integer = 1): Integer; function StrHasPrefix(const S: string; const Prefixes: array of string): Boolean; function StrIndex(const S: string; const List: array of string): Integer; function StrILastPos(const SubStr, S: string): Integer; function StrIPos(const SubStr, S: string): Integer; function StrIsOneOf(const S: string; const List: array of string): Boolean; function StrLastPos(const SubStr, S: string): Integer; function StrMatch(const Substr, S: string; const Index: Integer = 1): Integer; function StrMatches(const Substr, S: string; const Index: Integer = 1): Boolean; function StrNIPos(const S, SubStr: string; N: Integer): Integer; function StrNPos(const S, SubStr: string; N: Integer): Integer; function StrPrefixIndex(const S: string; const Prefixes: array of string): Integer; function StrSearch(const Substr, S: string; const Index: Integer = 1): Integer; // String Extraction function StrAfter(const SubStr, S: string): string; function StrBefore(const SubStr, S: string): string; function StrBetween(const S: string; const Start, Stop: Char): string; function StrChopRight(const S: string; N: Integer): string; function StrLeft(const S: string; Count: Integer): string; function StrMid(const S: string; Start, Count: Integer): string; function StrRestOf(const S: string; N: Integer): string; function StrRight(const S: string; Count: Integer): string; // Character Test Routines function CharEqualNoCase(const C1, C2: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsAlpha(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsAlphaNum(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsBlank(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsControl(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsDelete(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsDigit(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsFracDigit(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsHexDigit(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsLower(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsNumberChar(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsNumber(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsPrintable(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsPunctuation(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsReturn(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsSpace(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsUpper(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsValidIdentifierLetter(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsWhiteSpace(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function CharIsWildcard(const C: Char): Boolean; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} {$IFNDEF CLR} function CharType(const C: Char): Word; {$ENDIF ~CLR} // Character Transformation Routines function CharHex(const C: Char): Byte; function CharLower(const C: Char): Char; {$IFDEF CLR} inline; {$ENDIF} function CharUpper(const C: Char): Char; {$IFDEF CLR} inline; {$ENDIF} function CharToggleCase(const C: Char): Char; // Character Search and Replace function CharPos(const S: string; const C: Char; const Index: Integer = 1): Integer; function CharLastPos(const S: string; const C: Char; const Index: Integer = 1): Integer; function CharIPos(const S: string; C: Char; const Index: Integer = 1): Integer; function CharReplace(var S: string; const Search, Replace: Char): Integer; {$IFNDEF CLR} // PCharVector type PCharVector = ^PChar; function StringsToPCharVector(var Dest: PCharVector; const Source: TStrings): PCharVector; function PCharVectorCount(Source: PCharVector): Integer; procedure PCharVectorToStrings(const Dest: TStrings; Source: PCharVector); procedure FreePCharVector(var Dest: PCharVector); // MultiSz Routines type PMultiSz = PChar; PAnsiMultiSz = JclAnsiStrings.PMultiSz; PWideMultiSz = JclWideStrings.PMultiSz; TAnsiStrings = JclAnsiStrings.TAnsiStrings; TWideStrings = JclWideStrings.TWideStrings; TAnsiStringList = JclAnsiStrings.TAnsiStringList; TWideStringList = JclWideStrings.TWideStringList; function StringsToMultiSz(var Dest: PMultiSz; const Source: TStrings): PMultiSz; procedure MultiSzToStrings(const Dest: TStrings; const Source: PMultiSz); function MultiSzLength(const Source: PMultiSz): Integer; procedure AllocateMultiSz(var Dest: PMultiSz; Len: Integer); procedure FreeMultiSz(var Dest: PMultiSz); function MultiSzDup(const Source: PMultiSz): PMultiSz; function AnsiStringsToAnsiMultiSz(var Dest: PAnsiMultiSz; const Source: TAnsiStrings): PAnsiMultiSz; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} procedure AnsiMultiSzToAnsiStrings(const Dest: TAnsiStrings; const Source: PAnsiMultiSz); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function AnsiMultiSzLength(const Source: PAnsiMultiSz): Integer; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} procedure AllocateAnsiMultiSz(var Dest: PAnsiMultiSz; Len: Integer); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} procedure FreeAnsiMultiSz(var Dest: PAnsiMultiSz); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function AnsiMultiSzDup(const Source: PAnsiMultiSz): PAnsiMultiSz; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function WideStringsToWideMultiSz(var Dest: PWideMultiSz; const Source: TWideStrings): PWideMultiSz; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} procedure WideMultiSzToWideStrings(const Dest: TWideStrings; const Source: PWideMultiSz); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function WideMultiSzLength(const Source: PWideMultiSz): Integer; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} procedure AllocateWideMultiSz(var Dest: PWideMultiSz; Len: Integer); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} procedure FreeWideMultiSz(var Dest: PWideMultiSz); {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} function WideMultiSzDup(const Source: PWideMultiSz): PWideMultiSz; {$IFDEF SUPPORTS_INLINE} inline; {$ENDIF} {$ENDIF ~CLR} // TStrings Manipulation procedure StrIToStrings(S, Sep: string; const List: TStrings; const AllowEmptyString: Boolean = True); procedure StrToStrings(S, Sep: string; const List: TStrings; const AllowEmptyString: Boolean = True); function StringsToStr(const List: TStrings; const Sep: string; const AllowEmptyString: Boolean = True): string; procedure TrimStrings(const List: TStrings; DeleteIfEmpty: Boolean = True); procedure TrimStringsRight(const List: TStrings; DeleteIfEmpty: Boolean = True); procedure TrimStringsLeft(const List: TStrings; DeleteIfEmpty: Boolean = True); function AddStringToStrings(const S: string; Strings: TStrings; const Unique: Boolean): Boolean; // Miscellaneous {$IFDEF KEEP_DEPRECATED} function BooleanToStr(B: Boolean): string; {$ENDIF KEEP_DEPRECATED} // AnsiString here because it is binary data function FileToString(const FileName: string): {$IFDEF COMPILER12_UP}RawByteString{$ELSE}AnsiString{$ENDIF}; procedure StringToFile(const FileName: string; const Contents: {$IFDEF COMPILER12_UP}RawByteString{$ELSE}AnsiString{$ENDIF}; Append: Boolean = False); function StrToken(var S: string; Separator: Char): string; procedure StrTokens(const S: string; const List: TStrings); procedure StrTokenToStrings(S: string; Separator: Char; const List: TStrings); {$IFDEF CLR} function StrWord(const S: string; var Index: Integer; out Word: string): Boolean; {$ELSE} function StrWord(var S: PChar; out Word: string): Boolean; {$ENDIF CLR} function StrToFloatSafe(const S: string): Float; function StrToIntSafe(const S: string): Integer; procedure StrNormIndex(const StrLen: Integer; var Index: Integer; var Count: Integer); overload; {$IFDEF CLR} function ArrayOf(List: TStrings): TDynStringArray; overload; {$ENDIF CLR} {$IFDEF COMPILER5} // missing Delphi 5 functions function TryStrToInt(const S: string; out Value: Integer): Boolean; function TryStrToInt64(const S: string; out Value: Int64): Boolean; function TryStrToFloat(const S: string; out Value: Extended): Boolean; overload; function TryStrToFloat(const S: string; out Value: Double): Boolean; overload; function TryStrToFloat(const S: string; out Value: Single): Boolean; overload; function TryStrToCurr(const S: string; out Value: Currency): Boolean; {$ENDIF COMPILER5} {$IFDEF CLR} type TJclStringBuilder = System.Text.StringBuilder; TStringBuilder = TJclStringBuilder; function DotNetFormat(const Fmt: string; const Args: array of System.Object): string; overload; function DotNetFormat(const Fmt: string; const Arg0: System.Object): string; overload; function DotNetFormat(const Fmt: string; const Arg0, Arg1: System.Object): string; overload; function DotNetFormat(const Fmt: string; const Arg0, Arg1, Arg2: System.Object): string; overload; {$ELSE ~CLR} type FormatException = class(EJclError); ArgumentException = class(EJclError); ArgumentNullException = class(EJclError); ArgumentOutOfRangeException = class(EJclError); IToString = interface ['{C4ABABB4-1029-46E7-B5FA-99800F130C05}'] function ToString: string; end; TCharDynArray = array of Char; // The TStringBuilder class is a Delphi implementation of the .NET // System.Text.StringBuilder. // It is zero based and the method that allow an TObject (Append, Insert, // AppendFormat) are limited to IToString implementors. // This class is not threadsafe. Any instance of TStringBuilder should not // be used in different threads at the same time. TJclStringBuilder = class(TInterfacedObject, IToString) private FChars: TCharDynArray; FLength: Integer; FMaxCapacity: Integer; function GetCapacity: Integer; procedure SetCapacity(const Value: Integer); function GetChars(Index: Integer): Char; procedure SetChars(Index: Integer; const Value: Char); procedure Set_Length(const Value: Integer); protected function AppendPChar(Value: PChar; Count: Integer; RepeatCount: Integer = 1): TJclStringBuilder; function InsertPChar(Index: Integer; Value: PChar; Count: Integer; RepeatCount: Integer = 1): TJclStringBuilder; public constructor Create(const Value: string; Capacity: Integer = 16); overload; constructor Create(Capacity: Integer = 16; MaxCapacity: Integer = MaxInt); overload; constructor Create(const Value: string; StartIndex, Length, Capacity: Integer); overload; function Append(const Value: string): TJclStringBuilder; overload; function Append(const Value: string; StartIndex, Length: Integer): TJclStringBuilder; overload; function Append(Value: Boolean): TJclStringBuilder; overload; function Append(Value: Char; RepeatCount: Integer = 1): TJclStringBuilder; overload; function Append(const Value: array of Char): TJclStringBuilder; overload; function Append(const Value: array of Char; StartIndex, Length: Integer): TJclStringBuilder; overload; function Append(Value: Cardinal): TJclStringBuilder; overload; function Append(Value: Integer): TJclStringBuilder; overload; function Append(Value: Double): TJclStringBuilder; overload; function Append(Value: Int64): TJclStringBuilder; overload; function Append(Obj: TObject): TJclStringBuilder; overload; function AppendFormat(const Fmt: string; const Args: array of const): TJclStringBuilder; overload; function AppendFormat(const Fmt: string; Arg0: Variant): TJclStringBuilder; overload; function AppendFormat(const Fmt: string; Arg0, Arg1: Variant): TJclStringBuilder; overload; function AppendFormat(const Fmt: string; Arg0, Arg1, Arg2: Variant): TJclStringBuilder; overload; function Insert(Index: Integer; const Value: string; Count: Integer = 1): TJclStringBuilder; overload; function Insert(Index: Integer; Value: Boolean): TJclStringBuilder; overload; function Insert(Index: Integer; const Value: array of Char): TJclStringBuilder; overload; function Insert(Index: Integer; const Value: array of Char; StartIndex, Length: Integer): TJclStringBuilder; overload; function Insert(Index: Integer; Value: Cardinal): TJclStringBuilder; overload; function Insert(Index: Integer; Value: Integer): TJclStringBuilder; overload; function Insert(Index: Integer; Value: Double): TJclStringBuilder; overload; function Insert(Index: Integer; Value: Int64): TJclStringBuilder; overload; function Insert(Index: Integer; Obj: TObject): TJclStringBuilder; overload; function Replace(OldChar, NewChar: Char; StartIndex: Integer = 0; Count: Integer = -1): TJclStringBuilder; overload; function Replace(OldValue, NewValue: string; StartIndex: Integer = 0; Count: Integer = -1): TJclStringBuilder; overload; function Remove(StartIndex, Length: Integer): TJclStringBuilder; function EnsureCapacity(Capacity: Integer): Integer; function ToString: string; {$IFDEF RTL200_UP} override; {$ENDIF RTL200_UP} property __Chars__[Index: Integer]: Char read GetChars write SetChars; default; property Chars: TCharDynArray read FChars; property Length: Integer read FLength write Set_Length; property Capacity: Integer read GetCapacity write SetCapacity; property MaxCapacity: Integer read FMaxCapacity; end; TStringBuilder = TJclStringBuilder; // DotNetFormat() uses the .NET format style: "{argX}" function DotNetFormat(const Fmt: string; const Args: array of const): string; overload; function DotNetFormat(const Fmt: string; const Arg0: Variant): string; overload; function DotNetFormat(const Fmt: string; const Arg0, Arg1: Variant): string; overload; function DotNetFormat(const Fmt: string; const Arg0, Arg1, Arg2: Variant): string; overload; // TJclTabSet type TJclTabSet = class {$IFNDEF CLR}(TInterfacedObject, IToString){$ENDIF} private FStops: TDynIntegerArray; FRealWidth: Integer; FWidth: Integer; FZeroBased: Boolean; procedure CalcRealWidth; function GetCount: Integer; function GetStops(Index: Integer): Integer; function GetTabWidth: Integer; function GetZeroBased: Boolean; procedure SetStops(Index, Value: Integer); procedure SetTabWidth(Value: Integer); procedure SetZeroBased(Value: Boolean); protected function FindStop(Column: Integer): Integer; function InternalTabStops: TDynIntegerArray; function InternalTabWidth: Integer; procedure RemoveAt(Index: Integer); public constructor Create; overload; constructor Create(TabWidth: Integer); overload; constructor Create(const Tabstops: array of Integer; ZeroBased: Boolean); overload; constructor Create(const Tabstops: array of Integer; ZeroBased: Boolean; TabWidth: Integer); overload; // Tab stops manipulation function Add(Column: Integer): Integer; function Delete(Column: Integer): Integer; // Usage function Expand(const S: string): string; overload; function Expand(const S: string; Column: Integer): string; overload; procedure OptimalFillInfo(StartColumn, TargetColumn: Integer; out TabsNeeded, SpacesNeeded: Integer); function Optimize(const S: string): string; overload; function Optimize(const S: string; Column: Integer): string; overload; function StartColumn: Integer; function TabFrom(Column: Integer): Integer; function UpdatePosition(const S: string): Integer; overload; function UpdatePosition(const S: string; Column: Integer): Integer; overload; function UpdatePosition(const S: string; var Column, Line: Integer): Integer; overload; // Conversions function ToString: string; overload; {$IFDEF RTL200_UP} override; {$ENDIF RTL200_UP} function ToString(FormattingOptions: Integer): string; {$IFDEF RTL200_UP} reintroduce; {$ENDIF RTL200_UP} overload; class function FromString(const S: string): TJclTabSet; {$IFDEF SUPPORTS_STATIC} static; {$ENDIF SUPPORTS_STATIC} // Properties property ActualTabWidth: Integer read InternalTabWidth; property Count: Integer read GetCount; property TabStops[Index: Integer]: Integer read GetStops write SetStops; default; property TabWidth: Integer read GetTabWidth write SetTabWidth; property ZeroBased: Boolean read GetZeroBased write SetZeroBased; end; // Formatting constants const TabSetFormatting_SurroundStopsWithBrackets = 1; TabSetFormatting_EmptyBracketsIfNoStops = 2; TabSetFormatting_NoTabStops = 4; TabSetFormatting_NoTabWidth = 8; TabSetFormatting_AutoTabWidth = 16; // common combinations TabSetFormatting_Default = 0; TabSetFormatting_AlwaysUseBrackets = TabSetFormatting_SurroundStopsWithBrackets or TabSetFormatting_EmptyBracketsIfNoStops; TabSetFormatting_Full = TabSetFormatting_AlwaysUseBrackets or TabSetFormatting_AutoTabWidth; // aliases TabSetFormatting_StopsOnly = TabSetFormatting_NoTabWidth; TabSetFormatting_TabWidthOnly = TabSetFormatting_NoTabStops; TabSetFormatting_StopsWithoutBracketsAndTabWidth = TabSetFormatting_Default; // Tab expansion routines function StrExpandTabs(S: string): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload; function StrExpandTabs(S: string; TabWidth: Integer): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload; function StrExpandTabs(S: string; TabSet: TJclTabSet): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload; // Tab optimization routines function StrOptimizeTabs(S: string): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload; function StrOptimizeTabs(S: string; TabWidth: Integer): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload; function StrOptimizeTabs(S: string; TabSet: TJclTabSet): string; {$IFDEF SUPPORTS_INLINE}inline; {$ENDIF} overload; // move to JclBase? type NullReferenceException = class(EJclError) public constructor Create; overload; end; {$ENDIF ~CLR} procedure StrResetLength(var S: WideString); overload; procedure StrResetLength(var S: AnsiString); overload; procedure StrResetLength(S: TJclStringBuilder); overload; {$IFDEF SUPPORTS_UNICODE_STRING} procedure StrResetLength(var S: UnicodeString); overload; {$ENDIF SUPPORTS_UNICODE_STRING} // natural comparison functions function CompareNaturalStr(const S1, S2: string): Integer; function CompareNaturalText(const S1, S2: string): Integer; // internal structures published to make function inlining working {$IFNDEF CLR} const MaxStrCharCount = Ord(High(Char)) + 1; // # of chars in one set StrLoOffset = MaxStrCharCount * 0; // offset to lower case chars StrUpOffset = MaxStrCharCount * 1; // offset to upper case chars StrReOffset = MaxStrCharCount * 2; // offset to reverse case chars StrCaseMapSize = MaxStrCharCount * 3; // # of chars is a table var StrCaseMap: array [0..StrCaseMapSize - 1] of Char; // case mappings StrCaseMapReady: Boolean = False; // true if case map exists StrCharTypes: array [Char] of Word; {$ENDIF ~CLR} {$IFDEF UNITVERSIONING} const UnitVersioning: TUnitVersionInfo = ( RCSfile: '$URL: https://jcl.svn.sourceforge.net/svnroot/jcl/tags/JCL-1.104-Build3248/jcl/source/common/JclStrings.pas $'; Revision: '$Revision: 2582 $'; Date: '$Date: 2009-01-07 20:12:30 +0100 (mer., 07 janv. 2009) $'; LogPath: 'JCL\source\common' ); {$ENDIF UNITVERSIONING} implementation uses {$IFDEF CLR} System.Globalization, {$ENDIF CLR} {$IFDEF HAS_UNIT_LIBC} Libc, {$ENDIF HAS_UNIT_LIBC} {$IFDEF SUPPORTS_UNICODE} StrUtils, {$ENDIF SUPPORTS_UNICODE} JclLogic, JclResources, JclStreams; //=== Internal =============================================================== {$IFNDEF CLR} {$IFNDEF SUPPORTS_UNICODE} type TStrRec = packed record AllocSize: Longint; RefCount: Longint; Length: Longint; end; const StrRecSize = SizeOf(TStrRec); // size of the string header rec StrAllocOffset = 12; // offset to AllocSize in StrRec StrRefCountOffset = 8; // offset to RefCount in StrRec StrLengthOffset = 4; // offset to Length in StrRec {$ENDIF ~SUPPORTS_UNICODE} procedure LoadCharTypes; var CurrChar: Char; CurrType: Word; {$IFDEF CLR} Category: System.Globalization.UnicodeCategory; {$ENDIF CLR} begin for CurrChar := Low(CurrChar) to High(CurrChar) do begin {$IFDEF MSWINDOWS} GetStringTypeEx(LOCALE_USER_DEFAULT, CT_CTYPE1, @CurrChar, 1, 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} StrCharTypes[CurrChar] := CurrType; {$IFNDEF CHAR_TYPES_INITIALIZED} Implement case map initialization here {$ENDIF ~CHAR_TYPES_INITIALIZED} end; end; procedure LoadCaseMap; var CurrChar, UpCaseChar, LoCaseChar, ReCaseChar: Char; begin if not StrCaseMapReady then begin for CurrChar := Low(Char) to High(Char) do begin {$IFDEF MSWINDOWS} LoCaseChar := CurrChar; UpCaseChar := CurrChar; Windows.CharLowerBuff(@LoCaseChar, 1); Windows.CharUpperBuff(@UpCaseChar, 1); {$DEFINE CASE_MAP_INITIALIZED} {$ENDIF MSWINDOWS} {$IFDEF LINUX} LoCaseChar := Char(tolower(Byte(CurrChar))); UpCaseChar := Char(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; StrCaseMap[Ord(CurrChar) + StrLoOffset] := LoCaseChar; StrCaseMap[Ord(CurrChar) + StrUpOffset] := UpCaseChar; StrCaseMap[Ord(CurrChar) + StrReOffset] := ReCaseChar; end; StrCaseMapReady := True; end; end; {$ENDIF ~CLR} // Uppercases or Lowercases a give string depending on the // passed offset. (UpOffset or LoOffset) {$IFDEF CLR} const StrLoOffset = 0; StrUpOffset = 1; procedure StrCase(var Str: string; const Offset: Integer); begin if Offset = StrUpOffset then Str := Str.ToUpper else Str := Str.ToLower; end; {$ELSE} procedure StrCase(var Str: string; const Offset: Integer); {$IFDEF SUPPORTS_UNICODE} var Len: Integer; RetValue: string; begin case Offset of StrUpOffset: begin Len := LCMapString(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, PChar(Str), Length(Str), nil, 0); SetLength(RetValue, Len); if Len > 0 then LCMapString(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, PChar(Str), Length(Str), PChar(RetValue), Len); end; StrLoOffset: begin Len := LCMapString(LOCALE_USER_DEFAULT, LCMAP_LOWERCASE, PChar(Str), Length(Str), nil, 0); SetLength(RetValue, Len); if Len > 0 then LCMapString(LOCALE_USER_DEFAULT, LCMAP_LOWERCASE, PChar(Str), Length(Str), PChar(RetValue), Len); end else Assert(False, 'StrReOffset not supported'); Exit; end; Str := RetValue; end; {$ELSE} asm // make sure that the string is not null TEST EAX, EAX JZ @@StrIsNull // create unique string if this one is ref-counted PUSH EDX CALL UniqueString POP EDX // make sure that the new string is not null TEST EAX, EAX JZ @@StrIsNull // get the length, and prepare the counter MOV ECX, [EAX - StrRecSize].TStrRec.Length DEC ECX JS @@StrIsNull // ebx will hold the case map, esi pointer to Str PUSH EBX PUSH ESI PUSH EDI // load case map and prepare variables } {$IFDEF PIC} LEA EBX, [EBX][STRCASEMAP + EDX] {$ELSE} LEA EBX, [StrCaseMap + EDX] {$ENDIF PIC} MOV ESI, EAX XOR EDX, EDX XOR EAX, EAX @@NextChar: // get current char from the string MOV DL, [ESI] // get corresponding char from the case map MOV AL, [EBX + EDX] // store it back in the string MOV [ESI], AL // update the loop counter and check the end of stirng DEC ECX JL @@Done // do the same thing with next 3 chars MOV DL, [ESI + 1] MOV AL, [EBX + EDX] MOV [ESI + 1], AL DEC ECX JL @@Done MOV DL, [ESI + 2] MOV AL, [EBX+EDX] MOV [ESI + 2], AL DEC ECX JL @@Done MOV DL, [ESI + 3] MOV AL, [EBX + EDX] MOV [ESI + 3], AL // point string to next 4 chars ADD ESI, 4 // update the loop counter and check the end of stirng DEC ECX JGE @@NextChar @@Done: POP EDI POP ESI POP EBX @@StrIsNull: end; {$ENDIF SUPPORTS_UNICODE} {$ENDIF CLR} {$IFNDEF CLR} // Internal utility function // Uppercases or Lowercases a give null terminated string depending on the // passed offset. (UpOffset or LoOffset) procedure StrCaseBuff(S: PChar; const Offset: Integer); {$IFDEF SUPPORTS_UNICODE} var Len, SLen: Integer; RetValue: string; begin if S <> nil then begin SLen := StrLen(S); case Offset of StrUpOffset: begin Len := LCMapString(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, S, SLen, nil, 0); SetLength(RetValue, Len); if Len > 0 then LCMapString(LOCALE_USER_DEFAULT, LCMAP_UPPERCASE, S, SLen, PChar(RetValue), Len); end; StrLoOffset: begin Len := LCMapString(LOCALE_USER_DEFAULT, LCMAP_LOWERCASE, S, SLen, nil, 0); SetLength(RetValue, Len); if Len > 0 then LCMapString(LOCALE_USER_DEFAULT, LCMAP_LOWERCASE, S, SLen, PChar(RetValue), Len); end else Assert(False, 'StrReOffset not supported'); Exit; end; Move(PChar(RetValue)^, S^, Len * SizeOf(Char)); end; end; {$ELSE} asm // make sure the string is not null TEST EAX, EAX JZ @@StrIsNull // ebx will hold the case map, esi pointer to Str PUSH EBX PUSH ESI // load case map and prepare variables {$IFDEF PIC} LEA EBX, [EBX][STRCASEMAP + EDX] {$ELSE} LEA EBX, [StrCaseMap + EDX] {$ENDIF PIC} MOV ESI, EAX XOR EDX, EDX XOR EAX, EAX @@NextChar: // get current char from the string MOV DL, [ESI] // check for null char TEST DL, DL JZ @@Done // get corresponding char from the case map MOV AL, [EBX + EDX] // store it back in the string MOV [ESI], AL // do the same thing with next 3 chars MOV DL, [ESI + 1] TEST DL, DL JZ @@Done MOV AL, [EBX+EDX] MOV [ESI + 1], AL MOV DL, [ESI + 2] TEST DL, DL JZ @@Done MOV AL, [EBX+EDX] MOV [ESI + 2], AL MOV DL, [ESI + 3] TEST DL, DL JZ @@Done MOV AL, [EBX+EDX] MOV [ESI + 3], AL // point string to next 4 chars ADD ESI, 4 JMP @@NextChar @@Done: POP ESI POP EBX @@StrIsNull: end; {$ENDIF SUPPORTS_UNICODE} function StrEndW(Str: PWideChar): PWideChar; begin Result := Str; while Result^ <> #0 do Inc(Result); end; {$ENDIF ~CLR} function ArrayContainsChar(const Chars: array of Char; const C: Char): Boolean; { optimized version for sorted arrays var I, L, H: Integer; begin L := Low(Chars); H := High(Chars); while L <= H do begin I := (L + H) div 2; if C = Chars[I] then begin Result := True; Exit; end else if C < Chars[I] then H := I - 1 else // C > Chars[I] L := I + 1; end; Result := False; end;} var I: Integer; begin Result := True; for I := Low(Chars) to High(Chars) do if Chars[I] = C then Exit; Result := False; end; // String Test Routines function StrIsAlpha(const S: string): Boolean; var I: Integer; 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: string): Boolean; var I: Integer; 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: string): Boolean; var I: Integer; 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: string; const Chars: TCharValidator; CheckAll: Boolean): Boolean; var I: Integer; begin Result := False; if CheckAll then begin for I := 1 to Length(S) do begin Result := Chars(S[I]); if not Result then Break; end; end else begin for I := 1 to Length(S) do begin Result := Chars(S[I]); if Result then Break; end; end; end; function StrContainsChars(const S: string; const Chars: array of Char; CheckAll: Boolean): Boolean; var I: Integer; begin Result := False; if CheckAll then begin for I := 1 to Length(S) do begin Result := ArrayContainsChar(Chars, S[I]); if not Result then Break; end; end else begin for I := 1 to Length(S) do begin Result := ArrayContainsChar(Chars, S[I]); if Result then Break; end; end; end; function StrIsAlphaNumUnderscore(const S: string): Boolean; var I: Integer; C: Char; 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 := Length(S) > 0; end; function StrIsDigit(const S: string): Boolean; var I: Integer; 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: string; const ValidChars: TCharValidator): Boolean; var I: Integer; begin for I := 1 to Length(S) do begin Result := ValidChars(S[I]); if not Result then Exit; end; Result := Length(S) > 0; end; function StrIsSubset(const S: string; const ValidChars: array of Char): Boolean; var I: Integer; begin for I := 1 to Length(S) do begin Result := ArrayContainsChar(ValidChars, S[I]); if not Result then Exit; end; Result := Length(S) > 0; end; function StrSame(const S1, S2: string): Boolean; begin Result := StrCompare(S1, S2) = 0; end; //=== String Transformation Routines ========================================= function StrCenter(const S: string; L: Integer; C: Char = ' '): string; 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: string; CharPos: Integer): string; begin Result := S; if (CharPos > 0) and (CharPos <= Length(S)) then Result[CharPos] := CharLower(Result[CharPos]); end; function StrCharPosUpper(const S: string; CharPos: Integer): string; begin Result := S; if (CharPos > 0) and (CharPos <= Length(S)) then Result[CharPos] := CharUpper(Result[CharPos]); end; function StrDoubleQuote(const S: string): string; begin Result := NativeDoubleQuote + S + NativeDoubleQuote; end; function StrEnsureNoPrefix(const Prefix, Text: string): string; var PrefixLen: Integer; 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: string): string; var SuffixLen: Integer; StrLength: Integer; 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: string): string; var PrefixLen: Integer; begin PrefixLen := Length(Prefix); if Copy(Text, 1, PrefixLen) = Prefix then Result := Text else Result := Prefix + Text; end; function StrEnsureSuffix(const Suffix, Text: string): string; var SuffixLen: Integer; 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: string): string; var I, Len: Integer; procedure HandleHexEscapeSeq; const HexDigits = string('0123456789abcdefABCDEF'); var Val, N: Integer; begin N := Pos(S[I + 1], HexDigits) - 1; if N < 0 then // '\x' without hex digit following is not escape sequence Result := Result + '\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(Char)) then {$IFDEF CLR} raise EJclStringError.Create(RsNumericConstantTooLarge); {$ELSE} raise EJclStringError.CreateRes(@RsNumericConstantTooLarge); {$ENDIF CLR} Result := Result + Char(Val); end; end; procedure HandleOctEscapeSeq; const OctDigits = string('01234567'); var Val, N: Integer; begin // 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(Char)) then {$IFDEF CLR} raise EJclStringError.Create(RsNumericConstantTooLarge); {$ELSE} raise EJclStringError.CreateRes(@RsNumericConstantTooLarge); {$ENDIF CLR} Result := Result + Char(Val); end; 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 + NativeBell; 'b': Result := Result + NativeBackspace; 'f': Result := Result + NativeFormFeed; 'n': Result := Result + NativeLineFeed; 'r': Result := Result + NativeCarriageReturn; 't': Result := Result + NativeTab; 'v': Result := Result + NativeVerticalTab; '\': Result := Result + '\'; '"': Result := Result + '"'; '''': Result := Result + ''''; // Optionally escaped '?': Result := Result + '?'; // Optionally escaped 'x': if I < Len then // Start of hex escape sequence HandleHexEscapeSeq else // '\x' at end of string is not escape sequence Result := Result + '\x'; '0'..'7': // start of octal escape sequence HandleOctEscapeSeq; else // no escape sequence Result := Result + '\' + S[I]; end; end; Inc(I); end; end; function StrLower(const S: string): string; begin Result := S; StrLowerInPlace(Result); end; procedure StrLowerInPlace(var S: string); {$IFDEF PIC} begin StrCase(S, StrLoOffset); end; {$ELSE} asm // StrCase(S, StrLoOffset) XOR EDX, EDX // MOV EDX, StrLoOffset JMP StrCase end; {$ENDIF PIC} {$IFNDEF CLR} procedure StrLowerBuff(S: PChar); {$IFDEF PIC} begin StrCaseBuff(S, StrLoOffset); end; {$ELSE} asm // StrCaseBuff(S, LoOffset) XOR EDX, EDX // MOV EDX, LoOffset JMP StrCaseBuff end; {$ENDIF PIC} {$ENDIF ~CLR} {$IFDEF CLR} procedure MoveString(const Source: string; SrcIndex: Integer; var Dest: string; DstIndex, Count: Integer); begin Dec(SrcIndex); Dec(DstIndex); Dest := Dest.Remove(DstIndex, Count).Insert(DstIndex, Source.Substring(SrcIndex, Count)); end; {$ENDIF CLR} procedure StrMove(var Dest: string; const Source: string; const ToIndex, FromIndex, Count: Integer); 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 {$IFDEF CLR} MoveString(Source, FromIndex, Dest, ToIndex, Count); {$ELSE} Move(Source[FromIndex], Dest[ToIndex], Count * SizeOf(Char)); {$ENDIF CLR} end; function StrPadLeft(const S: string; Len: Integer; C: Char): string; var L: Integer; begin L := Length(S); if L < Len then Result := StringOfChar(C, Len - L) + S else Result := S; end; function StrPadRight(const S: string; Len: Integer; C: Char): string; var L: Integer; begin L := Length(S); if L < Len then Result := S + StringOfChar(C, Len - L) else Result := S; end; function StrProper(const S: string): string; begin {$IFDEF CLR} Result := S.ToLower; {$ELSE} Result := StrLower(S); {$ENDIF CLR} if Result <> '' then Result[1] := UpCase(Result[1]); end; {$IFNDEF CLR} procedure StrProperBuff(S: PChar); begin if (S <> nil) and (S^ <> #0) then begin StrLowerBuff(S); S^ := CharUpper(S^); end; end; {$ENDIF ~CLR} function StrQuote(const S: string; C: Char): string; var L: Integer; 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: string; const Chars: TCharValidator): string; {$IFDEF CLR} var I: Integer; sb: StringBuilder; begin sb := StringBuilder.Create(Length(S)); for I := 0 to S.Length - 1 do if not Chars(S[I]) then sb.Append(S[I]); Result := sb.ToString(); end; {$ELSE} var Source, Dest: PChar; Len, Index: Integer; begin Len := Length(S); SetLength(Result, Len); UniqueString(Result); Source := PChar(S); Dest := PChar(Result); for Index := 0 to Len - 1 do begin if not Chars(Source^) then begin Dest^ := Source^; Inc(Dest); end; Inc(Source); end; SetLength(Result, Dest - PChar(Result)); end; {$ENDIF CLR} function StrRemoveChars(const S: string; const Chars: array of Char): string; {$IFDEF CLR} var I: Integer; sb: StringBuilder; begin sb := StringBuilder.Create(Length(S)); for I := 0 to S.Length - 1 do if not ArrayContainsChar(Chars,S[I]) then sb.Append(S[I]); Result := sb.ToString(); end; {$ELSE} var Source, Dest: PChar; Len, Index: Integer; begin Len := Length(S); SetLength(Result, Len); UniqueString(Result); Source := PChar(S); Dest := PChar(Result); for Index := 0 to Len - 1 do begin if not ArrayContainsChar(Chars, Source^) then begin Dest^ := Source^; Inc(Dest); end; Inc(Source); end; SetLength(Result, Dest - PChar(Result)); end; {$ENDIF CLR} function StrRemoveEndChars(const S: string; const Chars: TCharValidator): string; {$IFDEF CLR} var Len: Integer; I: Integer; sb: StringBuilder; begin Len := Length(S); while (Len > 0) and Chars(s[Len]) do Dec(Len); sb := StringBuilder.Create(Len); for I := 0 to Len do sb.Append(S[I]); Result := sb.ToString(); end; {$ELSE} var Len : Integer; begin Len := Length(S); while (Len > 0) and Chars(s[Len]) do Dec(Len); Result := Copy (s, 1, Len); end; {$ENDIF CLR} function StrRemoveEndChars(const S: string; const Chars: array of Char): string; {$IFDEF CLR} var Len: Integer; I: Integer; sb: StringBuilder; begin Len := Length(S); while (Len > 0) and ArrayContainsChar(Chars, s[Len]) do Dec(Len); sb := StringBuilder.Create(Len); for I := 0 to Len do sb.Append(S[I]); Result := sb.ToString(); end; {$ELSE} var Len : Integer; begin Len := Length(S); while (Len > 0) and ArrayContainsChar(Chars, s[Len]) do Dec(Len); Result := Copy (s, 1, Len); end; {$ENDIF CLR} function StrKeepChars(const S: string; const Chars: TCharValidator): string; {$IFDEF CLR} var I: Integer; sb: StringBuilder; begin sb := StringBuilder.Create(Length(S)); for I := 0 to S.Length - 1 do if Chars(S[I]) then sb.Append(S[I]); Result := sb.ToString(); end; {$ELSE} var Source, Dest: PChar; Len, Index: Integer; begin Len := Length(S); SetLength(Result, Len); UniqueString(Result); Source := PChar(S); Dest := PChar(Result); for Index := 0 to Len - 1 do begin if Chars(Source^) then begin Dest^ := Source^; Inc(Dest); end; Inc(Source); end; SetLength(Result, Dest - PChar(Result)); end; {$ENDIF CLR} function StrKeepChars(const S: string; const Chars: array of Char): string; {$IFDEF CLR} var I: Integer; sb: StringBuilder; begin sb := StringBuilder.Create(Length(S)); for I := 0 to S.Length - 1 do if ArrayContainsChar(Chars,S[I]) then sb.Append(S[I]); Result := sb.ToString(); end; {$ELSE} var Source, Dest: PChar; Len, Index: Integer; begin Len := Length(S); SetLength(Result, Len); UniqueString(Result); Source := PChar(S); Dest := PChar(Result); for Index := 0 to Len - 1 do begin if ArrayContainsChar(Chars, Source^) then begin Dest^ := Source^; Inc(Dest); end; Inc(Source); end; SetLength(Result, Dest - PChar(Result)); end; {$ENDIF CLR} function StrRepeat(const S: string; Count: Integer): string; {$IFDEF CLR} var I, Len: Integer; sb: StringBuilder; begin Len := Length(S); if Len * Count > 0 then begin sb := StringBuilder.Create(Len * Count); for I := Count - 1 downto 0 do sb.Append(S); Result := sb.ToString(); end else Result := ''; end; {$ELSE} var Len, Index: Integer; Dest, Source: PChar; begin Len := Length(S); SetLength(Result, Count * Len); Dest := PChar(Result); Source := PChar(S); if Dest <> nil then for Index := 0 to Count - 1 do begin Move(Source^, Dest^, Len * SizeOf(Char)); Inc(Dest, Len); end; end; {$ENDIF CLR} function StrRepeatLength(const S: string; L: Integer): string; {$IFDEF CLR} var Count: Integer; LenS, Index: Integer; 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); Index := 1; while Count > 0 do begin MoveString(S, 1, Result, Index, LenS); Inc(Index, LenS); Dec(Count); end; if Length(S) > L then SetLength(Result, L); end; end; {$ELSE} var Len: Integer; Dest: PChar; begin Result := ''; Len := Length(S); if (Len > 0) and (S <> '') then begin SetLength(Result, L); Dest := PChar(Result); while (L > 0) do begin Move(S[1], Dest^, Min(L, Len) * SizeOf(Char)); Inc(Dest, Len); Dec(L, Len); end; end; end; {$ENDIF CLR} procedure StrReplace(var S: string; const Search, Replace: string; Flags: TReplaceFlags); {$IFDEF CLR} begin S := StringReplace(S, Search, Replace, Flags); // !!! Convertion to System.String end; {$ELSE} var SearchStr: string; ResultStr: string; { result string } SourcePtr: PChar; { pointer into S of character under examination } SourceMatchPtr: PChar; { pointers into S and Search when first character has } SearchMatchPtr: PChar; { been matched and we're probing for a complete match } ResultPtr: PChar; { pointer into Result of character being written } ResultIndex, SearchLength, { length of search string } ReplaceLength, { length of replace string } BufferLength, { length of temporary result buffer } ResultLength: Integer; { length of result string } C: Char; { first character of search string } IgnoreCase: Boolean; begin if Search = '' then begin if S = '' then begin S := Replace; Exit; end else raise EJclStringError.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 := PChar(ResultStr); SourcePtr := PChar(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 := PChar(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 - PChar(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 * SizeOf(Char)); 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; {$ENDIF CLR} function StrReplaceChar(const S: string; const Source, Replace: Char): string; {$IFNDEF CLR} var I: Integer; {$ENDIF ~CLR} begin {$IFDEF CLR} Result := S.Replace(Source, Replace); {$ELSE} Result := S; for I := 1 to Length(S) do if Result[I] = Source then Result[I] := Replace; {$ENDIF CLR} end; function StrReplaceChars(const S: string; const Chars: TCharValidator; Replace: Char): string; var I: Integer; {$IFDEF CLR} sb: StringBuilder; {$ENDIF CLR} begin {$IFDEF CLR} sb := StringBuilder.Create(S); for I := 0 to sb.Length - 1 do if Chars(sb[I]) then sb[I] := Replace; Result := sb.ToString(); {$ELSE} Result := S; for I := 1 to Length(S) do if Chars(Result[I]) then Result[I] := Replace; {$ENDIF CLR} end; function StrReplaceChars(const S: string; const Chars: array of Char; Replace: Char): string; var I: Integer; {$IFDEF CLR} sb: StringBuilder; {$ENDIF CLR} begin {$IFDEF CLR} sb := StringBuilder.Create(S); for I := 0 to sb.Length - 1 do if ArrayContainsChar(Chars,sb[I]) then sb[I] := Replace; Result := sb.ToString(); {$ELSE} Result := S; for I := 1 to Length(S) do if ArrayContainsChar(Chars, Result[I]) then Result[I] := Replace; {$ENDIF CLR} end; function StrReplaceButChars(const S: string; const Chars: TCharValidator; Replace: Char): string; var I: Integer; {$IFDEF CLR} sb: StringBuilder; {$ENDIF CLR} begin {$IFDEF CLR} sb := StringBuilder.Create(S); for I := 0 to sb.Length - 1 do if not Chars(sb[I]) then sb[I] := Replace; Result := sb.ToString(); {$ELSE} Result := S; for I := 1 to Length(S) do if not Chars(Result[I]) then Result[I] := Replace; {$ENDIF CLR} end; function StrReplaceButChars(const S: string; const Chars: array of Char; Replace: Char): string; var I: Integer; {$IFDEF CLR} sb: StringBuilder; {$ENDIF CLR} begin {$IFDEF CLR} sb := StringBuilder.Create(S); for I := 0 to sb.Length - 1 do if not ArrayContainsChar(Chars,sb[I]) then sb[I] := Replace; Result := sb.ToString(); {$ELSE} Result := S; for I := 1 to Length(S) do if not ArrayContainsChar(Chars, Result[I]) then Result[I] := Replace; {$ENDIF CLR} end; function StrReverse(const S: string): string; begin Result := S; StrReverseInplace(Result); end; procedure StrReverseInPlace(var S: string); { TODO -oahuser : Warning: This is dangerous for unicode surrogates } {$IFDEF CLR} var I, LenS: Integer; sb: StringBuilder; begin LenS := Length(S); sb := StringBuilder.Create(LenS); sb.Length := LenS; for I := 0 to LenS - 1 do sb[I] := S[LenS - I - 1]; S := sb.ToString(); end; {$ELSE} var P1, P2: PChar; C: Char; begin UniqueString(S); P1 := PChar(S); P2 := P1 + SizeOf(Char) * (Length(S) - 1); while P1 < P2 do begin C := P1^; P1^ := P2^; P2^ := C; Inc(P1); Dec(P2); end; end; {$ENDIF CLR} function StrSingleQuote(const S: string): string; begin Result := NativeSingleQuote + S + NativeSingleQuote; end; function StrSmartCase(const S: string; const Delimiters: TCharValidator): string; var {$IFDEF CLR} Index: Integer; LenS: Integer; sb: StringBuilder; {$ELSE} Source, Dest: PChar; Index, Len: Integer; {$ENDIF CLR} InternalDelimiters: TCharValidator; begin Result := ''; if Assigned(Delimiters) then InternalDelimiters := Delimiters else InternalDelimiters := CharIsSpace; if S <> '' then begin Result := S; {$IFDEF CLR} sb := StringBuilder.Create(S); LenS := Length(S); Index := 0; while Index < LenS do begin if (InternalDelimiters(sb[Index])) and (Index + 1 < LenS) and not (InternalDelimiters(sb[Index + 1])) then sb[Index + 1] := CharUpper(sb[Index + 1]); Inc(Index); end; sb[0] := CharUpper(sb[0]); Result := sb.ToString(); {$ELSE} UniqueString(Result); Len := Length(S); Source := PChar(S); Dest := PChar(Result); Inc(Dest); for Index := 2 to Len do begin if InternalDelimiters(Source^) and not InternalDelimiters(Dest^) then Dest^ := CharUpper(Dest^); Inc(Dest); Inc(Source); end; Result[1] := CharUpper(Result[1]); {$ENDIF CLR} end; end; function StrSmartCase(const S: string; const Delimiters: array of Char): string; var {$IFDEF CLR} Index: Integer; LenS: Integer; sb: StringBuilder; {$ELSE} Source, Dest: PChar; Index, Len: Integer; {$ENDIF CLR} begin Result := ''; if S <> '' then begin Result := S; {$IFDEF CLR} sb := StringBuilder.Create(S); LenS := Length(S); Index := 0; while Index < LenS do begin if ArrayContainsChar(Delimiters,sb[Index]) and (Index + 1 < LenS) and not ArrayContainsChar(Delimiters,sb[Index + 1]) then sb[Index + 1] := CharUpper(sb[Index + 1]); Inc(Index); end; sb[0] := CharUpper(sb[0]); Result := sb.ToString(); {$ELSE} UniqueString(Result); Len := Length(S); Source := PChar(S); Dest := PChar(Result); Inc(Dest); for Index := 2 to Len do begin if ArrayContainsChar(Delimiters, Source^) and not ArrayContainsChar(Delimiters, Dest^) then Dest^ := CharUpper(Dest^); Inc(Dest); Inc(Source); end; Result[1] := CharUpper(Result[1]); {$ENDIF CLR} end; end; function StrStringToEscaped(const S: string): string; var I: Integer; begin Result := ''; for I := 1 to Length(S) do begin case S[I] of NativeBackspace: Result := Result + '\b'; NativeBell: Result := Result + '\a'; NativeCarriageReturn: Result := Result + '\r'; NAtiveFormFeed: Result := Result + '\f'; NativeLineFeed: Result := Result + '\n'; NativeTab: Result := Result + '\t'; NativeVerticalTab: Result := Result + '\v'; NativeBackSlash: Result := Result + '\\'; NativeDoubleQuote: Result := Result + '\"'; else // Characters < ' ' are escaped with hex sequence if S[I] < #32 then Result := Result + Format('\x%.2x', [Integer(S[I])]) else Result := Result + S[I]; end; end; end; function StrStripNonNumberChars(const S: string): string; var I: Integer; C: Char; 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: string): string; var Index: Integer; C, L, N: Integer; BL, BH: Byte; S: string; {$IFDEF CLR} sb: StringBuilder; {$ENDIF CLR} begin {$IFDEF CLR} sb := StringBuilder.Create; {$ELSE} Result := ''; {$ENDIF CLR} if Source <> '' then begin S := Source; L := Length(S); if Odd(L) then begin S := '0' + S; Inc(L); end; Index := 1; {$IFDEF CLR} sb.Length := L div 2; {$ELSE} SetLength(Result, L div 2); {$ENDIF CLR} 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; {$IFDEF CLR} sb[N] := {$ELSE} Result[N] := {$ENDIF CLR} Char((BH shl 4) + BL); Inc(N); end; end; {$IFDEF CLR} Result := sb.ToString(); {$ENDIF CLR} end; function StrTrimCharLeft(const S: string; C: Char): string; var I, L: Integer; 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: string; const Chars: TCharValidator): string; var I, L: Integer; begin I := 1; L := Length(S); while (I <= L) and Chars(S[I]) do Inc(I); Result := Copy(S, I, L - I + 1); end; function StrTrimCharsLeft(const S: string; const Chars: array of Char): string; var I, L: Integer; begin I := 1; L := Length(S); while (I <= L) and ArrayContainsChar(Chars, S[I]) do Inc(I); Result := Copy(S, I, L - I + 1); end; function StrTrimCharRight(const S: string; C: Char): string; var I: Integer; begin I := Length(S); while (I >= 1) and (S[I] = C) do Dec(I); Result := Copy(S, 1, I); end; function StrTrimCharsRight(const S: string; const Chars: TCharValidator): string; var I: Integer; begin I := Length(S); while (I >= 1) and Chars(S[I]) do Dec(I); Result := Copy(S, 1, I); end; function StrTrimCharsRight(const S: string; const Chars: array of Char): string; var I: Integer; begin I := Length(S); while (I >= 1) and ArrayContainsChar(Chars, S[I]) do Dec(I); Result := Copy(S, 1, I); end; function StrTrimQuotes(const S: string): string; var First, Last: Char; L: Integer; begin L := Length(S); if L > 1 then begin First := S[1]; Last := S[L]; if (First = Last) and ((First = NativeSingleQuote) or (First = NativeDoubleQuote)) then Result := Copy(S, 2, L - 2) else Result := S; end else Result := S; end; function StrUpper(const S: string): string; begin Result := S; StrUpperInPlace(Result); end; procedure StrUpperInPlace(var S: string); {$IFDEF PIC} begin StrCase(S, StrUpOffset); end; {$ELSE} asm // StrCase(Str, StrUpOffset) MOV EDX, StrUpOffset JMP StrCase end; {$ENDIF PIC} {$IFNDEF CLR} procedure StrUpperBuff(S: PChar); {$IFDEF PIC} begin StrCaseBuff(S, StrUpOffset); end; {$ELSE} asm // StrCaseBuff(S, UpOffset) MOV EDX, StrUpOffset JMP StrCaseBuff end; {$ENDIF PIC} {$ENDIF ~CLR} {$IFNDEF CLR} //=== String Management ====================================================== {$IFNDEF SUPPORTS_UNICODE} {$IFDEF KEEP_DEPRECATED} procedure StrAddRef(var S: string); var Foo: string; begin if StrRefCount(S) = -1 then UniqueString(S) else begin Foo := S; Pointer(Foo) := nil; end; end; function StrAllocSize(const S: string): Longint; var P: Pointer; begin Result := 0; if Pointer(S) <> nil then begin P := Pointer(INT_PTR(Pointer(S)) - StrRefCountOffset); if Integer(P^) <> -1 then begin P := Pointer(INT_PTR(Pointer(S)) - StrAllocOffset); Result := Integer(P^); end; end; end; procedure StrDecRef(var S: string); var Foo: string; begin case StrRefCount(S) of -1, 0: { nothing } ; 1: begin Finalize(S); Pointer(S) := nil; end; else Pointer(Foo) := Pointer(S); end; end; function StrLength(const S: string): Longint; var P: Pointer; begin Result := 0; if Pointer(S) <> nil then begin P := Pointer(INT_PTR(Pointer(S)) - StrLengthOffset); Result := Longint(P^) and (not $80000000 shr 1); end; end; function StrRefCount(const S: string): Longint; var P: Pointer; begin Result := 0; if Pointer(S) <> nil then begin P := Pointer(INT_PTR(Pointer(S)) - StrRefCountOffset); Result := Longint(P^); end; end; {$ENDIF KEEP_DEPRECATED} {$ENDIF ~SUPPORTS_UNICODE} {$ENDIF ~CLR} procedure StrResetLength(var S: WideString); var I: Integer; begin for I := 0 to Length(S) - 1 do if S[I + 1] = #0 then begin SetLength(S, I); Exit; end; end; procedure StrResetLength(var S: AnsiString); var I: Integer; begin for I := 0 to Length(S) - 1 do if S[I + 1] = #0 then begin SetLength(S, I); Exit; end; end; procedure StrResetLength(S: TJclStringBuilder); var I: Integer; begin if S <> nil then for I := 0 to S.Length - 1 do if S[I] = #0 then begin S.Length := I; Exit; end; end; {$IFDEF SUPPORTS_UNICODE_STRING} procedure StrResetLength(var S: UnicodeString); var I: Integer; begin for I := 0 to Length(S) - 1 do if S[I + 1] = #0 then begin SetLength(S, I); Exit; end; end; {$ENDIF SUPPORTS_UNICODE_STRING} //=== String Search and Replace Routines ===================================== function StrCharCount(const S: string; C: Char): Integer; var I: Integer; begin Result := 0; for I := 1 to Length(S) do if S[I] = C then Inc(Result); end; function StrCharsCount(const S: string; const Chars: TCharValidator): Integer; var I: Integer; begin Result := 0; for I := 1 to Length(S) do if Chars(S[I]) then Inc(Result); end; function StrCharsCount(const S: string; const Chars: array of Char): Integer; var I: Integer; begin Result := 0; for I := 1 to Length(S) do if ArrayContainsChar(Chars, S[I]) then Inc(Result); end; function StrStrCount(const S, SubS: string): Integer; var I: Integer; 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; {$IFDEF SUPPORTS_UNICODE} (* { 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: string; Index, Count: Integer; CaseSensitive: Boolean): Integer; var Len1, Len2: Integer; I: Integer; C1, C2: Char; begin {$IFDEF CLR} if S1 = S2 then {$ELSE} if Pointer(S1) = Pointer(S2) then {$ENDIF CLR} 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: string): Integer; var Len1, Len2: Integer; begin {$IFDEF CLR} if S1 = S2 then {$ELSE} if Pointer(S1) = Pointer(S2) then {$ENDIF CLR} Result := 0 else begin Len1 := Length(S1); Len2 := Length(S2); Result := Len1 - Len2; if Result = 0 then Result := StrCompareRangeEx(S1, S2, 1, Len1, False); end; end; {$ELSE} // UNICODE {$IFDEF PIC} function _StrCompare(const S1, S2: string): Integer; forward; function StrCompare(const S1, S2: string): Integer; begin Result := _StrCompare(S1, S2); end; function _StrCompare(const S1, S2: string): Integer; {$ELSE} function StrCompare(const S1, S2: string): Integer; {$ENDIF PIC} asm // check if pointers are equal CMP EAX, EDX JE @@Equal // if S1 is nil return - Length(S2) TEST EAX, EAX JZ @@Str1Null // if S2 is nil return Length(S1) TEST EDX, EDX JZ @@Str2Null // EBX will hold case map, ESI S1, EDI S2 PUSH EBX PUSH ESI PUSH EDI // move string pointers MOV ESI, EAX MOV EDI, EDX // get the length of strings MOV EAX, [ESI-StrRecSize].TStrRec.Length MOV EDX, [EDI-StrRecSize].TStrRec.Length // exit if Length(S1) <> Length(S2) CMP EAX, EDX JNE @@MissMatch // check the length just in case DEC EDX JS @@InvalidStr DEC EAX JS @@InvalidStr // load case map LEA EBX, StrCaseMap // make ECX our loop counter MOV ECX, EAX // clear working regs XOR EAX, EAX XOR EDX, EDX // get last chars MOV AL, [ESI+ECX] MOV DL, [EDI+ECX] // lower case them MOV AL, [EBX+EAX] MOV DL, [EBX+EDX] // compare them CMP AL, DL JNE @@MissMatch // if there was only 1 char then exit JECXZ @@Match @@NextChar: // case sensitive compare of strings REPE CMPSB JE @@Match // if there was a missmatch try case insensitive compare, get the chars MOV AL, [ESI-1] MOV DL, [EDI-1] // lowercase and compare them, if equal then continue MOV AL, [EBX+EAX] MOV DL, [EBX+EDX] CMP AL, DL JE @@NextChar // if we make it here then strings don't match, return the difference @@MissMatch: SUB EAX, EDX POP EDI POP ESI POP EBX RET @@Match: // match, return 0 XOR EAX, EAX POP EDI POP ESI POP EBX RET @@InvalidStr: XOR EAX, EAX DEC EAX POP EDI POP ESI POP EBX RET @@Str1Null: // return = - Length(Str2); MOV EDX, [EDX-StrRecSize].TStrRec.Length SUB EAX, EDX RET @@Str2Null: // return = Length(Str2); MOV EAX, [EAX-StrRecSize].TStrRec.Length RET @@Equal: XOR EAX, EAX end; {$ENDIF SUPPORTS_UNICODE} function StrCompareRange(const S1, S2: string; const Index, Count: Integer): Integer; {$IFDEF SUPPORTS_UNICODE} begin Result := StrCompareRangeEx(S1, S2, Index, Count, True); end; {$ELSE} asm TEST EAX, EAX JZ @@Str1Null TEST EDX, EDX JZ @@StrNull DEC ECX JS @@StrNull PUSH EBX PUSH ESI PUSH EDI MOV EBX, Count DEC EBX JS @@NoWork MOV ESI, EAX MOV EDI, EDX MOV EDX, [ESI - StrRecSize].TStrRec.Length // # of chars in S1 - (Index - 1) SUB EDX, ECX JLE @@NoWork // # of chars in S1 - (Count - 1) SUB EDX, EBX JLE @@NoWork // move to index'th char ADD ESI, ECX MOV ECX, [EDI - StrRecSize].TStrRec.Length DEC ECX JS @@NoWork // if Length(S2) > Count then ECX := Count else ECX := Length(S2) CMP ECX, EBX JLE @@Skip1 MOV ECX, EBX @@Skip1: XOR EAX, EAX XOR EDX, EDX @@Loop: MOV AL, [ESI] INC ESI MOV DL, [EDI] INC EDI CMP AL, DL JNE @@MisMatch DEC ECX JGE @@Loop @@Match: XOR EAX, EAX POP EDI POP ESI POP EBX JMP @@Exit @@MisMatch: SUB EAX, EDX POP EDI POP ESI POP EBX JMP @@Exit @@NoWork: MOV EAX, -2 POP EDI POP ESI POP EBX JMP @@Exit @@Str1Null: MOV EAX, 0 TEST EDX, EDX JZ @@Exit @@StrNull: MOV EAX, -1 @@Exit: end; {$ENDIF SUPPORTS_UNICODE} {$IFNDEF CLR} procedure StrFillChar(var S; Count: Integer; C: Char); {$IFDEF SUPPORTS_UNICODE} asm DEC EDX JS @@Leave @@Loop: MOV [EAX], CX ADD EAX, 2 DEC EDX JNS @@Loop @@Leave: end; {$ELSE} begin if Count > 0 then FillChar(S, Count, C); end; {$ENDIF SUPPORTS_UNICODE} {$ENDIF CLR} function StrRepeatChar(C: Char; Count: Integer): string; {$IFDEF CLR} var sb: StringBuilder; begin sb := StringBuilder.Create(Count); while Count > 0 do begin sb.Append(C); Dec(Count); end; Result := sb.ToString(); end; {$ELSE} begin SetLength(Result, Count); if Count > 0 then StrFillChar(Result[1], Count, C); end; {$ENDIF CLR} {$IFDEF CLR} function StrFind(const Substr, S: string; const Index: Integer): Integer; begin Result := System.String(S).ToLower().IndexOf(System.String(SubStr).ToLower(), Index - 1) + 1; end; {$ELSE} function StrFind(const Substr, S: string; const Index: Integer): Integer; var Pos: PChar; begin if (SubStr <> '') and (S <> '') then begin pos := StrPos(@S[Index], PChar(SubStr)); if Pos = nil then result := 0 else Result := (Cardinal(Pos) - Cardinal(@S[1])) div SizeOf(Char) + 1; end else result := 0; end; {$ENDIF CLR} function StrHasPrefix(const S: string; const Prefixes: array of string): Boolean; begin Result := StrPrefixIndex(S, Prefixes) > -1; end; function StrIndex(const S: string; const List: array of string): Integer; var I: Integer; begin Result := -1; for I := Low(List) to High(List) do begin if StrCompare(S, List[I]) = 0 then begin Result := I; Break; end; end; end; function StrILastPos(const SubStr, S: string): Integer; begin Result := StrLastPos(StrUpper(SubStr), StrUpper(S)); end; function StrIPos(const SubStr, S: string): Integer; begin {$IFDEF CLR} Result := Pos(SubStr.ToUpper, S.ToUpper); {$ELSE} Result := Pos(StrUpper(SubStr), StrUpper(S)); {$ENDIF CLR} end; function StrIsOneOf(const S: string; const List: array of string): Boolean; begin Result := StrIndex(S, List) > -1; end; function StrLastPos(const SubStr, S: string): Integer; {$IFDEF CLR} begin Result := System.String(S).LastIndexOf(SubStr) + 1; end; {$ELSE} var Last, Current: PChar; begin Result := 0; Last := nil; Current := PChar(S); while (Current <> nil) and (Current^ <> #0) do begin Current := StrPos(PChar(Current), PChar(SubStr)); if Current <> nil then begin Last := Current; Inc(Current); end; end; if Last <> nil then Result := Abs(PChar(S) - Last) + 1; end; {$ENDIF CLR} // IMPORTANT NOTE: The StrMatch function does currently not work with the Asterix (*) function StrMatch(const Substr, S: string; const Index: Integer): Integer; {$IFDEF CLR} begin { TODO : StrMatch } Assert(False, 'Not implemented yet'); Result := 0; end; {$ELSE} {$IFDEF SUPPORTS_UNICODE} begin { TODO : StrMatch } Assert(False, 'Not implemented yet'); Result := 0; end; {$ELSE} asm // make sure that strings are not null TEST EAX, EAX JZ @@SubstrIsNull TEST EDX, EDX JZ @@StrIsNull // limit index to satisfy 1 <= index, and dec it DEC ECX JL @@IndexIsSmall // EBX will hold the case table, ESI pointer to Str, EDI pointer // to Substr and EBP # of chars in Substr to compare PUSH EBX PUSH ESI PUSH EDI PUSH EBP // set the string pointers MOV ESI, EDX MOV EDI, EAX // save the Index in EDX MOV EDX, ECX // save the address of Str to compute the result PUSH ESI // temporary get the length of Substr and Str MOV EBX, [EDI - StrRecSize].TStrRec.Length MOV ECX, [ESI - StrRecSize].TStrRec.Length // dec the length of Substr because the first char is brought out of it DEC EBX JS @@NotFound // #positions in Str to look at = Length(Str) - Length(Substr) - Index - 2 SUB ECX, EBX JLE @@NotFound SUB ECX, EDX JLE @@NotFound // # of chars in Substr to compare MOV EBP, EBX // point Str to Index'th char ADD ESI, EDX // load case map into EBX, and clear EAX & ECX LEA EBX, StrCaseMap XOR EAX, EAX XOR ECX, ECX // bring the first char out of the Substr and point Substr to the next char MOV CL, [EDI] INC EDI // lower case it MOV CL, [EBX + ECX] @@FindNext: // get the current char from Str into al MOV AL, [ESI] INC ESI // check the end of string TEST AL, AL JZ @@NotFound CMP CL, '*' // Wild Card? JE @@Compare CMP CL, '?' // Wild Card? JE @@Compare // lower case current char MOV AL, [EBX + EAX] // check if the current char matches the primary search char, // if not continue searching CMP AL, CL JNE @@FindNext @@Compare: // # of chars in Substr to compare } MOV EDX, EBP @@CompareNext: // dec loop counter and check if we reached the end. If yes then we found it DEC EDX JL @@Found // get the chars from Str and Substr, if they are equal then continue comparing MOV AL, [EDI + EDX] // char from Substr CMP AL, '*' // wild card? JE @@CompareNext CMP AL, '?' // wild card? JE @@CompareNext CMP AL, [ESI + EDX] // equal to PChar(Str)^ ? JE @@CompareNext MOV AL, [EBX + EAX + StrReOffset] // reverse case? CMP AL, [ESI + EDX] JNE @@FindNext // if still no, go back to the main loop // if they matched, continue comparing JMP @@CompareNext @@Found: // we found it, calculate the result MOV EAX, ESI POP ESI SUB EAX, ESI POP EBP POP EDI POP ESI POP EBX RET @@NotFound: // not found it, clear the result XOR EAX, EAX POP ESI POP EBP POP EDI POP ESI POP EBX RET @@IndexIsSmall: @@StrIsNull: // clear the result XOR EAX, EAX @@SubstrIsNull: @@Exit: end; {$ENDIF SUPPORTS_UNICODE} {$ENDIF CLR} // Derived from "Like" by Michael Winter function StrMatches(const Substr, S: string; const Index: Integer): Boolean; {$IFDEF CLR} begin Result := Substr = S; { TODO : StrMatches } end; {$ELSE} var StringPtr: PChar; PatternPtr: PChar; StringRes: PChar; PatternRes: PChar; begin if SubStr = '' then raise EJclStringError.CreateRes(@RsBlankSearchString); Result := SubStr = '*'; if Result or (S = '') then Exit; if (Index <= 0) or (Index > Length(S)) then raise EJclStringError.CreateRes(@RsArgumentOutOfRange); StringPtr := PChar(@S[Index]); PatternPtr := PChar(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; {$ENDIF CLR} function StrNPos(const S, SubStr: string; N: Integer): Integer; var I, P: Integer; 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: string; N: Integer): Integer; var I, P: Integer; 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: string; const Prefixes: array of string): Integer; var I: Integer; Test: string; 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; {$IFDEF CLR} function StrSearch(const Substr, S: string; const Index: Integer): Integer; begin Result := System.String(S).IndexOf(SubStr, Index - 1) + 1; end; {$ELSE} function StrSearch(const Substr, S: string; const Index: Integer): Integer; {$IFDEF SUPPORTS_UNICODE} begin Result := PosEx(SubStr, S, Index); end; {$ELSE} asm // make sure that strings are not null TEST EAX, EAX JZ @@SubstrIsNull TEST EDX, EDX JZ @@StrIsNull // limit index to satisfy 1 <= index, and dec it DEC ECX JL @@IndexIsSmall // ebp will hold # of chars in Substr to compare, esi pointer to Str, // edi pointer to Substr, ebx primary search char PUSH EBX PUSH ESI PUSH EDI PUSH EBP // set the string pointers MOV ESI, EDX MOV EDI, EAX // save the (Index - 1) in edx MOV EDX, ECX // save the address of Str to compute the result PUSH ESI // temporary get the length of Substr and Str MOV EBX, [EDI-StrRecSize].TStrRec.Length MOV ECX, [ESI-StrRecSize].TStrRec.Length // dec the length of Substr because the first char is brought out of it DEC EBX JS @@NotFound // # of positions in Str to look at = Length(Str) - Length(Substr) - Index - 2 SUB ECX, EBX JLE @@NotFound SUB ECX, EDX JLE @@NotFound // point Str to Index'th char ADD ESI, EDX // # of chars in Substr to compare MOV EBP, EBX // clear EAX & ECX (working regs) XOR EAX, EAX XOR EBX, EBX // bring the first char out of the Substr, and // point Substr to the next char MOV BL, [EDI] INC EDI // jump into the loop JMP @@Find @@FindNext: // update the loop counter and check the end of string. // if we reached the end, Substr was not found. DEC ECX JL @@NotFound @@Find: // get current char from the string, and /point Str to the next one. MOV AL, [ESI] INC ESI // does current char match primary search char? if not, go back to the main loop CMP AL, BL JNE @@FindNext // otherwise compare SubStr @@Compare: // move # of char to compare into edx, edx will be our compare loop counter. MOV EDX, EBP @@CompareNext: // check if we reached the end of Substr. If yes we found it. DEC EDX JL @@Found // get last chars from Str and SubStr and compare them, // if they don't match go back to out main loop. MOV AL, [EDI+EDX] CMP AL, [ESI+EDX] JNE @@FindNext // if they matched, continue comparing JMP @@CompareNext @@Found: // we found it, calculate the result and exit. MOV EAX, ESI POP ESI SUB EAX, ESI POP EBP POP EDI POP ESI POP EBX RET @@NotFound: // not found it, clear result and exit. XOR EAX, EAX POP ESI POP EBP POP EDI POP ESI POP EBX RET @@IndexIsSmall: @@StrIsNull: // clear result and exit. XOR EAX, EAX @@SubstrIsNull: @@Exit: end; {$ENDIF SUPPORTS_UNICODE} {$ENDIF CLR} //=== String Extraction ====================================================== function StrAfter(const SubStr, S: string): string; var P: Integer; 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: string): string; var P: Integer; begin P := StrFind(SubStr, S, 1); if P <= 0 then Result := S else Result := StrLeft(S, P - 1); end; function StrBetween(const S: string; const Start, Stop: Char): string; var PosStart, PosEnd: Integer; L: Integer; 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: string; N: Integer): string; begin Result := Copy(S, 1, Length(S) - N); end; function StrLeft(const S: string; Count: Integer): string; begin Result := Copy(S, 1, Count); end; function StrMid(const S: string; Start, Count: Integer): string; begin Result := Copy(S, Start, Count); end; function StrRestOf(const S: string; N: Integer): string; begin Result := Copy(S, N, (Length(S) - N + 1)); end; function StrRight(const S: string; Count: Integer): string; begin Result := Copy(S, Length(S) - Count + 1, Count); end; //=== Character (do we have it ;) ============================================ function CharEqualNoCase(const C1, C2: Char): 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: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsLetter(C); {$ELSE} Result := (StrCharTypes[C] and C1_ALPHA) <> 0; {$ENDIF CLR} end; function CharIsAlphaNum(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsLetterOrDigit(C); {$ELSE} Result := ((StrCharTypes[C] and C1_ALPHA) <> 0) or ((StrCharTypes[C] and C1_DIGIT) <> 0); {$ENDIF CLR} end; function CharIsBlank(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsSurrogate(C); {$ELSE} Result := ((StrCharTypes[C] and C1_BLANK) <> 0); {$ENDIF CLR} end; function CharIsControl(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsControl(C); {$ELSE} Result := (StrCharTypes[C] and C1_CNTRL) <> 0; {$ENDIF CLR} end; function CharIsDelete(const C: Char): Boolean; begin Result := (C = #8); end; function CharIsDigit(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsDigit(C); {$ELSE} Result := (StrCharTypes[C] and C1_DIGIT) <> 0; {$ENDIF CLR} end; function CharIsFracDigit(const C: Char): Boolean; begin {$IFDEF CLR} Result := (C = '.') or System.Char.IsDigit(C); {$ELSE} Result := (C = '.') or ((StrCharTypes[C] and C1_DIGIT) <> 0); {$ENDIF CLR} end; function CharIsHexDigit(const C: Char): Boolean; begin case C of 'A'..'F', 'a'..'f': Result := True; else {$IFDEF CLR} Result := System.Char.IsDigit(C); {$ELSE} Result := ((StrCharTypes[C] and C1_DIGIT) <> 0); {$ENDIF CLR} end; end; function CharIsLower(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsLower(C); {$ELSE} Result := (StrCharTypes[C] and C1_LOWER) <> 0; {$ENDIF CLR} end; function CharIsNumberChar(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsDigit(C) or (C = '+') or (C = '-') or (C = DecimalSeparator); {$ELSE} Result := ((StrCharTypes[C] and C1_DIGIT) <> 0) or (C = '+') or (C = '-') or (C = DecimalSeparator); {$ENDIF CLR} end; function CharIsNumber(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsDigit(C) or (C = DecimalSeparator); {$ELSE} Result := ((StrCharTypes[C] and C1_DIGIT) <> 0) or (C = DecimalSeparator); {$ENDIF CLR} end; function CharIsPrintable(const C: Char): Boolean; begin Result := not CharIsControl(C); end; function CharIsPunctuation(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsPunctuation(C); {$ELSE} Result := ((StrCharTypes[C] and C1_PUNCT) <> 0); {$ENDIF CLR} end; function CharIsReturn(const C: Char): Boolean; begin Result := (C = NativeLineFeed) or (C = NativeCarriageReturn); end; function CharIsSpace(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsSeparator(C); {$ELSE} Result := (StrCharTypes[C] and C1_SPACE) <> 0; {$ENDIF CLR} end; function CharIsUpper(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsUpper(C); {$ELSE} Result := (StrCharTypes[C] and C1_UPPER) <> 0; {$ENDIF CLR} end; function CharIsValidIdentifierLetter(const C: Char): Boolean; begin case C of {$IFDEF SUPPORTS_UNICODE} // from XML specifications #$00C0..#$00D6, #$00D8..#$00F6, #$00F8..#$02FF, #$0370..#$037D, #$037F..#$1FFF, #$200C..#$200D, #$2070..#$218F, #$2C00..#$2FEF, #$3001..#$D7FF, #$F900..#$FDCF, #$FDF0..#$FFFD, // #$10000..#$EFFFF, howto match surrogate pairs? #$00B7, #$0300..#$036F, #$203F..#$2040, {$ENDIF SUPPORTS_UNICODE} '0'..'9', 'A'..'Z', 'a'..'z', '_': Result := True; else Result := False; end; end; function CharIsWhiteSpace(const C: Char): Boolean; begin {$IFDEF CLR} Result := System.Char.IsWhiteSpace(C); {$ELSE} case C of NativeTab, NativeLineFeed, NativeVerticalTab, NativeFormFeed, NativeCarriageReturn, NativeSpace: Result := True; else Result := False; end; {$ENDIF CLR} end; function CharIsWildcard(const C: Char): Boolean; begin case C of '*', '?': Result := True; else Result := False; end; end; {$IFNDEF CLR} function CharType(const C: Char): Word; begin Result := StrCharTypes[C]; end; //=== PCharVector ============================================================ function StringsToPCharVector(var Dest: PCharVector; const Source: TStrings): PCharVector; var I: Integer; S: string; List: array of PChar; begin Assert(Source <> nil); Dest := AllocMem((Source.Count + SizeOf(Char)) * SizeOf(PChar)); SetLength(List, Source.Count + SizeOf(Char)); for I := 0 to Source.Count - 1 do begin S := Source[I]; List[I] := StrAlloc(Length(S) + SizeOf(Char)); StrPCopy(List[I], S); end; List[Source.Count] := nil; Move(List[0], Dest^, (Source.Count + 1) * SizeOf(PChar)); Result := Dest; end; function PCharVectorCount(Source: PCharVector): Integer; var P: PChar; begin Result := 0; if Source <> nil then begin P := Source^; while P <> nil do begin Inc(Result); P := PCharVector(INT_PTR(Source) + (SizeOf(PChar) * Result))^; end; end; end; procedure PCharVectorToStrings(const Dest: TStrings; Source: PCharVector); var I, Count: Integer; List: array of PChar; begin Assert(Dest <> nil); if Source <> nil then begin Count := PCharVectorCount(Source); SetLength(List, Count); Move(Source^, List[0], Count * SizeOf(PChar)); 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: PCharVector); var I, Count: Integer; List: array of PChar; begin if Dest <> nil then begin Count := PCharVectorCount(Dest); SetLength(List, Count); Move(Dest^, List[0], Count * SizeOf(PChar)); for I := 0 to Count - 1 do StrDispose(List[I]); FreeMem(Dest, (Count + 1) * SizeOf(PChar)); Dest := nil; end; end; {$ENDIF ~CLR} //=== Character Transformation Routines ====================================== function CharHex(const C: Char): 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: Char): Char; begin {$IFDEF CLR} Result := System.Char.ToLower(C); {$ELSE} Result := StrCaseMap[Ord(C) + StrLoOffset]; {$ENDIF CLR} end; function CharToggleCase(const C: Char): Char; begin {$IFDEF CLR} if System.Char.IsUpper(C) then Result := System.Char.ToLower(C) else if System.Char.IsLower(C) then Result := System.Char.ToUpper(C) else Result := C; {$ELSE} Result := StrCaseMap[Ord(C) + StrReOffset]; {$ENDIF CLR} end; function CharUpper(const C: Char): Char; begin {$IFDEF CLR} Result := System.Char.ToUpper(C); {$ELSE} Result := StrCaseMap[Ord(C) + StrUpOffset]; {$ENDIF CLR} end; //=== Character Search and Replace =========================================== function CharLastPos(const S: string; const C: Char; const Index: Integer): Integer; begin if (Index > 0) and (Index <= Length(S)) then begin for Result := Length(S) downto Index do if S[Result] = C then Exit; end; Result := 0; end; function CharPos(const S: string; const C: Char; const Index: Integer): Integer; begin if (Index > 0) and (Index <= Length(S)) then begin for Result := Index to Length(S) do if S[Result] = C then Exit; end; Result := 0; end; function CharIPos(const S: string; C: Char; const Index: Integer): Integer; begin if (Index > 0) and (Index <= Length(S)) then begin C := CharUpper(C); for Result := Index to Length(S) do {$IFDEF CLR} if System.Char.ToUpper(S[Result]) = C then {$ELSE} if StrCaseMap[Ord(S[Result]) + StrUpOffset] = C then {$ENDIF CLR} Exit; end; Result := 0; end; function CharReplace(var S: string; const Search, Replace: Char): Integer; {$IFDEF CLR} var I: Integer; begin Result := 0; for I := 1 to Length(S) do if S[I] = Search then Inc(Result); S := S.Replace(Search, Replace); end; {$ELSE} var P: PChar; Index, Len: Integer; begin Result := 0; if Search <> Replace then begin UniqueString(S); P := PChar(S); Len := Length(S); for Index := 0 to Len - 1 do begin if P^ = Search then begin P^ := Replace; Inc(Result); end; Inc(P); end; end; end; {$ENDIF CLR} {$IFNDEF CLR} //=== MultiSz ================================================================ function StringsToMultiSz(var Dest: PMultiSz; const Source: TStrings): PMultiSz; var I, TotalLength: Integer; P: PMultiSz; begin Assert(Source <> nil); TotalLength := 1; for I := 0 to Source.Count - 1 do if Source[I] = '' then raise EJclStringError.CreateRes(@RsInvalidEmptyStringItem) else Inc(TotalLength, StrLen(PChar(Source[I])) + 1); AllocateMultiSz(Dest, TotalLength); P := Dest; for I := 0 to Source.Count - 1 do begin P := StrECopy(P, PChar(Source[I])); Inc(P); end; P^ := #0; Result := Dest; end; procedure MultiSzToStrings(const Dest: TStrings; const Source: PMultiSz); var P: PMultiSz; 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 := StrEnd(P); Inc(P); end; end; finally Dest.EndUpdate; end; end; function MultiSzLength(const Source: PMultiSz): Integer; var P: PMultiSz; begin Result := 0; if Source <> nil then begin P := Source; repeat Inc(Result, StrLen(P) + 1); P := StrEnd(P); Inc(P); until P^ = #0; Inc(Result); end; end; procedure AllocateMultiSz(var Dest: PMultiSz; Len: Integer); begin if Len > 0 then GetMem(Dest, Len * SizeOf(Char)) else Dest := nil; end; procedure FreeMultiSz(var Dest: PMultiSz); begin if Dest <> nil then FreeMem(Dest); Dest := nil; end; function MultiSzDup(const Source: PMultiSz): PMultiSz; var Len: Integer; begin if Source <> nil then begin Len := MultiSzLength(Source); AllocateMultiSz(Result, Len); Move(Source^, Result^, Len * SizeOf(Char)); end else Result := nil; end; function AnsiStringsToAnsiMultiSz(var Dest: PAnsiMultiSz; const Source: TAnsiStrings): PAnsiMultiSz; begin Result := JclAnsiStrings.StringsToMultiSz(Dest, Source); end; procedure AnsiMultiSzToAnsiStrings(const Dest: TAnsiStrings; const Source: PAnsiMultiSz); begin JclAnsiStrings.MultiSzToStrings(Dest, Source); end; function AnsiMultiSzLength(const Source: PAnsiMultiSz): Integer; begin Result := JclAnsiStrings.MultiSzLength(Source); end; procedure AllocateAnsiMultiSz(var Dest: PAnsiMultiSz; Len: Integer); begin JclAnsiStrings.AllocateMultiSz(Dest, Len); end; procedure FreeAnsiMultiSz(var Dest: PAnsiMultiSz); begin JclAnsiStrings.FreeMultiSz(Dest); end; function AnsiMultiSzDup(const Source: PAnsiMultiSz): PAnsiMultiSz; begin Result := JclAnsiStrings.MultiSzDup(Source); end; function WideStringsToWideMultiSz(var Dest: PWideMultiSz; const Source: TWideStrings): PWideMultiSz; begin Result := JclWideStrings.StringsToMultiSz(Dest, Source); end; procedure WideMultiSzToWideStrings(const Dest: TWideStrings; const Source: PWideMultiSz); begin JclWideStrings.MultiSzToStrings(Dest, Source); end; function WideMultiSzLength(const Source: PWideMultiSz): Integer; begin Result := JclWideStrings.MultiSzLength(Source); end; procedure AllocateWideMultiSz(var Dest: PWideMultiSz; Len: Integer); begin JclWideStrings.AllocateMultiSz(Dest, Len); end; procedure FreeWideMultiSz(var Dest: PWideMultiSz); begin JclWideStrings.FreeMultiSz(Dest); end; function WideMultiSzDup(const Source: PWideMultiSz): PWideMultiSz; begin Result := JclWideStrings.MultiSzDup(Source); end; {$ENDIF ~CLR} //=== TStrings Manipulation ================================================== procedure StrToStrings(S, Sep: string; const List: TStrings; const AllowEmptyString: Boolean = True); var I, L: Integer; Left: string; 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 <> '' then List.Add(S); // Ignore empty strings at the end. finally List.EndUpdate; end; end; procedure StrIToStrings(S, Sep: string; const List: TStrings; const AllowEmptyString: Boolean = True); var I, L: Integer; LowerCaseStr: string; Left: string; 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 <> '' then List.Add(S); // Ignore empty strings at the end. finally List.EndUpdate; end; end; function StringsToStr(const List: TStrings; const Sep: string; const AllowEmptyString: Boolean): string; var I, L: Integer; 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: TStrings; DeleteIfEmpty: Boolean); var I: Integer; 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: TStrings; DeleteIfEmpty: Boolean); var I: Integer; 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: TStrings; DeleteIfEmpty: Boolean); var I: Integer; 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: string; Strings: TStrings; 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 ========================================================== {$IFDEF KEEP_DEPRECATED} function BooleanToStr(B: Boolean): string; const Bools: array [Boolean] of string = ('False', 'True'); begin Result := Bools[B]; end; {$ENDIF KEEP_DEPRECATED} function FileToString(const FileName: string): {$IFDEF COMPILER12_UP}RawByteString{$ELSE}AnsiString{$ENDIF}; var fs: TFileStream; Len: Integer; {$IFDEF CLR} Buf: array of Byte; {$ENDIF CLR} begin fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite); try Len := fs.Size; SetLength(Result, Len); if Len > 0 then {$IFDEF CLR} begin SetLength(Buf, Len); fs.ReadBuffer(Buf, Len); Result := Buf; end; {$ELSE} fs.ReadBuffer(Result[1], Len); {$ENDIF CLR} finally fs.Free; end; end; procedure StringToFile(const FileName: string; const Contents: {$IFDEF COMPILER12_UP}RawByteString{$ELSE}AnsiString{$ENDIF}; Append: Boolean); var FS: TFileStream; Len: Integer; begin if Append and FileExists(filename) then FS := TFileStream.Create(FileName, fmOpenReadWrite or fmShareDenyWrite) else FS := TFileStream.Create(FileName, fmCreate); try if Append then StreamSeek(FS, 0, soEnd); // faster than .Position := .Size Len := Length(Contents); if Len > 0 then {$IFDEF CLR} FS.WriteBuffer(BytesOf(Contents), Len); {$ELSE} FS.WriteBuffer(Contents[1], Len); {$ENDIF CLR} finally FS.Free; end; end; function StrToken(var S: string; Separator: Char): string; var I: Integer; 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; {$IFDEF CLR} procedure StrTokens(const S: string; const List: TStrings); var Start: Integer; Token: string; Done: Boolean; begin Assert(List <> nil); if List = nil then Exit; List.BeginUpdate; try List.Clear; Start := 0; repeat Done := StrWord(S, Start, Token); if Token <> '' then List.Add(Token); until Done; finally List.EndUpdate; end; end; function StrWord(const S: string; var Index: Integer; out Word: string): Boolean; var Start: Integer; C: Char; begin Word := ''; if (S = nil) or (S = '') then begin Result := True; Exit; end; Start := Index; Result := False; while True do begin case S[Index] of #0: begin if Start <> 0 then Word := S.Substring(Start, Index - Start); Result := True; Exit; end; NativeSpace, NativeLineFeed, NativeCarriageReturn: begin if Start <> 0 then begin Word := S.Substring(Start, Index - Start); Exit; end else begin C := S[Index]; while (C = NativeSpace) or (C = NativeLineFeed) or (C = NativeCarriageReturn) do begin Inc(Index); C := S[Index]; end; end; end; else if Start = 0 then Start := Index; Inc(Index); end; end; end; {$ELSE} procedure StrTokens(const S: string; const List: TStrings); var Start: PChar; Token: string; 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; function StrWord(var S: PChar; out Word: string): Boolean; var Start: PChar; 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; NativeSpace, NativeLineFeed, NativeCarriageReturn: 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; {$ENDIF ~CLR} procedure StrTokenToStrings(S: string; Separator: Char; const List: TStrings); var Token: string; 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 StrToFloatSafe(const S: string): Float; var Temp: string; I, J, K: Integer; SwapSeparators, IsNegative: Boolean; DecSep, ThouSep, C: Char; {$IFDEF CLR} sb: StringBuilder; {$ENDIF CLR} begin {$IFDEF CLR} DecSep := Char(DecimalSeparator[1]); ThouSep := Char(ThousandSeparator[1]); {$ELSE} DecSep := DecimalSeparator; ThouSep := ThousandSeparator; {$ENDIF CLR} Temp := S; SwapSeparators := False; IsNegative := False; J := 0; for I := 1 to Length(Temp) do begin C := Temp[I]; if C = '-' then IsNegative := not IsNegative else if (C <> ' ') and (C <> '(') and (C <> '+') 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 {$IFDEF CLR} sb := StringBuilder.Create(Temp); for I := 0 to sb.Length - 1 do if sb[I] = DecimalSeparator then sb[I] := ThouSep else if sb[I] = ThousandSeparator then sb[I] := DecSep; Temp := sb.ToString; {$ELSE} for I := 1 to Length(Temp) do if Temp[I] = DecSep then Temp[I] := ThouSep else if Temp[I] = ThouSep then Temp[I] := DecSep; {$ENDIF CLR} end; Temp := StrKeepChars(Temp, CharIsNumber); 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(Temp); if IsNegative then Result := -Result; end else Result := 0.0; end; function StrToIntSafe(const S: string): Integer; begin Result := Trunc(StrToFloatSafe(S)); end; procedure StrNormIndex(const StrLen: Integer; var Index: Integer; var Count: Integer); overload; begin Index := Max(1, Min(Index, StrLen + 1)); Count := Max(0, Min(Count, StrLen + 1 - Index)); end; {$IFDEF CLR} function ArrayOf(List: TStrings): TDynStringArray; var I: Integer; begin if List <> nil then begin SetLength(Result, List.Count); for I := 0 to List.Count - 1 do Result[I] := List[I]; end else Result := nil; end; {$ENDIF CLR} {$IFDEF COMPILER5} // missing Delphi 5 functions function TryStrToInt(const S: string; out Value: Integer): Boolean; var Err: Integer; begin Val(S, Value, Err); Result := Err = 0; end; function TryStrToInt64(const S: string; out Value: Int64): Boolean; var Err: Integer; begin Val(S, Value, Err); Result := Err = 0; end; function TryStrToFloat(const S: string; out Value: Extended): Boolean; begin Result := TextToFloat(PChar(S), Value, fvExtended); end; function TryStrToFloat(const S: string; out Value: Double): Boolean; var F: Extended; begin Result := TryStrToFloat(S, F); if Result then Value := F; end; function TryStrToFloat(const S: string; out Value: Single): Boolean; var F: Extended; begin Result := TryStrToFloat(S, F); if Result then Value := F; end; function TryStrToCurr(const S: string; out Value: Currency): Boolean; begin Result := TextToFloat(PChar(S), Value, fvCurrency); end; {$ENDIF COMPILER5} {$IFDEF CLR} function DotNetFormat(const Fmt: string; const Args: array of System.Object): string; begin Result := System.String.Format(Fmt, Args); end; function DotNetFormat(const Fmt: string; const Arg0: System.Object): string; begin Result := System.String.Format(Fmt, Arg0); end; function DotNetFormat(const Fmt: string; const Arg0, Arg1: System.Object): string; begin Result := System.String.Format(Fmt, Arg0, Arg1); end; function DotNetFormat(const Fmt: string; const Arg0, Arg1, Arg2: System.Object): string; begin Result := System.String.Format(Fmt, Arg0, Arg1, Arg2); end; {$ELSE} const BoolToStr: array [Boolean] of string = ('false', 'true'); {$IFDEF COMPILER5} MaxCurrency: Currency = 922337203685477.5807; varShortInt = $0010; { vt_i1 16 } varWord = $0012; { vt_ui2 18 } varLongWord = $0013; { vt_ui4 19 } varInt64 = $0014; { vt_i8 20 } {$ENDIF COMPILER5} type TInterfacedObjectAccess = class(TInterfacedObject); procedure MoveChar(const Source; var Dest; Count: Integer); begin if Count > 0 then Move(Source, Dest, Count * SizeOf(Char)); end; function DotNetFormat(const Fmt: string; const Arg0: Variant): string; begin Result := DotNetFormat(Fmt, [Arg0]); end; function DotNetFormat(const Fmt: string; const Arg0, Arg1: Variant): string; begin Result := DotNetFormat(Fmt, [Arg0, Arg1]); end; function DotNetFormat(const Fmt: string; const Arg0, Arg1, Arg2: Variant): string; begin Result := DotNetFormat(Fmt, [Arg0, Arg1, Arg2]); end; function DotNetFormat(const Fmt: string; const Args: array of const): string; var F, P: PChar; Len, Capacity, Count: Integer; Index, ErrorCode: Integer; S: string; procedure Grow(Count: Integer); begin if Len + Count > Capacity then begin Capacity := Capacity * 5 div 3 + Count; SetLength(Result, Capacity); end; end; function InheritsFrom(AClass: TClass; const ClassName: string): Boolean; begin Result := True; while AClass <> nil do begin if CompareText(AClass.ClassName, ClassName) = 0 then Exit; AClass := AClass.ClassParent; end; Result := False; end; function GetStringOf(const V: TVarData; Index: Integer): string; overload; begin case V.VType of varEmpty, varNull: raise ArgumentNullException.CreateRes(@RsArgumentIsNull); varSmallInt: Result := IntToStr(V.VSmallInt); varInteger: Result := IntToStr(V.VInteger); varSingle: Result := FloatToStr(V.VSingle); varDouble: Result := FloatToStr(V.VDouble); varCurrency: Result := CurrToStr(V.VCurrency); varDate: Result := DateTimeToStr(V.VDate); varOleStr: Result := V.VOleStr; varBoolean: Result := BoolToStr[V.VBoolean <> False]; varByte: Result := IntToStr(V.VByte); {$IFDEF COMPILER5} varWord: Result := IntToStr(Word(V.VSmallint)); varShortInt: Result := IntToStr(ShortInt(V.VByte)); varLongWord: Result := IntToStr(V.VError); {$ELSE} varWord: Result := IntToStr(V.VWord); varShortInt: Result := IntToStr(V.VShortInt); varLongWord: Result := IntToStr(V.VLongWord); varInt64: Result := IntToStr(V.VInt64); {$ENDIF COMPILER5} varString: Result := string(V.VString); {varArray, varDispatch, varError, varUnknown, varAny, varByRef:} else raise ArgumentNullException.CreateResFmt(@RsDotNetFormatArgumentNotSupported, [Index]); end; end; function GetStringOf(Index: Integer): string; overload; var V: TVarRec; Intf: IToString; begin V := Args[Index]; if (V.VInteger = 0) and (V.VType in [vtExtended, vtString, vtObject, vtClass, vtCurrency, vtInterface, vtInt64]) then raise ArgumentNullException.CreateResFmt(@RsArgumentIsNull, [Index]); case V.VType of vtInteger: Result := IntToStr(V.VInteger); vtBoolean: Result := BoolToStr[V.VBoolean]; vtChar: Result := string(AnsiString(V.VChar)); vtExtended: Result := FloatToStr(V.VExtended^); vtString: Result := string(V.VString^); vtPointer: Result := IntToHex(DWORD_PTR(V.VPointer), 8); vtPChar: Result := string(AnsiString(V.VPChar)); vtObject: if (V.VObject is TInterfacedObject) and V.VObject.GetInterface(IToString, Intf) then begin Result := Intf.ToString; Pointer(Intf) := nil; // do not release the object // undo the RefCount change Dec(TInterfacedObjectAccess(V.VObject).FRefCount); end else if InheritsFrom(V.VObject.ClassType, 'TComponent') and V.VObject.GetInterface(IToString, Intf) then Result := Intf.ToString else raise ArgumentNullException.CreateResFmt(@RsDotNetFormatArgumentNotSupported, [Index]); vtClass: Result := V.VClass.ClassName; vtWideChar: Result := V.VWideChar; vtPWideChar: Result := V.VPWideChar; vtAnsiString: Result := string(V.VAnsiString); vtCurrency: Result := CurrToStr(V.VCurrency^); vtVariant: Result := GetStringOf(TVarData(V.VVariant^), Index); vtInterface: if IInterface(V.VInterface).QueryInterface(IToString, Intf) = 0 then Result := IToString(Intf).ToString else raise ArgumentNullException.CreateResFmt(@RsDotNetFormatArgumentNotSupported, [Index]); vtWideString: Result := WideString(V.VWideString); vtInt64: Result := IntToStr(V.VInt64^); {$IFDEF SUPPORTS_UNICODE_STRING} vtUnicodeString: Result := UnicodeString(V.VUnicodeString); {$ENDIF SUPPORTS_UNICODE_STRING} else raise ArgumentNullException.CreateResFmt(@RsDotNetFormatArgumentNotSupported, [Index]); end; end; begin if Length(Args) = 0 then begin Result := Fmt; Exit; end; Len := 0; Capacity := Length(Fmt); SetLength(Result, Capacity); if Capacity = 0 then raise ArgumentNullException.CreateRes(@RsDotNetFormatNullFormat); P := Pointer(Fmt); F := P; while True do begin if (P[0] = #0) or (P[0] = '{') then begin Count := P - F; Inc(P); if (P[-1] <> #0) and (P[0] = '{') then Inc(Count); // include '{' if Count > 0 then begin Grow(Count); MoveChar(F[0], Result[Len + 1], Count); Inc(Len, Count); end; if P[-1] = #0 then Break; if P[0] <> '{' then begin F := P; Inc(P); while (P[0] <> #0) and (P[0] <> '}') do Inc(P); SetString(S, F, P - F); Val(S, Index, ErrorCode); if ErrorCode <> 0 then raise FormatException.CreateRes(@RsFormatException); if (Index < 0) or (Index > High(Args)) then raise FormatException.CreateRes(@RsFormatException); S := GetStringOf(Index); if S <> '' then begin Grow(Length(S)); MoveChar(S[1], Result[Len + 1], Length(S)); Inc(Len, Length(S)); end; if P[0] = #0 then Break; end; F := P + 1; end else if (P[0] = '}') and (P[1] = '}') then begin Count := P - F + 1; Inc(P); // skip next '}' Grow(Count); MoveChar(F[0], Result[Len + 1], Count); Inc(Len, Count); F := P + 1; end; Inc(P); end; SetLength(Result, Len); end; //=== { TJclStringBuilder } ===================================================== constructor TJclStringBuilder.Create(Capacity: Integer; MaxCapacity: Integer); begin inherited Create; SetLength(FChars, Capacity); FMaxCapacity := MaxCapacity; end; constructor TJclStringBuilder.Create(const Value: string; Capacity: Integer); begin Create(Capacity); Append(Value); end; constructor TJclStringBuilder.Create(const Value: string; StartIndex, Length, Capacity: Integer); begin Create(Capacity); Append(Value, StartIndex + 1, Length); end; function TJclStringBuilder.ToString: string; begin if FLength > 0 then SetString(Result, PChar(@FChars[0]), FLength) else Result := ''; end; function TJclStringBuilder.EnsureCapacity(Capacity: Integer): Integer; begin if System.Length(FChars) < Capacity then SetCapacity(Capacity); Result := System.Length(FChars); end; procedure TJclStringBuilder.SetCapacity(const Value: Integer); begin if Value <> System.Length(FChars) then begin SetLength(FChars, Value); if Value < FLength then FLength := Value; end; end; function TJclStringBuilder.GetChars(Index: Integer): Char; begin Result := FChars[Index]; end; procedure TJclStringBuilder.SetChars(Index: Integer; const Value: Char); begin FChars[Index] := Value; end; procedure TJclStringBuilder.Set_Length(const Value: Integer); begin FLength := Value; end; function TJclStringBuilder.GetCapacity: Integer; begin Result := System.Length(FChars); end; function TJclStringBuilder.AppendPChar(Value: PChar; Count: Integer; RepeatCount: Integer): TJclStringBuilder; var Capacity: Integer; begin if (Count > 0) and (RepeatCount > 0) then begin repeat Capacity := System.Length(FChars); if Capacity + Count > MaxCapacity then raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange); if Capacity < FLength + Count then SetLength(FChars, Capacity * 5 div 3 + Count); if Count = 1 then FChars[FLength] := Value[0] else MoveChar(Value[0], FChars[FLength], Count); Inc(FLength, Count); Dec(RepeatCount); until RepeatCount <= 0; end; Result := Self; end; function TJclStringBuilder.InsertPChar(Index: Integer; Value: PChar; Count, RepeatCount: Integer): TJclStringBuilder; var Capacity: Integer; begin if (Index < 0) or (Index > FLength) then raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange); if Index = FLength then AppendPChar(Value, Count, RepeatCount) else if (Count > 0) and (RepeatCount > 0) then begin repeat Capacity := System.Length(FChars); if Capacity + Count > MaxCapacity then raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange); if Capacity < FLength + Count then SetLength(FChars, Capacity * 5 div 3 + Count); MoveChar(FChars[Index], FChars[Index + Count], FLength - Index); if Count = 1 then FChars[Index] := Value[0] else MoveChar(Value[0], FChars[Index], Count); Inc(FLength, Count); Dec(RepeatCount); Inc(Index, Count); // little optimization until RepeatCount <= 0; end; Result := Self; end; function TJclStringBuilder.Append(const Value: array of Char): TJclStringBuilder; var Len: Integer; begin Len := System.Length(Value); if Len > 0 then AppendPChar(@Value[0], Len); Result := Self; end; function TJclStringBuilder.Append(const Value: array of Char; StartIndex, Length: Integer): TJclStringBuilder; var Len: Integer; begin Len := System.Length(Value); if (Length > 0) and (StartIndex < Len) then begin if StartIndex + Length > Len then Length := Len - StartIndex; AppendPChar(PChar(@Value[0]) + StartIndex, Length); end; Result := Self; end; function TJclStringBuilder.Append(Value: Char; RepeatCount: Integer = 1): TJclStringBuilder; begin Result := AppendPChar(@Value, 1, RepeatCount); end; function TJclStringBuilder.Append(const Value: string): TJclStringBuilder; var Len: Integer; begin Len := System.Length(Value); if Len > 0 then AppendPChar(Pointer(Value), Len); Result := Self; end; function TJclStringBuilder.Append(const Value: string; StartIndex, Length: Integer): TJclStringBuilder; var Len: Integer; begin Len := System.Length(Value); if (Length > 0) and (StartIndex < Len) then begin if StartIndex + Length > Len then Length := Len - StartIndex; AppendPChar(PChar(Pointer(Value)) + StartIndex, Length); end; Result := Self; end; function TJclStringBuilder.Append(Value: Boolean): TJclStringBuilder; begin Result := Append(BoolToStr[Value]); end; function TJclStringBuilder.Append(Value: Cardinal): TJclStringBuilder; begin Result := Append(IntToStr(Value)); end; function TJclStringBuilder.Append(Value: Integer): TJclStringBuilder; begin Result := Append(IntToStr(Value)); end; function TJclStringBuilder.Append(Value: Double): TJclStringBuilder; begin Result := Append(FloatToStr(Value)); end; function TJclStringBuilder.Append(Value: Int64): TJclStringBuilder; begin Result := Append(IntToStr(Value)); end; function TJclStringBuilder.Append(Obj: TObject): TJclStringBuilder; begin Result := Append(DotNetFormat('{0}', [Obj])); end; function TJclStringBuilder.AppendFormat(const Fmt: string; Arg0: Variant): TJclStringBuilder; begin Result := Append(DotNetFormat(Fmt, [Arg0])); end; function TJclStringBuilder.AppendFormat(const Fmt: string; Arg0, Arg1: Variant): TJclStringBuilder; begin Result := Append(DotNetFormat(Fmt, [Arg0, Arg1])); end; function TJclStringBuilder.AppendFormat(const Fmt: string; Arg0, Arg1, Arg2: Variant): TJclStringBuilder; begin Result := Append(DotNetFormat(Fmt, [Arg0, Arg1, Arg2])); end; function TJclStringBuilder.AppendFormat(const Fmt: string; const Args: array of const): TJclStringBuilder; begin Result := Append(DotNetFormat(Fmt, Args)); end; function TJclStringBuilder.Insert(Index: Integer; const Value: array of Char): TJclStringBuilder; var Len: Integer; begin Len := System.Length(Value); if Len > 0 then InsertPChar(Index, @Value[0], Len); Result := Self; end; function TJclStringBuilder.Insert(Index: Integer; const Value: string; Count: Integer): TJclStringBuilder; var Len: Integer; begin Len := System.Length(Value); if Len > 0 then InsertPChar(Index, Pointer(Value), Len, Count); Result := Self; end; function TJclStringBuilder.Insert(Index: Integer; Value: Boolean): TJclStringBuilder; begin Result := Insert(Index, BoolToStr[Value]); end; function TJclStringBuilder.Insert(Index: Integer; const Value: array of Char; StartIndex, Length: Integer): TJclStringBuilder; var Len: Integer; begin Len := System.Length(Value); if (Length > 0) and (StartIndex < Len) then begin if StartIndex + Length > Len then Length := Len - StartIndex; InsertPChar(Index, PChar(@Value[0]) + StartIndex, Length); end; Result := Self; end; function TJclStringBuilder.Insert(Index: Integer; Value: Double): TJclStringBuilder; begin Result := Insert(Index, FloatToStr(Value)); end; function TJclStringBuilder.Insert(Index: Integer; Value: Int64): TJclStringBuilder; begin Result := Insert(Index, IntToStr(Value)); end; function TJclStringBuilder.Insert(Index: Integer; Value: Cardinal): TJclStringBuilder; begin Result := Insert(Index, IntToStr(Value)); end; function TJclStringBuilder.Insert(Index, Value: Integer): TJclStringBuilder; begin Result := Insert(Index, IntToStr(Value)); end; function TJclStringBuilder.Insert(Index: Integer; Obj: TObject): TJclStringBuilder; begin Result := Insert(Index, Format('{0}', [Obj])); end; function TJclStringBuilder.Remove(StartIndex, Length: Integer): TJclStringBuilder; begin if (StartIndex < 0) or (Length < 0) or (StartIndex + Length > FLength) then raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange); if Length > 0 then begin MoveChar(FChars[StartIndex + Length], FChars[StartIndex], Length); Dec(FLength, Length); end; Result := Self; end; function TJclStringBuilder.Replace(OldChar, NewChar: Char; StartIndex, Count: Integer): TJclStringBuilder; var I: Integer; begin if Count = -1 then Count := FLength; if (StartIndex < 0) or (Count < 0) or (StartIndex + Count > FLength) then raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange); if (Count > 0) and (OldChar <> NewChar) then begin for I := StartIndex to StartIndex + Length - 1 do if FChars[I] = OldChar then FChars[I] := NewChar; end; Result := Self; end; function TJclStringBuilder.Replace(OldValue, NewValue: string; StartIndex, Count: Integer): TJclStringBuilder; var I: Integer; Offset: Integer; NewLen, OldLen, Capacity: Integer; begin if Count = -1 then Count := FLength; if (StartIndex < 0) or (Count < 0) or (StartIndex + Count > FLength) then raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange); if OldValue = '' then raise ArgumentException.CreateResFmt(@RsArgumentIsNull, [0]); if (Count > 0) and (OldValue <> NewValue) then begin OldLen := System.Length(OldValue); NewLen := System.Length(NewValue); Offset := NewLen - OldLen; Capacity := System.Length(FChars); for I := StartIndex to StartIndex + Length - 1 do if FChars[I] = OldValue[1] then begin if OldLen > 1 then if StrLComp(@FChars[I + 1], PChar(OldValue) + 1, OldLen - 1) <> 0 then Continue; if Offset <> 0 then begin if FLength - OldLen + NewLen > MaxCurrency then raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange); if Capacity < FLength + Offset then begin Capacity := Capacity * 5 div 3 + Offset; SetLength(FChars, Capacity); end; if Offset < 0 then MoveChar(FChars[I - Offset], FChars[I], FLength - I) else MoveChar(FChars[I + OldLen], FChars[I + OldLen + Offset], FLength - OldLen - I); Inc(FLength, Offset); end; if NewLen > 0 then begin if (OldLen = 1) and (NewLen = 1) then FChars[I] := NewValue[1] else MoveChar(NewValue[1], FChars[I], NewLen); end; end; end; Result := Self; end; {$ENDIF CLR} {$IFNDEF CLR} function StrExpandTabs(S: string): string; begin // use an empty tab set, which will default to a tab width of 2 Result := TJclTabSet(nil).Expand(s); end; function StrExpandTabs(S: string; TabWidth: Integer): string; var TabSet: TJclTabSet; begin // create a tab set with no tab stops and the given tab width TabSet := TJclTabSet.Create(TabWidth); try Result := TabSet.Expand(S); finally TabSet.Free; end; end; function StrExpandTabs(S: string; TabSet: TJclTabSet): string; begin // use the provided tab set to perform the expansion Result := TabSet.Expand(S); end; function StrOptimizeTabs(S: string): string; begin // use an empty tab set, which will default to a tab width of 2 Result := TJclTabSet(nil).Optimize(s); end; function StrOptimizeTabs(S: string; TabWidth: Integer): string; var TabSet: TJclTabSet; begin // create a tab set with no tab stops and the given tab width TabSet := TJclTabSet.Create(TabWidth); try Result := TabSet.Optimize(S); finally TabSet.Free; end; end; function StrOptimizeTabs(S: string; TabSet: TJclTabSet): string; begin // use the provided tab set to perform the optimization Result := TabSet.Optimize(S); end; //=== { TJclTabSet } ===================================================== constructor TJclTabSet.Create; begin // no tab stops, tab width set to auto Create([], True, 0); end; constructor TJclTabSet.Create(TabWidth: Integer); begin // no tab stops, specified tab width Create([], True, TabWidth); end; constructor TJclTabSet.Create(const Tabstops: array of Integer; ZeroBased: Boolean); begin // specified tab stops, tab width equal to distance between last two tab stops Create(Tabstops, ZeroBased, 0); end; constructor TJclTabSet.Create(const Tabstops: array of Integer; ZeroBased: Boolean; TabWidth: Integer); var idx: Integer; begin inherited Create; for idx := 0 to High(Tabstops) do Add(Tabstops[idx]); FWidth := TabWidth; FZeroBased := ZeroBased; CalcRealWidth; end; function TJclTabSet.Add(Column: Integer): Integer; begin if Self = nil then raise NullReferenceException.Create; if Column < StartColumn then raise ArgumentOutOfRangeException.Create('Column'); Result := FindStop(Column); if Result < 0 then begin // the column doesn't exist; invert the result of FindStop to get the correct index position Result := not Result; // increase the tab stop array SetLength(FStops, Length(FStops) + 1); // make room at the insert position MoveArray(FStops, Result, Result + 1, High(FStops) - Result); // add the tab stop at the correct location FStops[Result] := Column; CalcRealWidth; end else begin {$IFDEF CLR} raise EJclStringError.Create(RsTabs_DuplicatesNotAllowed); {$ELSE} raise EJclStringError.CreateRes(@RsTabs_DuplicatesNotAllowed); {$ENDIF} end; end; procedure TJclTabSet.CalcRealWidth; begin if FWidth < 1 then begin if Length(FStops) > 1 then FRealWidth := FStops[High(FStops)] - FStops[Pred(High(FStops))] else if Length(FStops) = 1 then FRealWidth := FStops[0] else FRealWidth := 2; end else FRealWidth := FWidth; end; function TJclTabSet.Delete(Column: Integer): Integer; begin Result := FindStop(Column); if Result >= 0 then RemoveAt(Result); end; function TJclTabSet.Expand(const S: string): string; begin Result := Expand(s, StartColumn); end; function TJclTabSet.Expand(const S: string; Column: Integer): string; var sb: TStringBuilder; head: PChar; cur: PChar; begin if Column < StartColumn then raise ArgumentOutOfRangeException.Create('Column'); sb := TStringBuilder.Create(Length(S)); try cur := PChar(S); while cur^ <> #0 do begin head := cur; while (cur^ <> #0) and (cur^ <> #9) do begin if CharIsReturn(cur^) then Column := StartColumn else Inc(Column); Inc(cur); end; if cur > head then sb.Append(head, 0, cur - head); if cur^ = #9 then begin sb.Append(' ', TabFrom(Column) - Column); Column := TabFrom(Column); Inc(cur); end; end; Result := sb.ToString; finally sb.Free; end; end; function TJclTabSet.FindStop(Column: Integer): Integer; begin if Self <> nil then begin Result := High(FStops); while (Result >= 0) and (FStops[Result] > Column) do Dec(Result); if (Result >= 0) and (FStops[Result] <> Column) then Result := not Succ(Result); end else Result := -1; end; class function TJclTabSet.FromString(const S: string): TJclTabSet; var cur: PChar; procedure SkipWhiteSpace; begin while CharIsWhiteSpace(cur^) do Inc(cur); end; function ParseNumber: Integer; var head: PChar; begin SkipWhiteSpace; head := cur; while CharIsDigit(cur^) do Inc(cur); if (cur <= head) or not TryStrToInt(Copy(head, 1, cur - head), Result) then Result := -1; end; procedure ParseStops; var openBracket, hadComma: Boolean; num: Integer; begin SkipWhiteSpace; openBracket := cur^ = '['; hadComma := False; if openBracket then Inc(cur); repeat num := ParseNumber; if (num < 0) and hadComma then {$IFDEF CLR} raise EJclStringError.Create(RsTabs_StopExpected) {$ELSE} raise EJclStringError.CreateRes(@RsTabs_StopExpected) {$ENDIF} else if num >= 0 then Result.Add(num); SkipWhiteSpace; hadComma := cur^ = ','; if hadComma then Inc(cur); until (cur^ = #0) or (cur^ = '+') or (cur^ = ']'); if hadComma then {$IFDEF CLR} raise EJclStringError.Create(RsTabs_StopExpected) {$ELSE} raise EJclStringError.CreateRes(@RsTabs_StopExpected) {$ENDIF} else if openBracket and (cur^ <> ']') then {$IFDEF CLR} raise EJclStringError.Create(RsTabs_CloseBracketExpected) {$ELSE} raise EJclStringError.CreateRes(@RsTabs_CloseBracketExpected); {$ENDIF} end; procedure ParseTabWidth; var num: Integer; begin SkipWhiteSpace; if cur^ = '+' then begin Inc(cur); SkipWhiteSpace; num := ParseNumber; if (num < 0) then {$IFDEF CLR} raise EJclStringError.Create(RsTabs_TabWidthExpected) {$ELSE} raise EJclStringError.CreateRes(@RsTabs_TabWidthExpected) {$ENDIF} else Result.TabWidth := num; end; end; procedure ParseZeroBasedFlag; begin SkipWhiteSpace; if cur^ = '0' then begin Inc(cur); if CharIsWhiteSpace(cur^) or (cur^ = #0) or (cur^ = '[') then begin Result.ZeroBased := True; SkipWhiteSpace; end else Dec(cur); end; end; begin Result := TJclTabSet.Create; try Result.ZeroBased := False; cur := PChar(S); ParseZeroBasedFlag; ParseStops; ParseTabWidth; except // clean up the partially complete instance (to avoid memory leaks)... Result.Free; // ... and re-raise the exception raise; end; end; function TJclTabSet.GetCount: Integer; begin if Self <> nil then Result := Length(FStops) else Result := 0; end; function TJclTabSet.GetStops(Index: Integer): Integer; begin if Self <> nil then begin if (Index < 0) or (Index >= Length(FStops)) then begin {$IFDEF CLR} raise EJclStringError.Create(RsArgumentOutOfRange); {$ELSE} raise EJclStringError.CreateRes(@RsArgumentOutOfRange); {$ENDIF CLR} end else Result := FStops[Index]; end else begin {$IFDEF CLR} raise EJclStringError.Create(RsArgumentOutOfRange); {$ELSE} raise EJclStringError.CreateRes(@RsArgumentOutOfRange); {$ENDIF CLR} end; end; function TJclTabSet.GetTabWidth: Integer; begin if Self <> nil then Result := FWidth else Result := 0; end; function TJclTabSet.GetZeroBased: Boolean; begin Result := (Self = nil) or FZeroBased; end; procedure TJclTabSet.OptimalFillInfo(StartColumn, TargetColumn: Integer; out TabsNeeded, SpacesNeeded: Integer); var nextTab: Integer; begin if StartColumn < Self.StartColumn then // starting column less than 1 or 0 (depending on ZeroBased state) raise ArgumentOutOfRangeException.Create('StartColumn'); if (TargetColumn < StartColumn) then // target lies before the starting column raise ArgumentOutOfRangeException.Create('TargetColumn'); TabsNeeded := 0; repeat nextTab := TabFrom(StartColumn); if nextTab <= TargetColumn then begin Inc(TabsNeeded); StartColumn := nextTab; end; until nextTab > TargetColumn; SpacesNeeded := TargetColumn - StartColumn; end; function TJclTabSet.Optimize(const S: string): string; begin Result := Optimize(S, StartColumn); end; function TJclTabSet.Optimize(const S: string; Column: Integer): string; var sb: TStringBuilder; head: PChar; cur: PChar; tgt: Integer; procedure AppendOptimalWhiteSpace(Target: Integer); var tabCount: Integer; spaceCount: Integer; begin if cur > head then begin OptimalFillInfo(Column, Target, tabCount, spaceCount); if tabCount > 0 then sb.Append(#9, tabCount); if spaceCount > 0 then sb.Append(' ', spaceCount); end; end; begin if Column < StartColumn then raise ArgumentOutOfRangeException.Create('Column'); sb := TStringBuilder.Create(Length(S)); try cur := PChar(s); while cur^ <> #0 do begin // locate first whitespace character head := cur; while (cur^ <> #0) and not CharIsWhiteSpace(cur^) do Inc(cur); // output non whitespace characters if cur > head then sb.Append(head, 0, cur - head); // advance column Inc(Column, cur - head); // initialize target column indexer tgt := Column; // locate end of whitespace sequence while CharIsWhiteSpace(cur^) do begin if CharIsReturn(cur^) then begin // append optimized whitespace sequence... AppendOptimalWhiteSpace(tgt); // ...set the column back to the start of the line... Column := StartColumn; // ...reset target column indexer... tgt := Column; // ...add the line break character... sb.Append(cur^); end else if cur^ = #9 then tgt := TabFrom(tgt) // expand the tab else Inc(tgt); // a normal whitespace; taking up 1 column Inc(cur); end; AppendOptimalWhiteSpace(tgt); // append optimized whitespace sequence... Column := tgt; // ...and memorize the column for the next iteration end; Result := sb.ToString; // convert result to a string finally sb.Free; end; end; procedure TJclTabSet.RemoveAt(Index: Integer); begin if Self <> nil then begin MoveArray(FStops, Succ(Index), Index, High(FStops) - Index); SetLength(FStops, High(FStops)); CalcRealWidth; end else raise NullReferenceException.Create; end; procedure TJclTabSet.SetStops(Index, Value: Integer); var temp: Integer; begin if Self <> nil then begin if (Index < 0) or (Index >= Length(FStops)) then begin {$IFDEF CLR} raise ArgumentOutOfRangeException.Create; {$ELSE} raise ArgumentOutOfRangeException.CreateRes(@RsArgumentOutOfRange); {$ENDIF CLR} end else begin temp := FindStop(Value); if temp < 0 then begin // remove existing tab stop... RemoveAt(Index); // now add the new tab stop Add(Value); end else if temp <> Index then begin // new tab stop already present at another index {$IFDEF CLR} raise EJclStringError.Create(RsTabs_DuplicatesNotAllowed); {$ELSE} raise EJclStringError.CreateRes(@RsTabs_DuplicatesNotAllowed); {$ENDIF} end; end; end else raise NullReferenceException.Create; end; procedure TJclTabSet.SetTabWidth(Value: Integer); begin if Self <> nil then begin FWidth := Value; CalcRealWidth; end else raise NullReferenceException.Create; end; procedure TJclTabSet.SetZeroBased(Value: Boolean); var shift: Integer; idx: Integer; begin if Self <> nil then begin if Value <> FZeroBased then begin FZeroBased := Value; if Value then shift := -1 else shift := 1; for idx := 0 to High(FStops) do FStops[idx] := FStops[idx] + shift; end; end else raise NullReferenceException.Create; end; function TJclTabSet.InternalTabStops: TDynIntegerArray; begin if Self <> nil then Result := FStops else Result := nil; end; function TJclTabSet.InternalTabWidth: Integer; begin if Self <> nil then Result := FRealWidth else Result := 2; end; function TJclTabSet.StartColumn: Integer; begin if GetZeroBased then Result := 0 else Result := 1; end; function TJclTabSet.TabFrom(Column: Integer): Integer; begin if Column < StartColumn then raise ArgumentOutOfRangeException.Create('Column'); Result := FindStop(Column); if Result < 0 then Result := not Result else Inc(Result); if Result >= GetCount then begin if GetCount > 0 then Result := FStops[High(FStops)] else Result := StartColumn; while Result <= Column do Inc(Result, ActualTabWidth); end else Result := FStops[Result]; end; function TJclTabSet.ToString: string; begin Result := ToString(TabSetFormatting_Full); end; function TJclTabSet.ToString(FormattingOptions: Integer): string; var sb: TStringBuilder; idx: Integer; function WantBrackets: Boolean; begin Result := (TabSetFormatting_SurroundStopsWithBrackets and FormattingOptions) <> 0; end; function EmptyBrackets: Boolean; begin Result := (TabSetFormatting_EmptyBracketsIfNoStops and FormattingOptions) <> 0; end; function IncludeAutoWidth: Boolean; begin Result := (TabSetFormatting_AutoTabWidth and FormattingOptions) <> 0; end; function IncludeTabWidth: Boolean; begin Result := (TabSetFormatting_NoTabWidth and FormattingOptions) = 0; end; function IncludeStops: Boolean; begin Result := (TabSetFormatting_NoTabStops and FormattingOptions) = 0; end; begin sb := TStringBuilder.Create; try // output the fixed tabulation positions if requested... if IncludeStops then begin // output each individual tabulation position for idx := 0 to GetCount - 1 do begin sb.Append(TabStops[idx]); sb.Append(','); end; // remove the final comma if any tabulation positions where outputted if sb.Length <> 0 then sb.Remove(sb.Length - 1, 1); // bracket the tabulation positions if requested if WantBrackets and (EmptyBrackets or (sb.Length > 0)) then begin sb.Insert(0, '['); sb.Append(']'); end; end; // output the tab width if requested.... if IncludeTabWidth and (IncludeAutoWidth or (TabWidth > 0)) then begin // separate the tab width from any outputted tabulation positions with a whitespace if sb.Length > 0 then sb.Append(' '); // flag tab width sb.Append('+'); // finally, output the tab width sb.Append(ActualTabWidth); end; // flag zero-based tabset by outputting a 0 (zero) as the first character. if ZeroBased then sb.Insert(0, string('0 ')); Result := StrTrimCharRight(sb.ToString, ' '); finally sb.Free; end; end; function TJclTabSet.UpdatePosition(const S: string): Integer; var lines: Integer; begin Result := StartColumn; UpdatePosition(S, Result, lines); end; function TJclTabSet.UpdatePosition(const S: string; Column: Integer): Integer; var lines: Integer; begin if Column < StartColumn then raise ArgumentOutOfRangeException.Create('Column'); Result := Column; UpdatePosition(S, Result, lines); end; function TJclTabSet.UpdatePosition(const S: string; var Column, Line: Integer): Integer; var prevChar: Char; cur: PChar; begin if Column < StartColumn then raise ArgumentOutOfRangeException.Create('Column'); // initialize loop cur := PChar(S); // iterate until end of string (the Null-character) while cur^ <> #0 do begin // check for line-breaking characters if CharIsReturn(cur^) then begin // Column moves back all the way to the left Column := StartColumn; // If this is the first line-break character or the same line-break character, increment the Line parameter Inc(Line); // check if it's the first of a two-character line-break prevChar := cur^; Inc(cur); // if it isn't a two-character line-break, undo the previous advancement if (cur^ = prevChar) or not CharIsReturn(cur^) then Dec(cur); end else // check for tab character and expand it if cur^ = #9 then Column := TabFrom(Column) else // a normal character; increment column Inc(Column); // advance pointer Inc(cur); end; // set the result to the newly calculated column Result := Column; end; //=== { NullReferenceException } ============================================= constructor NullReferenceException.Create; begin CreateRes(@RsArg_NullReferenceException); end; {$ENDIF ~CLR} function CompareNatural(const S1, S2: string; CaseInsensitive: Boolean): Integer; var Cur1, Len1, Cur2, Len2: Integer; procedure NumberCompare; var IsReallyNumber: Boolean; FirstDiffBreaks: Boolean; Val1, Val2: Integer; 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(S1[Cur1]); Val2 := StrToInt(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 {$IFDEF CLR} Result := System.String.Compare(S1.Substring(Cur1 - 1),S2.Substring(Cur2 - 1),CaseInsensitive); {$ELSE ~CLR} if CaseInsensitive then Result := StrLIComp(PChar(@S1[Cur1]), PChar(@S2[Cur2]), 1) else Result := StrLComp(PChar(@S1[Cur1]), PChar(@S2[Cur2]), 1); {$ENDIF ~CLR} Inc(Cur1); Inc(Cur2); end; end; end; function CompareNaturalStr(const S1, S2: string): Integer; overload; begin Result := CompareNatural(S1, S2, False); end; function CompareNaturalText(const S1, S2: string): Integer; overload; begin Result := CompareNatural(S1, S2, True); end; {$IFDEF CLR} {$IFDEF UNITVERSIONING} initialization RegisterUnitVersion(HInstance, UnitVersioning); {$ENDIF UNITVERSIONING} {$ELSE} initialization LoadCharTypes; // this table first LoadCaseMap; // or this function does not work {$IFDEF UNITVERSIONING} RegisterUnitVersion(HInstance, UnitVersioning); {$ENDIF UNITVERSIONING} {$ENDIF CLR} {$IFDEF UNITVERSIONING} finalization UnregisterUnitVersion(HInstance); {$ENDIF UNITVERSIONING} end.