663 lines
16 KiB
Plaintext
663 lines
16 KiB
Plaintext
{*******************************************************}
|
||
{ }
|
||
{ Tocsg.Strings }
|
||
{ }
|
||
{ Copyright (C) 2022 kkuzil }
|
||
{ }
|
||
{*******************************************************}
|
||
|
||
unit Tocsg.Strings;
|
||
|
||
interface
|
||
|
||
uses
|
||
System.Classes, System.SysUtils, Winapi.Windows;
|
||
|
||
function ReverseString(const sVal: string): String; inline;
|
||
procedure SumString(var sDest: String; sTail: String; sDm: String = ''; bIgrSpace: Boolean = false); inline;
|
||
procedure SumStringA(var sDest: AnsiString; sTail: AnsiString; sDm: AnsiString = ''; bIgrSpace: Boolean = false); inline;
|
||
function DeleteChars(sSrc: String; sDelChars: String): String;
|
||
function AppendChars(sSrc: String; sIfChars, sAppendStr: String): String;
|
||
function GetCapsuleStr(sCapB, sCapE: String; sText: String; bIgrCase: Boolean = true): String;
|
||
function GetRandomStr(nMin, nMax: Byte): String;
|
||
function GetRandomStrEx(nLen: Integer; sSetStr: String = ''): String;
|
||
function DeleteNullTail(sVal: String): String;
|
||
function GetGUID: String;
|
||
|
||
function SplitString(sText: String; const sDm: String;
|
||
aList: TStrings;
|
||
bNullStrInc: Boolean = false;
|
||
bIgrOverl: Boolean = false;
|
||
bClear: Boolean = true): Integer;
|
||
function SplitString2(sText: String; const sDelimiter: String;
|
||
aList: TStrings;
|
||
bNullStrInc: Boolean = false;
|
||
bIgrOverl: Boolean = false;
|
||
bClear: Boolean = true): Integer;
|
||
|
||
function InsertPointString(const sInsert: String; sText: String; nPoint: Integer): String;
|
||
function InsertPointString2(const sInsert: String; sText: String; nPoint: Integer): String;
|
||
function InsertPointComma(sText: String; nPoint: Integer): String; overload;
|
||
function InsertPointComma(llData: LONGLONG; nPoint: Integer): String; overload;
|
||
function MakeCharStr(c: Char; nCnt: Integer): String; inline;
|
||
function ExtrNumStr(const sStr: String): String;
|
||
function ExtrLastDelimiterStr(sStr: String; sDm: String): String;
|
||
|
||
function SaveStrToFile(sPath: String; sStr: String; aEncoding: TEncoding = nil): Boolean;
|
||
function LoadStrFromFile(sPath: String; aEncoding: TEncoding = nil): String;
|
||
function ExtractTextSafe(const sPath: String): String;
|
||
|
||
function StrsReplace(const S: String; const arrOld, arrNew: array of string): String; overload;
|
||
function StrsReplace(const S: String; const arrOld: array of string; sNew: String): String; overload;
|
||
function CountStr(const sSrc, sFind: string; n: Integer = 1): Integer;
|
||
|
||
function IsUTF8_AnsiChar(const str: PAnsiChar): Boolean;
|
||
function IsUTF8(const B: TBytes): Boolean;
|
||
|
||
function LastIndexOf(const sFind, sSource: string): Integer;
|
||
|
||
implementation
|
||
|
||
uses
|
||
Tocsg.Safe, Tocsg.Exception, System.StrUtils, System.IOUtils;
|
||
|
||
function ReverseString(const sVal: string): String;
|
||
var
|
||
i, nLen: Integer;
|
||
begin
|
||
nLen := Length(sVal);
|
||
SetLength(Result, nLen);
|
||
for i := 1 to nLen do
|
||
Result[i] := sVal[Succ(nLen - i)];
|
||
end;
|
||
|
||
procedure SumString(var sDest: String; sTail: String; sDm: String = ''; bIgrSpace: Boolean = false);
|
||
begin
|
||
if bIgrSpace and (sTail = '') then
|
||
exit;
|
||
|
||
if sDest = '' then
|
||
sDest := sTail
|
||
else
|
||
sDest := sDest + sDm + sTail;
|
||
end;
|
||
|
||
procedure SumStringA(var sDest: AnsiString; sTail: AnsiString; sDm: AnsiString = ''; bIgrSpace: Boolean = false);
|
||
begin
|
||
if bIgrSpace and (sTail = '') then
|
||
exit;
|
||
|
||
if sDest = '' then
|
||
sDest := sTail
|
||
else
|
||
sDest := sDest + sDm + sTail;
|
||
end;
|
||
|
||
function DeleteChars(sSrc: String; sDelChars: String): String;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
Result := '';
|
||
for i := 1 to Length(sSrc) do
|
||
if sDelChars.IndexOf(sSrc[i]) = -1 then
|
||
Result := Result + sSrc[i];
|
||
end;
|
||
|
||
function AppendChars(sSrc: String; sIfChars, sAppendStr: String): String;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
Result := '';
|
||
for i := 1 to Length(sSrc) do
|
||
if sIfChars.IndexOf(sSrc[i]) <> -1 then
|
||
Result := Result + sAppendStr + sSrc[i]
|
||
else
|
||
Result := Result + sSrc[i];
|
||
end;
|
||
|
||
function GetCapsuleStr(sCapB, sCapE: String; sText: String; bIgrCase: Boolean = true): String;
|
||
var
|
||
nPosS, nPosE: Integer;
|
||
begin
|
||
Result := '';
|
||
|
||
if bIgrCase then
|
||
begin
|
||
sCapB := UpperCase(sCapB);
|
||
sCapE := UpperCase(sCapE);
|
||
sText := UpperCase(sText);
|
||
end;
|
||
|
||
nPosS := Pos(sCapB, sText);
|
||
if nPosS > 0 then
|
||
begin
|
||
Delete(sText, 1, nPosS + Length(sCapB) - 1);
|
||
nPosE := Pos(sCapE, sText);
|
||
if nPosE > 0 then
|
||
Result := Copy(sText, 1, nPosE - 1);
|
||
end;
|
||
end;
|
||
|
||
function GetRandomStr(nMin, nMax: Byte): String;
|
||
var
|
||
i, nLen: Integer;
|
||
begin
|
||
Result := '';
|
||
if nMax = 0 then
|
||
exit;
|
||
|
||
if nMin > nMax then
|
||
nMin := nMax;
|
||
|
||
Randomize;
|
||
nLen := Random(nMax);
|
||
if nMax >= (nMin + nLen) then
|
||
Inc(nLen, nMin);
|
||
|
||
for i := 0 to nLen do
|
||
case Random(2) of
|
||
1 : Result := Result + Char(Integer('a') + Round(Random(26)));
|
||
else Result := Result + Char(Integer('A') + Round(Random(26)));
|
||
end;
|
||
end;
|
||
|
||
function GetRandomStrEx(nLen: Integer; sSetStr: String = ''): String;
|
||
const
|
||
DEF_SET = 'ABCDFGHIJKLMNPQRSQUVWXYZ0123456789'; // E, O 제거
|
||
var
|
||
i, nSetLen: Integer;
|
||
begin
|
||
if sSetStr = '' then
|
||
sSetStr := DEF_SET;
|
||
|
||
Randomize; // 난수 초기화
|
||
Result := '';
|
||
nSetLen := Length(sSetStr);
|
||
for i := 1 to nLen do
|
||
Result := Result + sSetStr[Random(nSetLen) + 1];
|
||
end;
|
||
|
||
function DeleteNullTail(sVal: String): String;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
Result := sVal;
|
||
if Result = '' then
|
||
exit;
|
||
|
||
for i := 1 to Length(Result) do
|
||
if Result[i] = #0 then
|
||
begin
|
||
SetLength(Result, i - 1);
|
||
exit;
|
||
end;
|
||
end;
|
||
|
||
function GetGUID: String;
|
||
var
|
||
GG: TGUID;
|
||
begin
|
||
Result := '';
|
||
try
|
||
if CreateGUID(GG) = S_OK then
|
||
Result := GUIDToString(GG);
|
||
except
|
||
// ..
|
||
end;
|
||
end;
|
||
|
||
function SplitString(sText: String; const sDm: String;
|
||
aList: TStrings;
|
||
bNullStrInc: Boolean = false;
|
||
bIgrOverl: Boolean = false;
|
||
bClear: Boolean = true): Integer;
|
||
var
|
||
nPos: Integer;
|
||
sTemp: String;
|
||
begin
|
||
if bClear then
|
||
aList.Clear;
|
||
|
||
while true do
|
||
begin
|
||
nPos := Pos(sDm, sText);
|
||
if nPos <> 0 then
|
||
begin
|
||
sTemp := Trim(Copy(sText, 0, nPos - 1));
|
||
|
||
if sTemp <> '' then
|
||
begin
|
||
if not bIgrOverl or
|
||
(aList.IndexOf(sTemp) = -1) then
|
||
aList.Add(sTemp);
|
||
end else
|
||
if bNullStrInc then
|
||
aList.Add('');
|
||
|
||
Delete(sText, 1, nPos + Length(sDm) - 1);
|
||
end else begin
|
||
if sText <> '' then
|
||
begin
|
||
if not bIgrOverl or
|
||
(aList.IndexOf(sText) = -1) then
|
||
aList.Add(Trim(sText));
|
||
end else
|
||
if bNullStrInc then
|
||
aList.Add('');
|
||
|
||
break;
|
||
end;
|
||
end;
|
||
|
||
Result := aList.Count;
|
||
end;
|
||
|
||
// 따옴표 묶음 인식
|
||
function SplitString2(sText: String; const sDelimiter: String;
|
||
aList: TStrings;
|
||
bNullStrInc: Boolean = false;
|
||
bIgrOverl: Boolean = false;
|
||
bClear: Boolean = true): Integer;
|
||
|
||
procedure AddStr(s: String);
|
||
begin
|
||
if not bIgrOverl or
|
||
(aList.IndexOf(s) = -1) then
|
||
aList.Add(s);
|
||
end;
|
||
|
||
var
|
||
nDdPos,
|
||
nPos: Integer;
|
||
sTemp,
|
||
sDdTemp: String;
|
||
begin
|
||
if bClear then
|
||
aList.Clear;
|
||
|
||
while true do
|
||
begin
|
||
sDdTemp := '';
|
||
|
||
if (Length(sText) > 0) and (sText[1] = '"') then
|
||
begin
|
||
Delete(sText, 1, 1);
|
||
nDdPos := Pos('"', sText);
|
||
if nDdPos > 0 then
|
||
begin
|
||
sDdTemp := Copy(sText, 1, nDdPos - 1);
|
||
Delete(sText, 1, nDdPos);
|
||
end else
|
||
sText := '"' + sText;
|
||
end;
|
||
|
||
nPos := Pos(sDelimiter, sText);
|
||
if nPos <> 0 then
|
||
begin
|
||
if sDdTemp <> '' then
|
||
sTemp := sDdTemp
|
||
// sTemp := '"' + sDdTemp + '"'
|
||
else
|
||
sTemp := Copy(sText, 0, nPos - 1);
|
||
AddStr(Trim(sTemp));
|
||
Delete(sText, 1, nPos+Length(sDelimiter)-1);
|
||
end else begin
|
||
if sDdTemp <> '' then
|
||
begin
|
||
AddStr(sDdTemp);
|
||
end else begin
|
||
if sText <> '' then
|
||
AddStr(Trim(sText))
|
||
else if bNullStrInc then
|
||
aList.Add('');
|
||
end;
|
||
break;
|
||
end;
|
||
end;
|
||
|
||
Result := aList.Count;
|
||
end;
|
||
|
||
function InsertPointString(const sInsert: String; sText: String; nPoint: Integer): String;
|
||
var
|
||
i : integer;
|
||
begin
|
||
for i := (Length(sText) - 1) div nPoint downto 1 do
|
||
Insert(sInsert, sText, Length(sText) - (i * nPoint) + 1);
|
||
|
||
Result := sText;
|
||
end;
|
||
|
||
function InsertPointString2(const sInsert: String; sText: String; nPoint: Integer): String;
|
||
var
|
||
i : integer;
|
||
begin
|
||
for i := 1 to (Length(sText) div nPoint) do
|
||
Insert(sInsert, sText, i * nPoint);
|
||
|
||
Result := sText;
|
||
end;
|
||
|
||
function InsertPointComma(sText: String; nPoint: Integer): String;
|
||
begin
|
||
Result := InsertPointString(',', sText, nPoint);
|
||
end;
|
||
|
||
function InsertPointComma(llData: LONGLONG; nPoint: Integer): String;
|
||
begin
|
||
Result := InsertPointString(',', IntToStr(llData), nPoint);
|
||
end;
|
||
|
||
procedure InsertPointCommaA(var sText: AnsiString; sAdd: AnsiString; nPoint: Integer);
|
||
begin
|
||
if sAdd <> '' then
|
||
begin
|
||
if sText <> '' then
|
||
sText := sText + ',' + sAdd
|
||
else
|
||
sText := sAdd;
|
||
end;
|
||
end;
|
||
|
||
function MakeCharStr(c: Char; nCnt: Integer): String; inline;
|
||
var
|
||
i: Integer;
|
||
begin
|
||
Result := '';
|
||
for i := 0 to nCnt - 1 do
|
||
Result := Result + c;
|
||
end;
|
||
|
||
function ExtrNumStr(const sStr: String): String;
|
||
var
|
||
i, nLen: Integer;
|
||
begin
|
||
Result := '';
|
||
nLen := Length(sStr);
|
||
for i := 1 to nLen do
|
||
case Byte(sStr[i]) of
|
||
48 .. 57 : Result := Result + sStr[i];
|
||
end;
|
||
end;
|
||
|
||
function ExtrLastDelimiterStr(sStr: String; sDm: String): String;
|
||
var
|
||
nPos: Integer;
|
||
begin
|
||
Result := sStr;
|
||
nPos := Result.LastDelimiter(sDm);
|
||
if nPos > 0 then
|
||
Delete(Result, 1, nPos + 1);
|
||
end;
|
||
|
||
function SaveStrToFile(sPath: String; sStr: String; aEncoding: TEncoding = nil): Boolean;
|
||
var
|
||
StrList: TStringList;
|
||
begin
|
||
Result := false;
|
||
try
|
||
if aEncoding = nil then
|
||
aEncoding := TEncoding.UTF8;
|
||
|
||
Guard(StrList, TStringList.Create);
|
||
StrList.Text := sStr;
|
||
StrList.SaveToFile(sPath, aEncoding);
|
||
Result := true;
|
||
except
|
||
on E: Exception do
|
||
ETgException.TraceException(E, 'Fail .. SaveStrFromFile()');
|
||
end;
|
||
end;
|
||
|
||
function LoadStrFromFile(sPath: String; aEncoding: TEncoding = nil): String;
|
||
var
|
||
StrList: TStringList;
|
||
begin
|
||
Result := '';
|
||
try
|
||
if not FileExists(sPath) then
|
||
exit;
|
||
|
||
if aEncoding = nil then
|
||
aEncoding := TEncoding.UTF8;
|
||
|
||
Guard(StrList, TStringList.Create);
|
||
StrList.LoadFromFile(sPath, aEncoding);
|
||
Result := StrList.Text;
|
||
except
|
||
on E: Exception do
|
||
ETgException.TraceException(E, 'Fail .. LoadStrFromFile()');
|
||
end;
|
||
end;
|
||
|
||
function ProcessInvalidUTF8Bytes(const Data: TBytes): TBytes;
|
||
var
|
||
i, nLen: Integer;
|
||
begin
|
||
try
|
||
i := 0;
|
||
SetLength(Result, 0);
|
||
|
||
nLen := Length(Data);
|
||
while i < nLen do
|
||
begin
|
||
if (Data[i] and $80) = 0 then
|
||
begin
|
||
// UTF-8 1바이트 (ASCII)
|
||
Result := Result + [Data[i]];
|
||
Inc(i);
|
||
end else
|
||
if ((Data[i] and $E0) = $C0) and (i+ 1 < nLen) and
|
||
((Data[i+1] and $C0) = $80) then
|
||
begin
|
||
// UTF-8 다중 바이트 시퀀스 시작 검사
|
||
Result := Result + [Data[i], Data[i+1]];
|
||
Inc(i, 2);
|
||
end else
|
||
if ((Data[i] and $F0) = $E0) and (i + 2 < nLen) and
|
||
((Data[i+1] and $C0) = $80) and ((Data[i+2] and $C0) = $80) then
|
||
begin
|
||
Result := Result + [Data[I], Data[I+1], Data[i+2]];
|
||
Inc(i, 3);
|
||
end else
|
||
if ((Data[i] and $F8) = $F0) and (i + 3 < nLen) and
|
||
((Data[i+1] and $C0) = $80) and ((Data[i+2] and $C0) = $80) and
|
||
((Data[i+3] and $C0) = $80) then
|
||
begin
|
||
Result := Result + [Data[i], Data[i+1], Data[i+2], Data[i+3]];
|
||
Inc(i, 4);
|
||
end else
|
||
Inc(i); // 유효하지 않은 바이트 → 무시
|
||
end;
|
||
except
|
||
on E: Exception do
|
||
ETgException.TraceException(E, 'Fail .. ProcessInvalidUTF8Bytes()');
|
||
end;
|
||
end;
|
||
|
||
function ExtractTextSafe(const sPath: String): String;
|
||
var
|
||
Raw: TBytes;
|
||
FndEnc: TEncoding;
|
||
begin
|
||
Result := '';
|
||
try
|
||
// 파일을 바이트 배열로 읽음
|
||
Raw := TFile.ReadAllBytes(sPath);
|
||
if Length(Raw) = 0 then
|
||
exit;
|
||
|
||
// BOM을 기반으로 인코딩 감지
|
||
FndEnc := nil;
|
||
if TEncoding.GetBufferEncoding(Raw, FndEnc, TEncoding.ANSI) = 0 then
|
||
begin
|
||
if IsUTF8(Raw) then
|
||
Result := TEncoding.UTF8.GetString(Raw)
|
||
else
|
||
Result := TEncoding.ANSI.GetString(Raw);
|
||
exit;
|
||
end;
|
||
|
||
Result := FndEnc.GetString(Raw);
|
||
except
|
||
on E: Exception do
|
||
ETgException.TraceException(E, 'Fail .. ExtractTextSafe()', 5);
|
||
end;
|
||
end;
|
||
|
||
function StrsReplace(const S: String; const arrOld, arrNew: array of string): String;
|
||
var
|
||
i : Integer;
|
||
begin
|
||
Result := S;
|
||
for i := Low(arrOld) to High(arrOld) do
|
||
Result := StringReplace(Result, arrOld[i], arrNew[i], [rfReplaceAll, rfIgnoreCase]);
|
||
end;
|
||
|
||
function StrsReplace(const S: String; const arrOld: array of string; sNew: String): String;
|
||
var
|
||
i : Integer;
|
||
begin
|
||
Result := S;
|
||
for i := Low(arrOld) to High(arrOld) do
|
||
Result := StringReplace(Result, arrOld[i], sNew, [rfReplaceAll, rfIgnoreCase]);
|
||
end;
|
||
|
||
function CountStr(const sSrc, sFind: string; n: Integer = 1): Integer;
|
||
var
|
||
i, nLen: Integer;
|
||
begin
|
||
result := 0;
|
||
nLen := length(sSrc);
|
||
i := n; // 일정 인덱스 이후의 문자를 검색.
|
||
while i <= nLen do
|
||
begin
|
||
i := PosEx(sFind, sSrc, i);
|
||
if i > 0 then
|
||
Inc(result)
|
||
else break;
|
||
|
||
Inc(i);
|
||
end;
|
||
end;
|
||
|
||
function IsUTF8_AnsiChar(const str: PAnsiChar): Boolean;
|
||
var
|
||
i, len, c, bits, b: Integer;
|
||
begin
|
||
Result := True; // 기본적으로 True로 설정
|
||
i := 0;
|
||
len := StrLen(str);
|
||
while i < len do
|
||
begin
|
||
c := Ord(str[i]);
|
||
if c > 128 then
|
||
begin
|
||
if (c >= 254) then Exit(False)
|
||
else if (c >= 252) then bits := 6
|
||
else if (c >= 248) then bits := 5
|
||
else if (c >= 240) then bits := 4
|
||
else if (c >= 224) then bits := 3
|
||
else if (c >= 192) then bits := 2
|
||
else Exit(False);
|
||
|
||
if (i + bits) > len then Exit(False);
|
||
while bits > 1 do
|
||
begin
|
||
Inc(i);
|
||
b := Ord(str[i]);
|
||
if (b < 128) or (b > 191) then Exit(False);
|
||
Dec(bits);
|
||
end;
|
||
end;
|
||
Inc(i);
|
||
end;
|
||
end;
|
||
|
||
function IsUTF8(const B: TBytes): Boolean;
|
||
var
|
||
i, Len: Integer;
|
||
C: Byte;
|
||
nNeed: Integer;
|
||
bMultiByte: Boolean;
|
||
begin
|
||
Result := False;
|
||
Len := Length(B);
|
||
if Len = 0 then
|
||
Exit;
|
||
|
||
i := 0;
|
||
nNeed := 0;
|
||
bMultiByte := False;
|
||
|
||
while i < Len do
|
||
begin
|
||
C := B[i];
|
||
|
||
if nNeed = 0 then
|
||
begin
|
||
// 0xxxxxxx (ASCII)
|
||
if (C and $80) = 0 then
|
||
begin
|
||
Inc(i);
|
||
Continue;
|
||
end;
|
||
|
||
// 110xxxxx (2바이트 시작)
|
||
if (C and $E0) = $C0 then
|
||
begin
|
||
nNeed := 1;
|
||
bMultiByte := True;
|
||
end
|
||
// 1110xxxx (3바이트 시작)
|
||
else if (C and $F0) = $E0 then
|
||
begin
|
||
nNeed := 2;
|
||
bMultiByte := True;
|
||
end
|
||
// 11110xxx (4바이트 시작)
|
||
else if (C and $F8) = $F0 then
|
||
begin
|
||
nNeed := 3;
|
||
bMultiByte := True;
|
||
end
|
||
else
|
||
Exit(False); // UTF-8 규칙 위반
|
||
end
|
||
else
|
||
begin
|
||
// 10xxxxxx (연속 바이트)
|
||
if (C and $C0) <> $80 then
|
||
Exit(False);
|
||
Dec(nNeed);
|
||
end;
|
||
|
||
Inc(i);
|
||
end;
|
||
|
||
// 모든 멀티바이트 시퀀스가 완결되었고,
|
||
// 최소 1개 이상의 멀티바이트 문자가 있었을 때만 UTF-8이라고 판단
|
||
Result := (nNeed = 0) and bMultiByte;
|
||
end;
|
||
|
||
function LastIndexOf(const sFind, sSource: string): Integer;
|
||
var
|
||
i, nFindLen, nSrcLen: Integer;
|
||
begin
|
||
Result := 0;
|
||
nFindLen := Length(sFind);
|
||
nSrcLen := Length(sSource);
|
||
if nFindLen > nSrcLen then
|
||
exit;
|
||
for i := nSrcLen - nFindLen downto 1 do
|
||
begin
|
||
if Copy(sSource, i, nFindLen) = sFind then
|
||
begin
|
||
Result := i;
|
||
exit;
|
||
end;
|
||
end;
|
||
end;
|
||
|
||
end.
|