BSOne.SFC/Tocsg.Lib/VCL/Other/KDL.StringUtils.pas

435 lines
11 KiB
Plaintext

{ String manipulation routines.
Copyright (C) 2006 - 2018 Aleg Azarouski.
}
unit KDL.StringUtils;
//{$I NoRTTI}
{$IFDEF UNICODE}
{$WEAKLINKRTTI ON}
{$RTTI EXPLICIT METHODS([]) PROPERTIES([]) FIELDS([])}
{$ENDIF}
interface
const
ListDivider = #13; // Used in DelphiToStringEx
defHumanizeDivider = '\'; // Used in StringToHumanized
defHumanizedLF = '\#10';
UnicodeLabel: array[0..2] of Byte = ($EF, $BB, $BF);
// Split a string by a given divider
// Return in El - left part, in s - rest of a string
procedure SplitBy(var S: string; const Divider: string; var Elem: string);
// Escaped string to string (based on the JEDI Code Library (JCL))
function StrEscapedToString(const S: string): string;
// Encode string to lng file string
// DividerCR - substitution for #13
// DividerCRLF - substitution for #13#10
// DividerLF - substitution for #10
function StringToLng(const S: string; Humanize: Boolean; const DividerCR,
DividerCRLF, DividerLF: string): string;
// Decode lng file string to string
// DividerCR - substitution for #13
// DividerCRLF - substitution for #13#10
// DividerLF - substitution for #10
// DefaultLineBreak - used when DividerCR = DividerCRLF
function LngToString(const S: string; Humanize: Boolean; const DividerCR,
DividerCRLF, DividerLF, DefaultLineBreak: string): string;
implementation
uses
SysUtils, StrUtils;
{$region 'Delphi string conversions'}
procedure SplitBy(var S: string; const Divider: string; var Elem: string);
var
i: Integer;
begin
i := Pos(Divider, S);
if i <= 0 then begin
Elem := S;
S := '';
end else begin
Elem := Copy(S, 1, i-1);
Delete(S, 1, i + Length(Divider) - 1);
end;
end;
// Encode string to delphi style string
function StringToDelphi(const s: string): string;
var
i: Integer;
insideStr: Boolean;
res: string;
ch: Char;
procedure SwitchStr; // '...'
begin
insideStr := not insideStr;
res := res + '''';
end;
begin
insideStr := False;
if s = '' then
res := ''''''
else begin
for i := 1 to Length(s) do begin
ch := s[i];
case ch of
'''': begin
if insideStr then
res := res + ''''''
else begin
res := res + '''''''';
insideStr := True;
end;
end;
#0..#31: begin
if insideStr then SwitchStr;
res := res + '#' + IntToStr(ord(ch));
end;
else begin
if not insideStr then SwitchStr;
res := res + ch;
end;
end;
end;
end;
if insideStr then SwitchStr;
Result := res;
end;
type
EDelphiToStringError = Class(Exception)
public
iBadChar: Integer; // Bad character position
end;
// Decode delphi style string to string
function DelphiToString(const s: string): string;
label
Err;
var
i, iOpened: Integer;
res: string;
insideStr, insideCode: Boolean;
ch: Char;
procedure OpenStr; // '...
begin
insideStr := True;
iOpened := i;
end;
procedure OpenCode; // #13
begin
insideCode := True;
iOpened := i;
end;
function CloseCode: Boolean;
begin
try
res := res + Char(StrToInt(Copy(s, iOpened+1, i-iOpened-1)));
Result := True;
insideCode := False;
except
Result := False;
end;
end;
var
Ex: EDelphiToStringError;
begin
res := '';
insideStr := False;
insideCode := False;
// 'Method ''%s'' not supported by automation object'
// 'Exception %s in module %s at %p.'#13#10'%s%s'#13#10
// '''hallo' -- 'hallo'''
// 'hal'''#13#10'lo' -- 'hallo''hallo'
for i := 1 to Length(s) do begin
ch := s[i];
if insideStr then begin
// Str opened, code closed
if ch = '''' then
insideStr := False
else
res := res + ch;
end else begin
if insideCode then begin
// Str closed, code opened
case ch of
'''': begin
if not CloseCode then
goto Err;
OpenStr;
end;
'#': begin
if not CloseCode then
goto Err;
OpenCode;
end;
'0'..'9':;
else goto Err;
end;
end else begin
// Str closed, code closed
case ch of
'''': begin
if (i > 1) and (s[i-1] = '''') then
res := res + '''';
OpenStr;
end;
'#': OpenCode;
else begin
Result := res;
Ex := EDelphiToStringError.Create('Bad decoded string: "' + s + '"');
Ex.iBadChar := i;
raise Ex;
end;
end;
end;
end;
end;
if insideStr then begin
Err:
raise Exception.Create('Bad decoded string: "' + s + '"');
end;
if insideCode then CloseCode;
Result := res;
end;
// Decode delphi style string and stringlist to string
// Stringlist elements delimited by #13
function DelphiToStringEx(s: string): string;
var
res, s1: string;
procedure AddResS1;
begin
if res <> '' then
res := res + ListDivider;
res := res + S1;
end;
var
Ok: Boolean;
begin
res := '';
repeat
Ok := True;
try
s1 := DelphiToString(s);
except
on E: EDelphiToStringError do begin
AddResS1;
s := Trim(Copy(s, E.iBadChar+1, MaxInt));
Ok := False;
end;
end;
until Ok;
AddResS1;
Result := res;
end;
{$endregion}
{$region 'Escaped string conversions'}
function StrEscapedToString(const S: string): string;
// \x041f --> wide character
procedure HandleHexEscapeSeq(const S: string; var I: Integer; Len: Integer;
var Dest: string);
const
hexDigits = string('0123456789abcdefABCDEF');
var
startI, val, n: Integer;
begin
startI := I;
val := 0;
while I < StartI + 4 do begin
n := Pos(S[I+1], hexDigits) - 1;
if n < 0 then begin
if startI = I then begin
// '\x' without hex digit following is not escape sequence
Dest := Dest + '\x';
Exit;
end;
end else begin
Inc(I);
if n >= 16 then
n := n - 6;
val := val * 16 + n;
if val > Ord(High(Char)) then
raise Exception.CreateFmt(
'Numeric constant too large (%d) at position %d.', [val, startI]);
end;
end;
Dest := Dest + Char(val);
end;
procedure HandleOctEscapeSeq(const S: string; var I: Integer; Len: Integer;
var Dest: string);
const
octDigits = string('01234567');
var
startI, val, n: Integer;
begin
startI := I;
// first digit
val := Pos(S[I], octDigits) - 1;
if I < Len then
begin
n := Pos(S[I + 1], octDigits) - 1;
if n >= 0 then
begin
Inc(I);
val := val * 8 + n;
end;
if I < Len then
begin
n := Pos(S[I + 1], octDigits) - 1;
if n >= 0 then
begin
Inc(I);
val := val * 8 + n;
end;
end;
end;
if val > Ord(High(Char)) then
raise Exception.CreateFmt(
'Numeric constant too large (%d) at position %d.', [val, startI]);
Dest := Dest + Char(val);
end;
const
NativeBell = Char(#7);
NativeBackspace = Char(#8);
NativeTab = Char(#9);
NativeLineFeed = Char(#10);
NativeVerticalTab = Char(#11);
NativeFormFeed = Char(#12);
NativeCarriageReturn = Char(#13);
var
I, Len: Integer;
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(S, I, Len, Result)
else
// '\x' at end of string is not escape sequence
Result := Result + '\x';
'0'..'7':
// start of octal escape sequence
HandleOctEscapeSeq(S, I, Len, Result);
else
// no escape sequence
Result := Result + '\' + S[I];
end;
end;
Inc(I);
end;
end;
{$endregion}
{$region 'Humanized string conversions'}
// Encode string to "humanized" style string
// DividerCR - substitution for #13
// DividerCRLF - substitution for #13#10
// DividerLF - substitution for #10
function StringToHumanized(const s: string; const DividerCR, DividerCRLF,
DividerLF: string): string;
begin
if (Pos(DividerCR, s) > 0)
or (Pos(DividerCRLF, s) > 0)
or (Pos(DividerLF, s) > 0)
then
raise Exception.CreateFmt(
'String "%s" contains a humanize divider "%s", "%s" or "%s" and can''t'
+ ' be converted properly.'#13#10
+ 'Try set a different string as the divider for this application.',
[s, DividerCR, DividerCRLF, DividerLF]);
Result := StringReplace(
StringReplace(
StringReplace(s, sLineBreak, DividerCRLF, [rfReplaceAll]),
#13, DividerCR, [rfReplaceAll]),
#10, DividerLF, [rfReplaceAll]);
end;
// Decode "humanized" style string to string
// DividerCR - substitution for #13
// DividerCRLF - substitution for #13#10
// DividerLF - substitution for #10
// DefaultLineBreak - used when DividerCR = DividerCRLF
function HumanizedToString(const s: string; const DividerCR, DividerCRLF,
DividerLF, DefaultLineBreak: string): string;
begin
if DividerCR = DividerCRLF then
Result := StringReplace(s, DividerCR, DefaultLineBreak, [rfReplaceAll])
else begin
Result := StringReplace(s, DividerCR, #13, [rfReplaceAll]);
Result := StringReplace(Result, DividerCRLF, sLineBreak, [rfReplaceAll]);
end;
Result := StringReplace(Result, DividerLF, #10, [rfReplaceAll]);
end;
{$endregion}
{$region 'Lng file string conversions'}
function StringToLng(const S: string; Humanize: Boolean; const DividerCR,
DividerCRLF, DividerLF: string): string;
begin
if Humanize then
Result := StringToHumanized(S, DividerCR, DividerCRLF, DividerLF)
else
Result := StringToDelphi(S);
end;
function LngToString(const S: string; Humanize: Boolean; const DividerCR,
DividerCRLF, DividerLF, DefaultLineBreak: string): string;
begin
if Humanize then
Result := HumanizedToString(S, DividerCR, DividerCRLF, DividerLF,
DefaultLineBreak)
else
Result := DelphiToStringEx(S);
end;
{$endregion}
end.