{**************************************************************************************************}
{ }
{ 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 Developers of the Original Code are documented in the accompanying help file }
{ JCLHELP.hlp. Portions created by these individuals are Copyright (C) of these individuals. }
{ }
{**************************************************************************************************}
{ }
{ Various character and string routines (searching, testing and transforming) }
{ }
{ Unit owner: Azret Botash }
{ }
{**************************************************************************************************}
// $Id: JclStrings.pas,v 1.2 2004/04/14 21:55:07 druid Exp $
// rr 25 feb 2003 Linux port (implemented LoadCharTypes & LoadCaseMap)
// mvb 20 jan 2002 added StrIToStrings to interface section
// mvb 20 jan 2002 added AllowEmptyString parameter to StringsToStr function
// mvb 20 jan 2002 added AddStringToStrings() by Jeff
// - StrIToStrings default parameter now true
// - StrToStrings default parameter now true
// - Rewrote StrSmartCase to fix a bug.
// - Fixed a bug in StrIsAlphaNumUnderscore
// - Fixed a bug in StrIsSubset
// - Simplified StrLower
// - Fixed a bug in StrRepeatLength
// - Fixed a bug in StrLastPos
// - Added function StrTrimCharsRight (Leonard Wennekers)
// - Added function StrTrimCharsLeft (Leonard Wennekers)
// - Added StrNormIndex function (Alexander Radchenko)
// - Changed Assert in StrTokens/ to If List <> nil
// - Deleted an commented out version of StrReplace. If anyone ever want to finish the old
// version please go the archive version 0.39
// - Modified StrFillChar a little bit (added an if for count > 0)
// - StrCharPosLower (Jean-Fabien Connault)
// - StrCharPosUpper (Jean-Fabien Connault)
// - Changed to 100 chars per line style
// - Note to Marcel: Have a look at StrToStrings and StrItoStrings. They are untested but
// should work more or less equal to the BreakApart functions by JFC.
// - Changed StrNPos for special case
// - Changed StrIPos for special case
// - Fixed a bug in CharPos : didn'T work if index = length(s)
// - Fixed a bug in CharIPos : didn'T work if index = length(s)
unit JclStrings;
{$I jcl.inc}
interface
uses
Classes, SysUtils,
JclBase;
// Character constants and sets
const
// Misc. often used character definitions
AnsiNull = AnsiChar(#0);
AnsiSoh = AnsiChar(#1);
AnsiStx = AnsiChar(#2);
AnsiEtx = AnsiChar(#3);
AnsiEot = AnsiChar(#4);
AnsiEnq = AnsiChar(#5);
AnsiAck = AnsiChar(#6);
AnsiBell = AnsiChar(#7);
AnsiBackspace = AnsiChar(#8);
AnsiTab = AnsiChar(#9);
AnsiLineFeed = AnsiChar(#10);
AnsiVerticalTab = AnsiChar(#11);
AnsiFormFeed = AnsiChar(#12);
AnsiCarriageReturn = AnsiChar(#13);
AnsiCrLf = AnsiString(#13#10);
AnsiSo = AnsiChar(#14);
AnsiSi = AnsiChar(#15);
AnsiDle = AnsiChar(#16);
AnsiDc1 = AnsiChar(#17);
AnsiDc2 = AnsiChar(#18);
AnsiDc3 = AnsiChar(#19);
AnsiDc4 = AnsiChar(#20);
AnsiNak = AnsiChar(#21);
AnsiSyn = AnsiChar(#22);
AnsiEtb = AnsiChar(#23);
AnsiCan = AnsiChar(#24);
AnsiEm = AnsiChar(#25);
AnsiEndOfFile = AnsiChar(#26);
AnsiEscape = AnsiChar(#27);
AnsiFs = AnsiChar(#28);
AnsiGs = AnsiChar(#29);
AnsiRs = AnsiChar(#30);
AnsiUs = AnsiChar(#31);
AnsiSpace = AnsiChar(' ');
AnsiComma = AnsiChar(',');
AnsiBackslash = AnsiChar('\');
AnsiForwardSlash = AnsiChar('/');
AnsiDoubleQuote = AnsiChar('"');
AnsiSingleQuote = AnsiChar('''');
{$IFDEF MSWINDOWS}
AnsiLineBreak = AnsiCrLf;
{$ENDIF}
{$IFDEF UNIX}
AnsiLineBreak = AnsiLineFeed;
{$ENDIF}
// Misc. character sets
AnsiSigns = ['-', '+'];
AnsiWhiteSpace = [AnsiTab, AnsiLineFeed, AnsiVerticalTab, AnsiFormFeed,
AnsiCarriageReturn, AnsiSpace];
AnsiDecDigits = ['0'..'9'];
AnsiOctDigits = ['0'..'7'];
AnsiHexDigits = ['0'..'9', 'A'..'F', 'a'..'f'];
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}
{$ENDIF MSWINDOWS}
//--------------------------------------------------------------------------------------------------
// String Test Routines
//--------------------------------------------------------------------------------------------------
function StrIsAlpha(const S: AnsiString): Boolean;
function StrIsAlphaNum(const S: AnsiString): Boolean;
function StrIsAlphaNumUnderscore(const S: AnsiString): Boolean;
function StrContainsChars(const S: AnsiString; Chars: TSysCharSet; CheckAll: Boolean): Boolean;
function StrIsDigit(const S: AnsiString): Boolean;
function StrIsNumber(const S: AnsiString): Boolean;
function StrIsSubset(const S: AnsiString; const ValidChars: TSysCharSet): Boolean;
function StrSame(const S1, S2: AnsiString): Boolean;
//--------------------------------------------------------------------------------------------------
// String Transformation Routines
//--------------------------------------------------------------------------------------------------
function StrCenter(const S: AnsiString; L: Integer; C: AnsiChar = ' '): AnsiString;
function StrCharPosLower(const S: AnsiString; CharPos: Integer): AnsiString;
function StrCharPosUpper(const S: AnsiString; CharPos: Integer): AnsiString;
function StrDoubleQuote(const S: AnsiString): AnsiString;
function StrEnsureNoPrefix(const Prefix, Text: AnsiString): AnsiString;
function StrEnsureNoSuffix(const Suffix, Text: AnsiString): AnsiString;
function StrEnsurePrefix(const Prefix, Text: AnsiString): AnsiString;
function StrEnsureSuffix(const Suffix, Text: AnsiString): AnsiString;
function StrEscapedToString(const S: AnsiString): AnsiString;
function StrLower(const S: AnsiString): AnsiString;
procedure StrLowerInPlace(var S: AnsiString);
procedure StrLowerBuff(S: PAnsiChar);
procedure StrMove(var Dest: AnsiString; const Source: AnsiString; const ToIndex,
FromIndex, Count: Integer);
function StrPadLeft(const S: AnsiString; Len: Integer; C: AnsiChar = AnsiSpace ): AnsiString;
function StrPadRight(const S: AnsiString; Len: Integer; C: AnsiChar = AnsiSpace ): AnsiString;
function StrProper(const S: AnsiString): AnsiString;
procedure StrProperBuff(S: PAnsiChar);
function StrQuote(const S: AnsiString; C: AnsiChar): AnsiString;
function StrRemoveChars(const S: AnsiString; const Chars: TSysCharSet): AnsiString;
function StrKeepChars(const S: AnsiString; const Chars: TSysCharSet): AnsiString;
procedure StrReplace(var S: AnsiString; const Search, Replace: AnsiString; Flags: TReplaceFlags = []);
function StrReplaceChar(const S: AnsiString; const Source, Replace: Char): AnsiString;
function StrReplaceChars(const S: AnsiString; const Chars: TSysCharSet; Replace: Char): AnsiString;
function StrReplaceButChars(const S: AnsiString; const Chars: TSysCharSet; Replace: Char): AnsiString;
function StrRepeat(const S: AnsiString; Count: Integer): AnsiString;
function StrRepeatLength(const S: AnsiString; Const L: Integer): AnsiString;
function StrReverse(const S: AnsiString): AnsiString;
procedure StrReverseInPlace(var S: AnsiString);
function StrSingleQuote(const S: AnsiString): AnsiString;
function StrSmartCase(const S: AnsiString; Delimiters: TSysCharSet): AnsiString;
function StrStringToEscaped(const S: AnsiString): AnsiString;
function StrStripNonNumberChars(const S: AnsiString): AnsiString;
function StrToHex(const Source: AnsiString): AnsiString;
function StrTrimCharLeft(const S: AnsiString; C: AnsiChar): AnsiString;
function StrTrimCharsLeft(const S: AnsiString; const Chars: TSysCharSet): AnsiString;
function StrTrimCharRight(const S: AnsiString; C: AnsiChar): AnsiString;
function StrTrimCharsRight(const S: AnsiString; const Chars: TSysCharSet): AnsiString;
function StrTrimQuotes(const S: AnsiString): AnsiString;
function StrUpper(const S: AnsiString): AnsiString;
procedure StrUpperInPlace(var S: AnsiString);
procedure StrUpperBuff(S: PAnsiChar);
{$IFDEF WIN32}
function StrOemToAnsi(const S: AnsiString): AnsiString;
function StrAnsiToOem(const S: AnsiString): AnsiString;
{$ENDIF WIN32}
//--------------------------------------------------------------------------------------------------
// String Management
//--------------------------------------------------------------------------------------------------
procedure StrAddRef(var S: AnsiString);
function StrAllocSize(const S: AnsiString): Longint;
procedure StrDecRef(var S: AnsiString);
function StrLen(S: PChar): Integer;
function StrLength(const S: AnsiString): Longint;
function StrRefCount(const S: AnsiString): Longint;
procedure StrResetLength(var S: AnsiString);
//--------------------------------------------------------------------------------------------------
// String Search and Replace Routines
//--------------------------------------------------------------------------------------------------
function StrCharCount(const S: AnsiString; C: AnsiChar): Integer;
function StrCharsCount(const S: AnsiString; Chars: TSysCharSet): Integer;
function StrStrCount(const S, SubS: AnsiString): Integer;
function StrCompare(const S1, S2: AnsiString): Integer;
function StrCompareRange(const S1, S2: AnsiString; const Index, Count: Integer): Integer;
function StrFillChar(const C: AnsiChar; const Count: Integer): AnsiString;
function StrFind(const Substr, S: AnsiString; const Index: Integer = 1): Integer;
function StrHasPrefix(const S: AnsiString; const Prefixes: array of string): Boolean;
function StrIndex(const S: AnsiString; const List: array of string): Integer;
function StrILastPos(const SubStr, S: AnsiString): Integer;
function StrIPos(const SubStr, S: AnsiString): Integer;
function StrIsOneOf(const S: AnsiString; const List: array of AnsiString): Boolean;
function StrLastPos(const SubStr, S: AnsiString): Integer;
function StrMatch(const Substr, S: AnsiString; const Index: Integer = 1): Integer;
function StrMatches(const Substr, S: AnsiString; const Index: Integer = 1): Boolean;
function StrNIPos(const S, SubStr: AnsiString; N: Integer): Integer;
function StrNPos(const S, SubStr: AnsiString; N: Integer): Integer;
function StrPrefixIndex(const S: AnsiString; const Prefixes: array of string): Integer;
function StrSearch(const Substr, S: AnsiString; const Index: Integer = 1): Integer;
//--------------------------------------------------------------------------------------------------
// String Extraction
//--------------------------------------------------------------------------------------------------
function StrAfter(const SubStr, S: AnsiString): AnsiString;
function StrBefore(const SubStr, S: AnsiString): AnsiString;
function StrBetween(const S: AnsiString; const Start, Stop: AnsiChar): AnsiString;
function StrChopRight(const S: AnsiString; N: Integer): AnsiString;
function StrLeft(const S: AnsiString; Count: Integer): AnsiString;
function StrMid(const S: AnsiString; Start, Count: Integer): AnsiString;
function StrRestOf(const S: AnsiString; N: Integer): AnsiString;
function StrRight(const S: AnsiString; Count: Integer): AnsiString;
//--------------------------------------------------------------------------------------------------
// Character Test Routines
//--------------------------------------------------------------------------------------------------
function CharEqualNoCase(const C1, C2: AnsiChar): Boolean;
function CharIsAlpha(const C: AnsiChar): Boolean;
function CharIsAlphaNum(const C: AnsiChar): Boolean;
function CharIsBlank(const C: AnsiChar): Boolean;
function CharIsControl(const C: AnsiChar): Boolean;
function CharIsDelete(const C: AnsiChar): Boolean;
function CharIsDigit(const C: AnsiChar): Boolean;
function CharIsLower(const C: AnsiChar): Boolean;
function CharIsNumber(const C: AnsiChar): Boolean;
function CharIsPrintable(const C: AnsiChar): Boolean;
function CharIsPunctuation(const C: AnsiChar): Boolean;
function CharIsReturn(const C: AnsiChar): Boolean;
function CharIsSpace(const C: AnsiChar): Boolean;
function CharIsUpper(const C: AnsiChar): Boolean;
function CharIsWhiteSpace(const C: AnsiChar): Boolean;
function CharType(const C: AnsiChar): Word;
//--------------------------------------------------------------------------------------------------
// Character Transformation Routines
//--------------------------------------------------------------------------------------------------
function CharHex(const C: AnsiChar): Byte;
function CharLower(const C: AnsiChar): AnsiChar;
function CharUpper(const C: AnsiChar): AnsiChar;
function CharToggleCase(const C: AnsiChar): AnsiChar;
//--------------------------------------------------------------------------------------------------
// Character Search and Replace
//--------------------------------------------------------------------------------------------------
function CharPos(const S: AnsiString; const C: AnsiChar; const Index: Integer = 1): Integer;
function CharIPos(const S: AnsiString; const C: AnsiChar; const Index: Integer = 1 ): Integer;
function CharReplace(var S: AnsiString; const Search, Replace: AnsiChar): Integer;
//--------------------------------------------------------------------------------------------------
// 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
//--------------------------------------------------------------------------------------------------
function StringsToMultiSz(var Dest: PChar; const Source: TStrings): PChar;
procedure MultiSzToStrings(const Dest: TStrings; const Source: PChar);
procedure FreeMultiSz(var Dest: PChar);
//--------------------------------------------------------------------------------------------------
// TStrings Manipulation
//--------------------------------------------------------------------------------------------------
procedure StrIToStrings(S, Sep: AnsiString; const List: TStrings; const AllowEmptyString: Boolean = True);
procedure StrToStrings(S, Sep: AnsiString; const List: TStrings; const AllowEmptyString: Boolean = True);
function StringsToStr(const List: TStrings; const Sep: AnsiString; const AllowEmptyString: Boolean = True): AnsiString;
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
//--------------------------------------------------------------------------------------------------
function BooleanToStr(B: Boolean): AnsiString;
function FileToString(const FileName: AnsiString): AnsiString;
procedure StringToFile(const FileName, Contents: AnsiString);
function StrToken(var S: AnsiString; Separator: AnsiChar): AnsiString;
procedure StrTokens(const S: AnsiString; const List: TStrings);
procedure StrTokenToStrings(S: AnsiString; Separator: AnsiChar; const List: TStrings);
function StrWord(var S: PAnsiChar; out Word: AnsiString): Boolean;
function StrToFloatSafe(const S: AnsiString): Float;
function StrToIntSafe(const S: AnsiString): Integer;
procedure StrNormIndex(const StrLen: integer; var Index: integer; var Count: integer); overload;
{$IFNDEF COMPILER5_UP}
//--------------------------------------------------------------------------------------------------
// Backward compatibility
//--------------------------------------------------------------------------------------------------
function AnsiSameText(const S1, S2: AnsiString): Boolean;
{$ENDIF COMPILER5_UP}
//--------------------------------------------------------------------------------------------------
// Exceptions
//--------------------------------------------------------------------------------------------------
type
EJclStringError = EJclError;
implementation
uses
{$IFDEF MSWINDOWS}
Windows,
{$ENDIF MSWINDOWS}
{$IFDEF LINUX}
Libc,
{$ENDIF LINUX}
JclLogic,
JclResources;
//==================================================================================================
// Internal
//==================================================================================================
type
TAnsiStrRec = packed record
AllocSize: Longint;
RefCount: Longint;
Length: Longint;
end;
const
AnsiStrRecSize = SizeOf(TAnsiStrRec); // size of the AnsiString header rec
AnsiCharCount = Ord(High(AnsiChar)) + 1; // # of chars in one set
AnsiLoOffset = AnsiCharCount * 0; // offset to lower case chars
AnsiUpOffset = AnsiCharCount * 1; // offset to upper case chars
AnsiReOffset = AnsiCharCount * 2; // offset to reverse case chars
AnsiAlOffset = 12; // offset to AllocSize in StrRec
AnsiRfOffset = 8; // offset to RefCount in StrRec
AnsiLnOffset = 4; // offset to Length in StrRec
AnsiCaseMapSize = AnsiCharCount * 3; // # of chars is a table
var
AnsiCaseMap: array [0..AnsiCaseMapSize - 1] of AnsiChar; // case mappings
AnsiCaseMapReady: Boolean = False; // true if case map exists
AnsiCharTypes: array [AnsiChar] of Word;
//--------------------------------------------------------------------------------------------------
procedure LoadCharTypes;
var
CurrChar: AnsiChar;
CurrType: Word;
begin
for CurrChar := Low(AnsiChar) to High(AnsiChar) do
begin
{$IFDEF MSWINDOWS}
GetStringTypeExA(LOCALE_USER_DEFAULT, CT_CTYPE1, @CurrChar, SizeOf(AnsiChar), CurrType);
{$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;
{$ENDIF LINUX}
AnsiCharTypes[CurrChar] := CurrType;
end;
end;
//--------------------------------------------------------------------------------------------------
procedure LoadCaseMap;
var
CurrChar, UpCaseChar, LoCaseChar, ReCaseChar: AnsiChar;
begin
if not AnsiCaseMapReady then
begin
for CurrChar := Low(AnsiChar) to High(AnsiChar) do
begin
{$IFDEF MSWINDOWS}
LoCaseChar := CurrChar;
UpCaseChar := CurrChar;
Windows.CharLowerBuff(@LoCaseChar, 1);
Windows.CharUpperBuff(@UpCaseChar, 1);
{$ENDIF MSWINDOWS}
{$IFDEF LINUX}
LoCaseChar := AnsiChar(tolower(Byte(CurrChar)));
UpCaseChar := AnsiChar(toupper(Byte(CurrChar)));
{$ENDIF LINUX}
if CharIsUpper(CurrChar) then
ReCaseChar := LoCaseChar
else
if CharIsLower(CurrChar) then
ReCaseChar := UpCaseChar
else
ReCaseChar := CurrChar;
AnsiCaseMap[Ord(CurrChar) + AnsiLoOffset] := LoCaseChar;
AnsiCaseMap[Ord(CurrChar) + AnsiUpOffset] := UpCaseChar;
AnsiCaseMap[Ord(CurrChar) + AnsiReOffset] := ReCaseChar;
end;
AnsiCaseMapReady := True;
end;
end;
//--------------------------------------------------------------------------------------------------
// Uppercases or Lowercases a give AnsiString depending on the
// passed offset. (UpOffset or LoOffset)
procedure StrCase(var Str: AnsiString; const Offset: Integer); register; assembler;
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 - AnsiStrRecSize].TAnsiStrRec.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][AnsiCaseMap + EDX]
{$ELSE}
LEA EBX, [AnsiCaseMap + EDX]
{$ENDIF}
MOV ESI, EAX
XOR EDX, EDX
XOR EAX, EAX
@@NextChar:
// get current char from the AnsiString
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 AnsiString 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;
//--------------------------------------------------------------------------------------------------
// Internal utility function
// Uppercases or Lowercases a give null terminated string depending on the
// passed offset. (UpOffset or LoOffset)
procedure StrCaseBuff(S: PAnsiChar; const Offset: Integer); register; assembler;
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][AnsiCaseMap + EDX]
{$ELSE}
LEA EBX, [AnsiCaseMap + EDX]
{$ENDIF}
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;
//==================================================================================================
// String Test Routines
//==================================================================================================
function StrIsAlpha(const S: AnsiString): 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: AnsiString): 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 StrContainsChars(const S: AnsiString; Chars: TSysCharSet; CheckAll: Boolean): Boolean;
var
I: Integer;
C: Char;
begin
Result := Chars = [];
if not Result then
begin
if CheckAll then
begin
for I := 1 to Length(S) do
begin
C := S[I];
if C in Chars then
begin
Chars := Chars - [C];
if Chars = [] then
Break;
end;
end;
Result := (Chars = []);
end
else
begin
for I := 1 to Length(S) do
if S[I] in Chars then
begin
Result := True;
Break;
end;
end;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrIsAlphaNumUnderscore(const S: AnsiString): Boolean;
var
I: Integer;
C: AnsiChar;
begin
for i := 1 to Length(s) do
begin
C := S[I];
if not (CharIsAlphaNum(C) or (C = '_')) then
begin
Result := False;
Exit;
end;
end;
Result := True and (Length(S) > 0);
end;
//--------------------------------------------------------------------------------------------------
function StrIsDigit(const S: AnsiString): Boolean;
var
I: 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 StrIsNumber(const S: AnsiString): Boolean;
var
I: Integer;
begin
Result := S <> '';
for I := 1 to Length(S) do
begin
if not CharIsNumber(S[I]) then
begin
Result := False;
Exit;
end;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrIsSubset(const S: AnsiString; const ValidChars: TSysCharSet): Boolean;
var
I: Integer;
begin
for I := 1 to Length(S) do
begin
if not (S[I] in ValidChars) then
begin
Result := False;
Exit;
end;
end;
Result := True and (Length(S) > 0);
end;
//--------------------------------------------------------------------------------------------------
function StrSame(const S1, S2: AnsiString): Boolean;
begin
Result := StrCompare(S1, S2) = 0;
end;
//==================================================================================================
// String Transformation Routines
//==================================================================================================
function StrCenter(const S: AnsiString; L: Integer; C: AnsiChar = ' '): AnsiString;
begin
if Length(S) < L then
begin
Result := StringOfChar(C, (L - Length(S)) div 2) + S;
Result := Result + StringOfChar(C, L - Length(Result));
end
else
Result := S;
end;
//--------------------------------------------------------------------------------------------------
function StrCharPosLower(const S: AnsiString; CharPos: Integer): AnsiString;
begin
Result := S;
if (CharPos > 0) and (CharPos <= Length(S)) then
Result[CharPos] := JclStrings.CharLower(Result[CharPos]);
end;
//--------------------------------------------------------------------------------------------------
function StrCharPosUpper(const S: AnsiString; CharPos: Integer): AnsiString;
begin
Result := S;
if (CharPos > 0) and (CharPos <= Length(S)) then
Result[CharPos] := CharUpper(Result[CharPos]);
end;
//--------------------------------------------------------------------------------------------------
function StrDoubleQuote(const S: AnsiString): AnsiString;
begin
Result := AnsiDoubleQuote + S + AnsiDoubleQuote;
end;
//--------------------------------------------------------------------------------------------------
function StrEnsureNoPrefix(const Prefix, Text: AnsiString): AnsiString;
var
PrefixLen : Integer;
begin
PrefixLen := Length(Prefix);
if Copy(Text, 1, PrefixLen) = Prefix then
Result := Copy(Text, PrefixLen + 1, Length(Text))
else
Result := Text;
end;
//--------------------------------------------------------------------------------------------------
{ TODOc Author: Olivier Sannier}
function StrEnsureNoSuffix(const Suffix, Text: AnsiString): AnsiString;
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;
//--------------------------------------------------------------------------------------------------
{ TODOc Author: Olivier Sannier}
function StrEnsurePrefix(const Prefix, Text: AnsiString): AnsiString;
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: AnsiString): AnsiString;
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: AnsiString): AnsiString;
var
I, Len, N, Val: Integer;
procedure HandleHexEscapeSeq;
const
HexDigits = AnsiString('0123456789abcdefABCDEF');
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 > 255 then
raise EJclStringError.CreateResRec(@RsNumericConstantTooLarge);
Result := Result + Chr(Val);
end;
end;
procedure HandleOctEscapeSeq;
const
OctDigits = AnsiString('01234567');
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 > 255 then
raise EJclStringError.CreateResRec(@RsNumericConstantTooLarge);
Result := Result + Chr(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 + AnsiBell;
'b':
Result := Result + AnsiBackspace;
'f':
Result := Result + AnsiFormFeed;
'n':
Result := Result + AnsiLineFeed;
'r':
Result := Result + AnsiCarriageReturn;
't':
Result := Result + AnsiTab;
'v':
Result := Result + AnsiVerticalTab;
'\':
Result := Result + '\';
'"':
Result := Result + '"';
'''':
Result := Result + ''''; // Optionally escaped
'?':
Result := Result + '?'; // Optionally escaped
'x':
if I < Len then
// Start of hex escape sequence
HandleHexEscapeSeq
else
// '\x' at end of AnsiString 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: AnsiString): AnsiString;
begin
Result := S;
StrLowerInPlace(Result);
end;
//--------------------------------------------------------------------------------------------------
procedure StrLowerInPlace(var S: AnsiString); assembler;
{$IFDEF PIC}
begin
StrCase(S, AnsiLoOffset);
end;
{$ELSE}
asm
// StrCase(S, AnsiLoOffset)
XOR EDX, EDX // MOV EDX, LoOffset
JMP StrCase
end;
{$ENDIF}
//--------------------------------------------------------------------------------------------------
procedure StrLowerBuff(S: PAnsiChar); assembler;
{$IFDEF PIC}
begin
StrCaseBuff(S, AnsiLoOffset);
end;
{$ELSE}
asm
// StrCaseBuff(S, LoOffset)
XOR EDX, EDX // MOV EDX, LoOffset
JMP StrCaseBuff
end;
{$ENDIF}
//--------------------------------------------------------------------------------------------------
procedure StrMove(var Dest: AnsiString; const Source: AnsiString;
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 -orr : Is failure without notice the proper thing to do here? }
Exit;
// Move
Move(Source[FromIndex], Dest[ToIndex], Count);
end;
//--------------------------------------------------------------------------------------------------
function StrPadLeft(const S: AnsiString; Len: Integer; C: AnsiChar): AnsiString;
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: AnsiString; Len: Integer; C: AnsiChar): AnsiString;
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: AnsiString): AnsiString;
begin
Result := S;
StrProperBuff(PChar(Result));
end;
//--------------------------------------------------------------------------------------------------
procedure StrProperBuff(S: PAnsiChar);
begin
if (S <> nil) and (S^ <> #0) then
begin
StrLowerBuff(S);
S^ := CharUpper(S^);
end;
end;
//--------------------------------------------------------------------------------------------------
function StrQuote(const S: AnsiString; C: AnsiChar): AnsiString;
var
L: 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: AnsiString; const Chars: TSysCharSet): AnsiString;
var
Source, Dest: PChar;
begin
SetLength(Result, Length(S));
UniqueString(Result);
Source := PChar(S);
Dest := PChar(Result);
while (Source <> nil) and (Source^ <> #0) do
begin
if not (Source^ in Chars) then
begin
Dest^ := Source^;
Inc(Dest);
end;
Inc(Source);
end;
SetLength(Result, (Longint(Dest) - Longint(PChar(Result))) div SizeOf(AnsiChar));
end;
//--------------------------------------------------------------------------------------------------
function StrKeepChars(const S: AnsiString; const Chars: TSysCharSet): AnsiString;
var
Source, Dest: PChar;
begin
SetLength(Result, Length(S));
UniqueString(Result);
Source := PChar(S);
Dest := PChar(Result);
while (Source <> nil) and (Source^ <> #0) do
begin
if Source^ in Chars then
begin
Dest^ := Source^;
Inc(Dest);
end;
Inc(Source);
end;
SetLength(Result, (Longint(Dest) - Longint(PChar(Result))) div SizeOf(AnsiChar));
end;
//--------------------------------------------------------------------------------------------------
function StrRepeat(const S: AnsiString; Count: Integer): AnsiString;
var
L: Integer;
P: PChar;
begin
L := Length(S);
SetLength(Result, Count * L);
P := Pointer(Result);
while Count > 0 do
begin
Move(Pointer(S)^, P^, L);
P := P + L;
Dec(Count);
end;
end;
//--------------------------------------------------------------------------------------------------
function StrRepeatLength(const S: AnsiString; Const L: Integer): AnsiString;
var
Count: Integer;
LenS: Integer;
P: PChar;
begin
Result := '';
LenS := Length(S);
if LenS > 0 then
begin
Count := L div LenS;
if Count * LenS < L then
Inc(Count);
SetLength(Result, Count * LenS);
P := Pointer(Result);
while Count> 0 do
begin
Move(Pointer(S)^, P^, LenS);
P := P + LenS;
Dec(Count);
end;
if Length(S) > L then
SetLength(Result, L);
end;
end;
//--------------------------------------------------------------------------------------------------
{ Temporary replacement of StrReplace. Basic algorithm is the same except that
it has been simplified a little. This version is a little slower than the one
below but at least it works. Someone will have to go over this sometime. }
// case insensitive StrReplace
procedure StrReplaceCS(var S: AnsiString; const Search, Replace: AnsiString; Flags: TReplaceFlags);
var
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 }
SearchLength, { length of search string }
ReplaceLength, { length of replace string }
ResultLength: Integer; { length of result string (maximum, worst-case scenario) }
C: Char; { first character of search string }
begin
//if (S = '') or (Search = '') then Exit;
{ avoid having to call Length() within the loop }
SearchLength := Length(Search);
ReplaceLength := Length(Replace);
{ initialize result string to maximum (worst case scenario) length }
if Length(Search) >= ReplaceLength then
ResultLength := Length(S)
else
ResultLength := ((Length(S) div Length(Search)) + 1) * Length(Replace);
SetLength(ResultStr, ResultLength);
{ get pointers to begin of source and result }
ResultPtr := PChar(ResultStr);
SourcePtr := PChar(S);
C := Search[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 }
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(Search) + 1;
while (SourceMatchPtr^ = SearchMatchPtr^) and (SearchMatchPtr^ <> #0) do
begin
Inc(SourceMatchPtr);
Inc(SearchMatchPtr);
end;
{ did we find a complete match? }
if SearchMatchPtr^ = #0 then
begin
if ReplaceLength > 0 then
{ append replace to result and move past the search string in source }
Move((@Replace[1])^, ResultPtr^, ReplaceLength);
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;
{ append null terminator, copy into S and reset the string length }
ResultPtr^ := #0;
S := ResultStr;
SetLength(S, StrLen(PChar(S)));
end;
// case insensitive StrReplace
procedure StrReplaceCI(var S: AnsiString; Search, Replace: AnsiString; Flags: TReplaceFlags);
var
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 }
SearchLength, { length of search string }
ReplaceLength, { length of replace string }
ResultLength: Integer; { length of result string (maximum, worst-case scenario) }
C: Char; { first character of search string }
begin
//if (S = '') or (Search = '') then Exit;
Search := AnsiUpperCase(Search);
{ avoid having to call Length() within the loop }
SearchLength := Length(Search);
ReplaceLength := Length(Replace);
{ initialize result string to maximum (worst case scenario) length }
if Length(Search) >= ReplaceLength then
ResultLength := Length(S)
else
ResultLength := ((Length(S) div Length(Search)) + 1) * Length(Replace);
SetLength(ResultStr, ResultLength);
{ get pointers to begin of source and result }
ResultPtr := PChar(ResultStr);
SourcePtr := PChar(S);
C := Search[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 }
while (CharUpper(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(Search) + 1;
while (CharUpper(SourceMatchPtr^) = SearchMatchPtr^) and (SearchMatchPtr^ <> #0) do
begin
Inc(SourceMatchPtr);
Inc(SearchMatchPtr);
end;
{ did we find a complete match? }
if SearchMatchPtr^ = #0 then
begin
if ReplaceLength > 0 then
{ append replace to result and move past the search string in source }
Move((@Replace[1])^, ResultPtr^, ReplaceLength);
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;
{ append null terminator, copy into S and reset the string length }
ResultPtr^ := #0;
S := ResultStr;
SetLength(S, StrLen(PChar(S)));
end;
procedure StrReplace(var S: AnsiString; const Search, Replace: AnsiString; Flags: TReplaceFlags);
begin
if Search = '' then
raise EJclStringError.CreateResRec(@RsBlankSearchString);
if S <> '' then
begin
if rfIgnoreCase in Flags then
StrReplaceCI(S, Search, Replace, Flags)
else
StrReplaceCS(S, Search, Replace, Flags);
end;
end;
//--------------------------------------------------------------------------------------------------
function StrReplaceChar(const S: AnsiString; const Source, Replace: Char): AnsiString;
var
I: Integer;
begin
Result := S;
for I := 1 to Length(S) do
if Result[I] = Source then
Result[I] := Replace;
end;
//--------------------------------------------------------------------------------------------------
function StrReplaceChars(const S: AnsiString; const Chars: TSysCharSet; Replace: Char): AnsiString;
var
I: Integer;
begin
Result := S;
for I := 1 to Length(S) do
if Result[I] in Chars then
Result[I] := Replace;
end;
//--------------------------------------------------------------------------------------------------
function StrReplaceButChars(const S: AnsiString; const Chars: TSysCharSet;
Replace: Char): AnsiString;
var
I: Integer;
begin
Result := S;
for I := 1 to Length(S) do
if not (Result[I] in Chars) then
Result[I] := Replace;
end;
//--------------------------------------------------------------------------------------------------
function StrReverse(const S: AnsiString): AnsiString;
begin
Result := S;
StrReverseInplace(Result);
end;
//--------------------------------------------------------------------------------------------------
procedure StrReverseInPlace(var S: AnsiString);
var
P1, P2: PChar;
C: AnsiChar;
begin
UniqueString(S);
P1 := PChar(S);
P2 := P1 + SizeOf(AnsiChar) * (Length(S) - 1);
while P1 < P2 do
begin
C := P1^;
P1^ := P2^;
P2^ := C;
Inc(P1);
Dec(P2);
end;
end;
//--------------------------------------------------------------------------------------------------
function StrSingleQuote(const S: AnsiString): AnsiString;
begin
Result := AnsiSingleQuote + S + AnsiSingleQuote;
end;
//--------------------------------------------------------------------------------------------------
function StrSmartCase(const S: AnsiString; Delimiters: TSysCharSet): AnsiString;
var
Source, Dest: PChar;
begin
Result := '';
if Delimiters = [] then
Include(Delimiters, AnsiSpace);
if S <> '' then
begin
Result := S;
UniqueString(Result);
Source := PChar(S);
Dest := PChar(Result);
Inc(Dest);
while Source^ <> #0 do
begin
if (Source^ in Delimiters) and (Dest^ <> #0) then
Dest^ := CharUpper(Dest^);
Inc(Dest);
Inc(Source);
end;
Result[1] := CharUpper(Result[1]);
end;
end;
//--------------------------------------------------------------------------------------------------
function StrStringToEscaped(const S: AnsiString): AnsiString;
var
I: Integer;
begin
Result := '';
for I := 1 to Length(S) do
begin
case S[I] of
AnsiBackspace:
Result := Result + '\b';
AnsiBell:
Result := Result + '\a';
AnsiCarriageReturn:
Result := Result + '\r';
AnsiFormFeed:
Result := Result + '\f';
AnsiLineFeed:
Result := Result + '\n';
AnsiTab:
Result := Result + '\t';
AnsiVerticalTab:
Result := Result + '\v';
'\':
Result := Result + '\\';
'"':
Result := Result + '\"';
else
// Characters < ' ' are escaped with hex sequence
if S[I] < #32 then
Result := Result + Format('\x%.2x',[Integer(S[I])])
else
Result := Result + S[I];
end;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrStripNonNumberChars(const S: AnsiString): AnsiString;
var
I: Integer;
C: AnsiChar;
begin
Result := '';
for I := 1 to Length(S) do
begin
C := S[I];
if CharIsNumber(C) then
Result := Result + C;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrToHex(const Source: AnsiString): AnsiString;
var
P: PChar;
C, L, N: Integer;
BL, BH: Byte;
S: AnsiString;
begin
Result := '';
if Source <> '' then
begin
S := Source;
L := Length(S);
if Odd(L) then
begin
S := '0' + S;
Inc(L);
end;
P := PChar(S);
SetLength(Result, L div 2);
C := 1;
N := 1;
while C <= L do
begin
BH := CharHex(P^);
Inc(P);
BL := CharHex(P^);
Inc(P);
Inc(C, 2);
if (BH = $FF) or (BL = $FF) then
begin
Result := '';
Exit;
end;
Byte(Result[N]) := Byte((BH shl 4) + BL);
Inc(N);
end;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrTrimCharLeft(const S: AnsiString; C: AnsiChar): AnsiString;
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: AnsiString; const Chars: TSysCharSet): AnsiString;
var
I, L: Integer;
begin
I := 1;
L := Length(S);
while (I <= L) and (S[I] in Chars) do Inc(I);
Result := Copy(S, I, L - I + 1);
end;
//--------------------------------------------------------------------------------------------------
function StrTrimCharsRight(const S: AnsiString; const Chars: TSysCharSet): AnsiString;
var
I: Integer;
begin
I := Length(S);
while (I >= 1) and (S[I] in Chars) do Dec(I);
Result := Copy(S, 1, I);
end;
//--------------------------------------------------------------------------------------------------
function StrTrimCharRight(const S: AnsiString; C: AnsiChar): AnsiString;
var
I: Integer;
begin
I := Length(S);
while (I >= 1) and (S[I] = C) do Dec(I);
Result := Copy(S, 1, I);
end;
//--------------------------------------------------------------------------------------------------
function StrTrimQuotes(const S: AnsiString): AnsiString;
var
First, Last: AnsiChar;
L: Integer;
begin
L := Length(S);
if L > 1 then
begin
First := S[1];
Last := S[L];
if (First = Last) and ((First = AnsiSingleQuote) or (First = AnsiDoubleQuote)) then
Result := Copy(S, 2, L - 2)
else
Result := S;
end
else
Result := S;
end;
//--------------------------------------------------------------------------------------------------
function StrUpper(const S: AnsiString): AnsiString;
begin
Result := S;
StrUpperInPlace(Result);
end;
//--------------------------------------------------------------------------------------------------
procedure StrUpperInPlace(var S: AnsiString); assembler;
{$IFDEF PIC}
begin
StrCase(S, AnsiUpOffset);
end;
{$ELSE}
asm
// StrCase(Str, AnsiUpOffset)
MOV EDX, AnsiUpOffset
JMP StrCase
end;
{$ENDIF}
//--------------------------------------------------------------------------------------------------
procedure StrUpperBuff(S: PAnsiChar); assembler;
{$IFDEF PIC}
begin
StrCaseBuff(S, AnsiUpOffset);
end;
{$ELSE}
asm
// StrCaseBuff(S, UpOffset)
MOV EDX, AnsiUpOffset
JMP StrCaseBuff
end;
{$ENDIF}
//--------------------------------------------------------------------------------------------------
{$IFDEF WIN32}
function StrOemToAnsi(const S: AnsiString): AnsiString;
begin
SetLength(Result, Length(S));
OemToAnsiBuff(@S[1], @Result[1], Length(S));
end;
{$ENDIF WIN32}
//--------------------------------------------------------------------------------------------------
{$IFDEF WIN32}
function StrAnsiToOem(const S: AnsiString): AnsiString;
begin
SetLength(Result, Length(S));
AnsiToOemBuff(@S[1], @Result[1], Length(S));
end;
{$ENDIF WIN32}
//--------------------------------------------------------------------------------------------------
//==================================================================================================
// String Management
//==================================================================================================
procedure StrAddRef(var S: AnsiString);
var
Foo: AnsiString;
begin
if StrRefCount(S) = -1 then
UniqueString(S)
else
begin
Foo := S;
Pointer(Foo) := nil;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrAllocSize(const S: AnsiString): Longint;
var
P: Pointer;
begin
Result := 0;
if Pointer(S) <> nil then
begin
P := Pointer(Integer(Pointer(S)) - AnsiRfOffset);
if Integer(P^) <> -1 then
begin
P := Pointer(Integer(Pointer(S)) - AnsiAlOffset);
Result := Integer(P^);
end;
end;
end;
//--------------------------------------------------------------------------------------------------
procedure StrDecRef(var S: AnsiString);
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 StrLen(S: PChar): Integer; assembler;
asm
TEST EAX, EAX
JZ @@EXIT
PUSH EBX
MOV EDX, EAX // save pointer
@L1: MOV EBX, [EAX] // read 4 bytes
ADD EAX, 4 // increment pointer
LEA ECX, [EBX-$01010101] // subtract 1 from each byte
NOT EBX // invert all bytes
AND ECX, EBX // and these two
AND ECX, $80808080 // test all sign bits
JZ @L1 // no zero bytes, continue loop
TEST ECX, $00008080 // test first two bytes
JZ @L2
SHL ECX, 16 // not in the first 2 bytes
SUB EAX, 2
@L2: SHL ECX, 9 // use carry flag to avoid a branch
SBB EAX, EDX // compute length
POP EBX
JZ @@EXIT // Az: SBB sets zero flag
DEC EAX // do not include null terminator
@@EXIT:
end;
//--------------------------------------------------------------------------------------------------
function StrLength(const S: AnsiString): Longint;
var
P: Pointer;
begin
Result := 0;
if Pointer(S) <> nil then
begin
P := Pointer(Integer(Pointer(S)) - AnsiLnOffset);
Result := Integer(P^) and (not $80000000 shr 1);
end;
end;
//--------------------------------------------------------------------------------------------------
function StrRefCount(const S: AnsiString): Longint;
var
P: Pointer;
begin
Result := 0;
if Pointer(S) <> nil then
begin
P := Pointer(Integer(Pointer(S)) - AnsiRfOffset);
Result := Integer(P^);
end;
end;
//--------------------------------------------------------------------------------------------------
procedure StrResetLength(var S: AnsiString);
begin
SetLength(S, StrLen(PChar(S)));
end;
//==================================================================================================
// String Search and Replaces Routines
//==================================================================================================
function StrCharCount(const S: AnsiString; C: AnsiChar): 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: AnsiString; Chars: TSysCharSet): Integer;
var
I: Integer;
begin
Result := 0;
for I := 1 to Length(S) do
if S[I] in Chars then
Inc(Result);
end;
//--------------------------------------------------------------------------------------------------
function StrStrCount(const S, SubS: AnsiString): 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 PIC}
function _StrCompare(const S1, S2: AnsiString): Integer; forward;
function StrCompare(const S1, S2: AnsiString): Integer;
begin
Result := _StrCompare(S1, S2);
end;
function _StrCompare(const S1, S2: AnsiString): Integer; assembler;
{$ELSE}
function StrCompare(const S1, S2: AnsiString): Integer; assembler;
{$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 AnsiString pointers
MOV ESI, EAX
MOV EDI, EDX
// get the length of strings
MOV EAX, [ESI-AnsiStrRecSize].TAnsiStrRec.Length
MOV EDX, [EDI-AnsiStrRecSize].TAnsiStrRec.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, AnsiCaseMap
// 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-AnsiStrRecSize].TAnsiStrRec.Length
SUB EAX, EDX
RET
@@Str2Null:
// return = Length(Str2);
MOV EAX, [EAX-AnsiStrRecSize].TAnsiStrRec.Length
RET
@@Equal:
XOR EAX, EAX
end;
//--------------------------------------------------------------------------------------------------
function StrCompareRange(const S1, S2: AnsiString; const Index, Count: Integer): Integer; assembler;
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 - AnsiStrRecSize].TAnsiStrRec.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 - AnsiStrRecSize].TAnsiStrRec.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;
//--------------------------------------------------------------------------------------------------
function StrFillChar(const C: AnsiChar; const Count: Integer): AnsiString;
begin
Assert(Count >= 0);
SetLength(Result, Count);
if (Count > 0) then
FillChar(Result[1], Count, Ord(C));
end;
//--------------------------------------------------------------------------------------------------
function StrFind(const Substr, S: AnsiString; const Index: Integer): Integer; assembler;
const
SearchChar: Byte = 0;
NumberOfChars: Integer = 0;
asm
// if SubStr = '' then Return := 0;
TEST EAX, EAX
JZ @@SubstrIsNull
// if Str = '' then Return := 0;
TEST EDX, EDX
JZ @@StrIsNull
// Index := Index - 1; if Index < 0 then Return := 0;
DEC ECX
JL @@IndexIsSmall
// EBX will hold the case table, ESI pointer to Str, EDI pointer
// to Substr and - # of chars in Substr to compare
PUSH EBX
PUSH ESI
PUSH EDI
// set the string pointers
MOV ESI, EDX
MOV EDI, EAX
// save the Index in EDX
MOV EDX, ECX
// temporary get the length of Substr and Str
MOV EBX, [EDI - AnsiStrRecSize].TAnsiStrRec.Length
MOV ECX, [ESI - AnsiStrRecSize].TAnsiStrRec.Length
// save the address of Str to compute the result
PUSH ESI
// 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 NumberOfChars, EBX
// point Str to Index'th char
ADD ESI, EDX
// load case map into EBX, and clear EAX
LEA EBX, AnsiCaseMap
XOR EAX, EAX
XOR EDX, EDX
// bring the first char out of the Substr and point Substr to the next char
MOV DL, [EDI]
INC EDI
// lower case it
MOV DL, [EBX + EDX]
MOV SearchChar, DL
JMP @@Find
@@FindNext:
// update the loop counter and check the end of AnsiString.
// if we reached the end, Substr was not found.
DEC ECX
JL @@NotFound
@@Find:
// get current char from the AnsiString, and point Str to the next one
MOV AL, [ESI]
INC ESI
// lower case current char
MOV AL, [EBX + EAX]
// does current char match primary search char? if not, go back to the main loop
CMP AL, SearchChar
JNE @@FindNext
@@Compare:
// # of chars in Substr to compare
MOV EDX, NumberOfChars
@@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, [ESI + EDX]
CMP AL, [EDI + EDX]
JE @@CompareNext
// otherwise try the reverse case. If they still don't match go back to the Find loop
MOV AL, [EBX + EAX + AnsiReOffset]
CMP AL, [EDI + EDX]
JNE @@FindNext
// if they matched, continue comparing
JMP @@CompareNext
@@Found:
// we found it, calculate the result
MOV EAX, ESI
POP ESI
SUB EAX, ESI
POP EDI
POP ESI
POP EBX
RET
@@NotFound:
// not found it, clear the result
XOR EAX, EAX
POP ESI
POP EDI
POP ESI
POP EBX
RET
@@IndexIsSmall:
@@StrIsNull:
// clear the result
XOR EAX, EAX
@@SubstrIsNull:
@@Exit:
end;
//--------------------------------------------------------------------------------------------------
function StrHasPrefix(const S: AnsiString; const Prefixes: array of string): Boolean;
begin
Result := StrPrefixIndex(S, Prefixes) > -1;
end;
//--------------------------------------------------------------------------------------------------
function StrIndex(const S: AnsiString; const List: array of string): Integer;
var
I: Integer;
begin
Result := -1;
for I := Low(List) to High(List) do
begin
if AnsiSameText(S, List[I]) then
begin
Result := I;
Break;
end;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrILastPos(const SubStr, S: AnsiString): Integer;
begin
Result := JclStrings.StrLastPos(StrUpper(SubStr), StrUpper(S));
end;
//--------------------------------------------------------------------------------------------------
function StrIPos(const SubStr, S: AnsiString): integer;
begin
Result := Pos(AnsiUpperCase(SubStr), AnsiUpperCase(S));
end;
//--------------------------------------------------------------------------------------------------
function StrIsOneOf(const S: AnsiString; const List: array of AnsiString): Boolean;
begin
Result := StrIndex(S, List) > -1;
end;
//--------------------------------------------------------------------------------------------------
function StrLastPos(const SubStr, S: AnsiString): Integer;
var
Last, Current: PAnsiChar;
begin
Result := 0;
Last := nil;
Current := PAnsiChar(S);
while (Current <> nil) and (Current^ <> #0) do
begin
Current := AnsiStrPos(PAnsiChar(Current), PAnsiChar(SubStr));
if Current <> nil then
begin
Last := Current;
Inc(Current);
end;
end;
if Last <> nil then
Result := Abs((Longint(PAnsiChar(S)) - Longint(Last)) div SizeOf(AnsiChar)) + 1;
end;
//--------------------------------------------------------------------------------------------------
// IMPORTANT NOTE: The StrMatch function does currently not work with the Asterix (*)
function StrMatch(const Substr, S: AnsiString; const Index: Integer): Integer; assembler;
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 AnsiString 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 - AnsiStrRecSize].TAnsiStrRec.Length
MOV ECX, [ESI - AnsiStrRecSize].TAnsiStrRec.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, AnsiCaseMap
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 AnsiString
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 + AnsiReOffset] // 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;
//--------------------------------------------------------------------------------------------------
// Derived from "Like" by Michael Winter
function StrMatches(const Substr, S: AnsiString; const Index: Integer): Boolean;
var
StringPtr: PChar;
PatternPtr: PChar;
StringRes: PChar;
PatternRes: PChar;
begin
if SubStr = '' then
raise EJclStringError.CreateResRec(@RsBlankSearchString);
Result := SubStr = '*';
if Result or (S = '') then
Exit;
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;
//--------------------------------------------------------------------------------------------------
function StrNPos(const S, SubStr: AnsiString; 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: AnsiString; 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: AnsiString; 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 AnsiSameText(Test, Prefixes[I]) then
begin
Result := I;
Break;
end;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrSearch(const Substr, S: AnsiString; const Index: Integer): Integer; assembler;
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 AnsiString 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-AnsiStrRecSize].TAnsiStrRec.Length
MOV ECX, [ESI-AnsiStrRecSize].TAnsiStrRec.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 AnsiString.
// if we reached the end, Substr was not found.
DEC ECX
JL @@NotFound
@@Find:
// get current char from the AnsiString, 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;
//==================================================================================================
// String Extraction
//==================================================================================================
function StrAfter(const SubStr, S: AnsiString): AnsiString;
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: AnsiString): AnsiString;
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: AnsiString; const Start, Stop: AnsiChar): AnsiString;
var
PosStart, PosEnd: Integer;
L: Integer;
begin
PosStart := Pos(Start, S);
PosEnd := Pos(Stop, S);
if (PosStart > 0) and (PosEnd > PosStart) then
begin
L := PosEnd - PosStart;
Result := Copy(S, PosStart + 1, L - 1);
end
else
Result := '';
end;
//--------------------------------------------------------------------------------------------------
function StrChopRight(const S: AnsiString; N: Integer): AnsiString;
begin
Result := Copy(S, 1, Length(S) - N);
end;
//--------------------------------------------------------------------------------------------------
function StrLeft(const S: AnsiString; Count: Integer): AnsiString;
begin
Result := Copy(S, 1, Count);
end;
//--------------------------------------------------------------------------------------------------
function StrMid(const S: AnsiString; Start, Count: Integer): AnsiString;
begin
Result := Copy(S, Start, Count);
end;
//--------------------------------------------------------------------------------------------------
function StrRestOf(const S: AnsiString; N: Integer ): AnsiString;
begin
Result := Copy(S, N, (Length(S) - N + 1));
end;
//--------------------------------------------------------------------------------------------------
function StrRight(const S: AnsiString; Count: Integer): AnsiString;
begin
Result := Copy(S, Length(S) - Count + 1, Count);
end;
//==================================================================================================
// Character
//==================================================================================================
function CharEqualNoCase(const C1, C2: AnsiChar): Boolean;
begin
//if they are not equal chars, may be same letter different case
Result := (C1 = C2) or
(CharIsAlpha(C1) and CharIsAlpha(C2) and (CharLower(C1) = CharLower(C2)));
end;
//--------------------------------------------------------------------------------------------------
function CharIsAlpha(const C: AnsiChar): Boolean;
begin
Result := (AnsiCharTypes[C] and C1_ALPHA) <> 0;
end;
//--------------------------------------------------------------------------------------------------
function CharIsAlphaNum(const C: AnsiChar): Boolean;
begin
Result := ((AnsiCharTypes[C] and C1_ALPHA) <> 0) or
((AnsiCharTypes[C] and C1_DIGIT) <> 0);
end;
//--------------------------------------------------------------------------------------------------
function CharIsBlank(const C: AnsiChar): Boolean;
begin
Result := ((AnsiCharTypes[C] and C1_BLANK) <> 0);
end;
//--------------------------------------------------------------------------------------------------
function CharIsControl(const C: AnsiChar): Boolean;
begin
Result := (AnsiCharTypes[C] and C1_CNTRL) <> 0;
end;
//--------------------------------------------------------------------------------------------------
function CharIsDelete(const C: AnsiChar): Boolean;
begin
Result := (C = #8);
end;
//--------------------------------------------------------------------------------------------------
function CharIsDigit(const C: AnsiChar): Boolean;
begin
Result := (AnsiCharTypes[C] and C1_DIGIT) <> 0;
end;
//--------------------------------------------------------------------------------------------------
function CharIsLower(const C: AnsiChar): Boolean;
begin
Result := (AnsiCharTypes[C] and C1_LOWER) <> 0;
end;
//--------------------------------------------------------------------------------------------------
function CharIsNumber(const C: AnsiChar): Boolean;
begin
Result := ((AnsiCharTypes[C] and C1_DIGIT) <> 0) or
(C in AnsiSigns) or (C = DecimalSeparator);
end;
//--------------------------------------------------------------------------------------------------
function CharIsPrintable(const C: AnsiChar): Boolean;
begin
Result := not CharIsControl(C);
end;
//--------------------------------------------------------------------------------------------------
function CharIsPunctuation(const C: AnsiChar): Boolean;
begin
Result := ((AnsiCharTypes[C] and C1_PUNCT) <> 0);
end;
//--------------------------------------------------------------------------------------------------
function CharIsReturn(const C: AnsiChar): Boolean;
begin
Result := (C = AnsiLineFeed) or (C = AnsiCarriageReturn);
end;
//--------------------------------------------------------------------------------------------------
function CharIsSpace(const C: AnsiChar): Boolean;
begin
Result := (AnsiCharTypes[C] and C1_SPACE) <> 0;
end;
//--------------------------------------------------------------------------------------------------
function CharIsUpper(const C: AnsiChar): Boolean;
begin
Result := (AnsiCharTypes[C] and C1_UPPER) <> 0;
end;
//--------------------------------------------------------------------------------------------------
function CharIsWhiteSpace(const C: AnsiChar): Boolean;
begin
Result := C in AnsiWhiteSpace;
end;
//--------------------------------------------------------------------------------------------------
function CharType(const C: AnsiChar): Word;
begin
Result := AnsiCharTypes[C];
end;
//==================================================================================================
// PCharVector
//==================================================================================================
function StringsToPCharVector(var Dest: PCharVector; const Source: TStrings): PCharVector;
var
I: Integer;
S: AnsiString;
List: array of PChar;
begin
Assert(Source <> nil);
Dest := AllocMem((Source.Count + SizeOf(AnsiChar)) * SizeOf(PChar));
SetLength(List, Source.Count + SizeOf(AnsiChar));
for I := 0 to Source.Count - 1 do
begin
S := Source[I];
List[I] := StrAlloc(Length(S) + SizeOf(AnsiChar));
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(Longint(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.Clear;
for I := 0 to Count - 1 do
Dest.Add(List[I]);
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;
//==================================================================================================
// Character Transformation Routines
//==================================================================================================
function CharHex(const C: AnsiChar): Byte;
begin
Result := $FF;
if C in AnsiDecDigits then
Result := Ord(CharUpper(C)) - Ord('0')
else
begin
if C in AnsiHexDigits then
Result := Ord(CharUpper(C)) - (Ord('A')) + 10;
end;
end;
//--------------------------------------------------------------------------------------------------
function CharLower(const C: AnsiChar): AnsiChar;
begin
Result := AnsiCaseMap[Ord(C) + AnsiLoOffset];
end;
//--------------------------------------------------------------------------------------------------
function CharToggleCase(const C: AnsiChar): AnsiChar;
begin
Result := AnsiCaseMap[Ord(C) + AnsiReOffset];
end;
//--------------------------------------------------------------------------------------------------
function CharUpper(const C: AnsiChar): AnsiChar;
begin
Result := AnsiCaseMap[Ord(C) + AnsiUpOffset];
end;
//==================================================================================================
// Character Search and Replace
//==================================================================================================
function CharPos(const S: AnsiString; const C: AnsiChar; const Index: Integer): Integer;
var
P: PAnsiChar;
begin
Result := 0;
if (Index > 0) and (Index <= Length(S)) then
begin
P := PAnsiChar(S);
Result := Index - 1;
Inc(P, Result);
while P^ <> #0 do
begin
Inc(Result);
if P^ = C then
Break;
Inc(P);
end;
if P^ = #0 then
Result := 0;
end;
end;
//--------------------------------------------------------------------------------------------------
function CharIPos(const S: AnsiString; const C: AnsiChar; const Index: Integer): Integer;
var
P: PAnsiChar;
CU: AnsiChar;
begin
Result := 0;
if (Index > 0) and (Index <= Length(S)) then
begin
CU := CharUpper(C);
P := PAnsiChar(S);
Result := Index - 1;
Inc(P, Result);
while P^ <> #0 do
begin
Inc(Result);
if AnsiCaseMap[Ord(P^) + AnsiUpOffset] = CU then
Break;
Inc(P);
end;
if P^ = #0 then
Result := 0;
end;
end;
//--------------------------------------------------------------------------------------------------
function CharReplace(var S: AnsiString; const Search, Replace: AnsiChar): Integer;
var
P: PAnsiChar;
begin
Result := 0;
if Search <> Replace then
begin
UniqueString(S);
P := PAnsiChar(S);
while P^ <> #0 do
begin
if P^ = Search then
begin
P^ := Replace;
Inc(Result);
end;
Inc(P);
end;
end;
end;
//==================================================================================================
// MultiSz
//==================================================================================================
function StringsToMultiSz(var Dest: PChar; const Source: TStrings): PChar;
var
I, TotalLength: Integer;
P: PChar;
begin
Assert(Source <> nil);
TotalLength := 0;
for I := 0 to Source.Count - 1 do
Inc(TotalLength, Length(Source[I]) + SizeOf(AnsiChar));
Dest := AllocMem(TotalLength + SizeOf(AnsiChar));
P := Dest;
for I := 0 to Source.Count - 1 do
begin
if Source[I] = '' then
begin
FreeMem(Dest);
Dest := nil;
raise EJclStringError.CreateResRec(@RsInvalidEmptyStringItem);
end;
P := StrECopy(P, PChar(Source[I]));
Inc(P);
end;
Result := Dest;
end;
//--------------------------------------------------------------------------------------------------
procedure MultiSzToStrings(const Dest: TStrings; const Source: PChar);
var
P: PChar;
begin
Assert(Dest <> nil);
Dest.Clear;
if Source <> nil then
begin
P := Source;
while P^ <> #0 do
begin
Dest.Add(P);
P := StrEnd(P);
Inc(P);
end;
end;
end;
//--------------------------------------------------------------------------------------------------
procedure FreeMultiSz(var Dest: PChar);
begin
FreeMem(Dest);
Dest := nil;
end;
//==================================================================================================
// TStrings Manipulation
//==================================================================================================
procedure StrToStrings(S, Sep: AnsiString; const List: TStrings; const AllowEmptyString: Boolean = True);
var
I, L: Integer;
Left: AnsiString;
begin
Assert(List <> nil);
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);
System.Delete(S, 1, I + L - 1);
I := Pos(Sep, S);
end;
if S <> '' then
List.Add(S); // Ignore empty strings at the end.
end;
//--------------------------------------------------------------------------------------------------
procedure StrIToStrings(S, Sep: AnsiString; const List: TStrings; const AllowEmptyString: Boolean = True);
var
I, L: Integer;
LowerCaseStr: string;
Left: AnsiString;
begin
Assert(List <> nil);
LowerCaseStr := StrLower(S);
Sep := StrLower(Sep);
List.Clear;
L := Length(Sep);
I := Pos(Sep, LowerCaseStr);
while (I > 0) do
begin
Left := StrLeft(S, I - 1);
if (Left <> '') or AllowEmptyString then
List.Add(Left);
System.Delete(S, 1, I + L - 1);
System.Delete(LowerCaseStr, 1, I + L - 1);
I := Pos(Sep, LowerCaseStr);
end;
if S <> '' then
List.Add(S); // Ignore empty strings at the end.
end;
//--------------------------------------------------------------------------------------------------
function StringsToStr(const List: TStrings; const Sep: AnsiString; const AllowEmptyString: Boolean): AnsiString;
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);
System.Delete(Result, Length(Result) - L + 1, L);
end;
end;
//--------------------------------------------------------------------------------------------------
procedure TrimStrings(const List: TStrings; DeleteIfEmpty: Boolean);
var
I: Integer;
begin
Assert(List <> nil);
for I := List.Count - 1 downto 0 do
begin
List[I] := Trim(List[I]);
if (List[I] = '') and DeleteIfEmpty then
List.Delete(I);
end;
end;
//--------------------------------------------------------------------------------------------------
procedure TrimStringsRight(const List: TStrings; DeleteIfEmpty: Boolean);
var
I: Integer;
begin
Assert(List <> nil);
for I := List.Count - 1 downto 0 do
begin
List[I] := TrimRight(List[I]);
if (List[I] = '') and DeleteIfEmpty then
List.Delete(I);
end;
end;
//--------------------------------------------------------------------------------------------------
procedure TrimStringsLeft(const List: TStrings; DeleteIfEmpty: Boolean);
var
I: Integer;
begin
Assert(List <> nil);
for I := List.Count - 1 downto 0 do
begin
List[I] := TrimLeft(List[I]);
if (List[I] = '') and DeleteIfEmpty then
List.Delete(I);
end;
end;
//--------------------------------------------------------------------------------------------------
{ todoc
descr: conditionally adds a string to a string list.
s: the string to add
strings: the string list to add s to
unique: determines whether s can be added to the list if an entry already exists
result: true if the string was added, false if it was not
author: Jean-Fabien Connault
}
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
//==================================================================================================
function BooleanToStr(B: Boolean): AnsiString;
const
Bools: array [Boolean] of PChar = ('False', 'True');
begin
Result := Bools[B];
end;
//--------------------------------------------------------------------------------------------------
function FileToString(const FileName: AnsiString): AnsiString;
var
fs: TFileStream;
len: Integer;
begin
fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
len := fs.Size;
SetLength(Result, len);
if len > 0 then
fs.ReadBuffer(Result[1], len);
finally
fs.Free;
end;
end;
//--------------------------------------------------------------------------------------------------
procedure StringToFile(const FileName, Contents: AnsiString);
var
fs: TFileStream;
len: Integer;
begin
fs := TFileStream.Create(FileName, fmCreate);
try
len := Length(Contents);
if len > 0 then
fs.WriteBuffer(Contents[1], Length(Contents));
finally
fs.Free;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrToken(var S: AnsiString; Separator: AnsiChar): AnsiString;
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;
//--------------------------------------------------------------------------------------------------
procedure StrTokens(const S: AnsiString; const List: TStrings);
var
Start: PChar;
Token: string;
Done: Boolean;
begin
Assert(List <> nil);
if List = nil then
Exit;
List.Clear;
Start := Pointer(S);
repeat
Done := StrWord(Start, Token);
if Token <> '' then
List.Add(Token);
until Done;
end;
//--------------------------------------------------------------------------------------------------
procedure StrTokenToStrings(S: AnsiString; Separator: AnsiChar; const List: TStrings);
var
Token: AnsiString;
begin
Assert(List <> nil);
if List = nil then
Exit;
List.Clear;
while S <> '' do
begin
Token := StrToken(S, Separator);
List.Add(Token);
end;
end;
//--------------------------------------------------------------------------------------------------
function StrWord(var S: PAnsiChar; out Word: AnsiString): Boolean;
var
Start: PAnsiChar;
begin
Word := '';
if S = nil then
begin
Result := True;
Exit;
end;
Start := nil;
Result := False;
while True do
begin
case S^ of
#0:
begin
if Start <> nil then
SetString(Word, Start, S - Start);
Result := True;
Exit;
end;
AnsiSpace, AnsiLineFeed, AnsiCarriageReturn:
begin
if Start <> nil then
begin
SetString(Word, Start, S - Start);
Exit;
end
else
while (S^ in [AnsiSpace, AnsiLineFeed, AnsiCarriageReturn]) do
Inc(S);
end;
else
if Start = nil then
Start := S;
Inc(S);
end;
end;
end;
//--------------------------------------------------------------------------------------------------
function StrToFloatSafe(const S: AnsiString): Float;
{ TODOc: Contributors: Robert Rossmair }
var
Temp: AnsiString;
I, J, K: Integer;
SwapSeparators, IsNegative: Boolean;
begin
Temp := S;
SwapSeparators := False;
IsNegative := False;
J := 0;
for I := 1 to Length(Temp) do
begin
if Temp[I] = '-' then
IsNegative := not IsNegative
else
if not (Temp[I] in [' ', '(', '+']) then
begin
// if it appears prior to any digit, it has to be a decimal separator
SwapSeparators := Temp[I] = ThousandSeparator;
J := I;
Break;
end;
end;
if not SwapSeparators then
begin
K := CharPos(Temp, DecimalSeparator);
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, DecimalSeparator) > 1) or
// we assume (consistent with Windows Platform SDK documentation),
// that thousand separators appear only to the left of the decimal
(K < CharPos(Temp, ThousandSeparator)));
end;
if SwapSeparators then
begin
// assume a numerical string from a different locale,
// where DecimalSeparator and ThousandSeparator are exchanged
for I := 1 to Length(Temp) do
if Temp[I] = DecimalSeparator then
Temp[I] := ThousandSeparator
else
if Temp[I] = ThousandSeparator then
Temp[I] := DecimalSeparator;
end;
Temp := StrKeepChars(Temp, ['0'..'9', DecimalSeparator]);
if Length(Temp) > 0 then
begin
if Temp[1] = DecimalSeparator then
Temp := '0' + Temp;
if Temp[length(Temp)] = DecimalSeparator then
Temp := Temp + '0';
Result := StrToFloat(Temp);
if IsNegative then
Result := -Result;
end
else
Result := 0.0;
end;
//--------------------------------------------------------------------------------------------------
function StrToIntSafe(const S: AnsiString): 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;
//==================================================================================================
// Backward compatibility
//==================================================================================================
{$IFNDEF COMPILER5_UP}
function AnsiSameText(const S1, S2: AnsiString): Boolean;
begin
Result := CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, PChar(S1),
Length(S1), PChar(S2), Length(S2)) = 2;
end;
{$ENDIF COMPILER5_UP}
//==================================================================================================
// Initialization
//==================================================================================================
initialization
LoadCharTypes; // this table first
LoadCaseMap; // or this function does not work
end.