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