BSOne.SFC/Tocsg.Lib/VCL/Tocsg.Strings.pas

663 lines
16 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{*******************************************************}
{ }
{ 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.