dcstrutils.pas 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491
  1. {
  2. Double Commander
  3. -------------------------------------------------------------------------
  4. Useful functions dealing with strings.
  5. Copyright (C) 2006-2025 Alexander Koblov ([email protected])
  6. Copyright (C) 2012 Przemyslaw Nagay ([email protected])
  7. This program is free software; you can redistribute it and/or modify
  8. it under the terms of the GNU General Public License as published by
  9. the Free Software Foundation; either version 2 of the License, or
  10. (at your option) any later version.
  11. This program is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. GNU General Public License for more details.
  15. You should have received a copy of the GNU General Public License
  16. along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. }
  18. unit DCStrUtils;
  19. {$mode objfpc}{$H+}
  20. interface
  21. uses
  22. Classes, SysUtils, DCBasicTypes, LazUtf8;
  23. const
  24. NoQuotesSpecialChars = [' ', '"', '''', '(', ')', ':', '&', '!', '$', '*', '?', '=', '`', '\', '|', ';', #10];
  25. DoubleQuotesSpecialChars = ['$', '\', '`', '"', #10];
  26. type
  27. TPathType = (ptNone, ptRelative, ptAbsolute);
  28. {en
  29. Checks if StringToCheck contains any of the single characters in
  30. PossibleCharacters. Only ASCII can be searched.
  31. }
  32. function ContainsOneOf(StringToCheck: String; PossibleCharacters: String): Boolean;
  33. {en
  34. Convert known directory separators to the current directory separator.
  35. }
  36. function NormalizePathDelimiters(const Path: String): String;
  37. {en
  38. Convert known directory separators to user defined directory separator.
  39. }
  40. function ReplaceDirectorySeparator(const Path: String; const Separator : Char): String;
  41. {en
  42. Get last directory name in path
  43. @returns(Last directory name in path)
  44. }
  45. function GetLastDir(Path : String) : String;
  46. {en
  47. Retrieves the root directory for a path.
  48. @param(sPath Absolute path to a directory or a file.)
  49. @returns(Root directory or an empty string if the path is not absolute.)
  50. }
  51. function GetRootDir(sPath : String) : String;
  52. {en
  53. Retrieves parent directory for a path (removes the last subdirectory in the path).
  54. @param(sPath Absolute or relative path to a directory or a file.)
  55. @returns(Parent directory or an empty string
  56. if the path does not have a parent directory.)
  57. }
  58. function GetParentDir(sPath : String) : String;
  59. {en
  60. Gets the deepest (longest) path that exist.
  61. }
  62. function GetDeepestExistingPath(const sPath : String) : String;
  63. function GetSplitFileName(var sFileName, sPath : String) : String;
  64. function MakeFileName(const sPath, sFileNameDef : String) : String;
  65. {en
  66. Split path into list of directories
  67. @param(DirName Path)
  68. @param(Dirs List of directories names)
  69. @returns(The function returns the number of directories found, or -1
  70. if none were found.)
  71. }
  72. function GetDirs (DirName : String; var Dirs : TStringList) : Longint;
  73. {en
  74. Get absolute file name from relative file name
  75. @param(sPath Current path)
  76. @param(sRelativeFileName Relative file name)
  77. @returns(Absolute file name)
  78. }
  79. function GetAbsoluteFileName(const sPath, sRelativeFileName : String) : String;
  80. {en
  81. Checks if a path to a directory or file is absolute or relative.
  82. @returns(ptNone if a path is just a directory or file name (MyDir)
  83. ptRelative if a path is relative (MyDir/MySubDir)
  84. ptAbsolute if a path is absolute) (/root/MyDir)
  85. }
  86. function GetPathType(const sPath : String): TPathType;
  87. function ExtractFileDirEx(const FileName: String): String;
  88. function ExtractFilePathEx(const FileName: String): String;
  89. function ExtractFileNameEx(const FileName: String): String;
  90. {en
  91. Get file name without path and extension
  92. @param(FileName File name)
  93. @returns(File name without path and extension)
  94. }
  95. function ExtractOnlyFileName(const FileName: string): string;
  96. {en
  97. Get file extension without the '.' at the front.
  98. }
  99. function ExtractOnlyFileExt(const FileName: string): string;
  100. {en
  101. Remove file extension with the '.' from file name.
  102. }
  103. function RemoveFileExt(const FileName: String): String;
  104. function ReplaceInvalidChars(const FileName: String): String;
  105. function RemoveInvalidCharsFromFileName(const FileName: String): String;
  106. function ContainsWildcards(const Path: String): Boolean;
  107. {en
  108. Expands an absolute file path by removing all relative references.
  109. Processes '/../' and '/./'.
  110. Example: /home/user/files/../somedirectory/./file.txt
  111. = /home/user/somedirectory/file.txt
  112. @param(Path path to expand.)
  113. }
  114. function ExpandAbsolutePath(const Path: String): String;
  115. function HasPathInvalidCharacters(Path: String): Boolean;
  116. {en
  117. Checks if a file or directory belongs in the specified path.
  118. Only strings are compared, no file-system checks are done.
  119. @param(sBasePath
  120. Absolute path where the path to check should be in.)
  121. @param(sPathToCheck
  122. Absolute path to file or directory to check.)
  123. @param(AllowSubDirs
  124. If @true, allows the sPathToCheck to point to a file or directory in some subdirectory of sBasePath.
  125. If @false, only allows the sPathToCheck to point directly to a file or directory in sBasePath.)
  126. @param(AllowSame
  127. If @true, returns @true if sBasePath = sPathToCheck.
  128. If @false, returns @false if sBasePath = sPathToCheck.)
  129. @return(@true if sPathToCheck points to a directory or file in sBasePath.
  130. @false otherwise.)
  131. Examples:
  132. IsInPath('/home', '/home/somedir/somefile', True, False) = True
  133. IsInPath('/home', '/home/somedir/somefile', False, False) = False
  134. IsInPath('/home', '/home/somedir/', False, False) = True
  135. IsInPath('/home', '/home', False, False) = False
  136. IsInPath('/home', '/home', False, True) = True
  137. }
  138. function IsInPath(sBasePath : String; sPathToCheck : String;
  139. AllowSubDirs : Boolean; AllowSame : Boolean) : Boolean;
  140. {en
  141. Changes a path to be relative to some parent directory.
  142. @param(sPrefix
  143. Absolute path that is a parent of sPath.)
  144. @param(sPath
  145. Path to change. Must be a subpath of sPrefix, otherwise no change is made.)
  146. Examples:
  147. ExtractDirLevel('/home', '/home/somedir/somefile') = '/somedir/somefile'
  148. }
  149. function ExtractDirLevel(const sPrefix, sPath: String): String;
  150. {en
  151. Adds a path delimiter at the beginning of the string, if it not exists.
  152. }
  153. function IncludeFrontPathDelimiter(s: String): String;
  154. {en
  155. Removes a path delimiter at the beginning of the string, if it exists.
  156. }
  157. function ExcludeFrontPathDelimiter(s: String): String;
  158. {en
  159. Removes a path delimiter at the ending of the string, if it exists.
  160. Doesn't remove path delimiter if it is the only character in the path (root dir),
  161. so it is safer to use than ExcludeTrailingPathDelimiter, especially on Unix.
  162. }
  163. function ExcludeBackPathDelimiter(const Path: String): String;
  164. {en
  165. Return position of character in string begun from start position
  166. @param(C character)
  167. @param(S String)
  168. @param(StartPos Start position)
  169. @returns(Position of character in string)
  170. }
  171. function CharPos(C: Char; const S: string; StartPos: Integer = 1): Integer;
  172. {en
  173. Return position of any of tag-characters in string T in string S begun from start position
  174. @param(T set of characters)
  175. @param(S String)
  176. @param(StartPos Start position)
  177. @param(SearchBackward set @True if need search backwards)
  178. @returns(Position of character in string)
  179. }
  180. function TagPos(T: string; const S: string; StartPos: Integer;SearchBackward: boolean=False): Integer;
  181. {en
  182. Split file name on name and extension
  183. @param(sFileName File name)
  184. @param(n Name)
  185. @param(e Extension)
  186. }
  187. procedure DivFileName(const sFileName:String; out n,e:String);
  188. {en
  189. Split ';' separated path list to array
  190. @param(Path Path list to split)
  191. @returns(Path array)
  192. }
  193. function SplitPath(const Path: String): TDynamicStringArray;
  194. {en
  195. Split file mask on name mask and extension mask
  196. @param(DestMask File mask)
  197. @param(DestNameMask Name mask)
  198. @param(DestExtMask Extension mask)
  199. }
  200. procedure SplitFileMask(const DestMask: String; out DestNameMask: String; out DestExtMask: String);
  201. {en
  202. Apply name and extension mask to the file name
  203. @param(aFileName File name)
  204. @param(NameMask Name mask)
  205. @param(ExtMask Extension mask)
  206. }
  207. function ApplyRenameMask(aFileName: String; NameMask: String; ExtMask: String): String;
  208. {en
  209. Get count of character in string
  210. @param(Char Character)
  211. @param(S String)
  212. @returns(Count of character)
  213. }
  214. function NumCountChars(const Char: Char; const S: String): Integer;
  215. {en
  216. Trim the leading and ending spaces
  217. }
  218. function TrimPath(const Path: String): String;
  219. {en
  220. Remove last line ending in text
  221. @param(sText Text)
  222. @param(TextLineBreakStyle Text line break style)
  223. }
  224. function TrimRightLineEnding(const sText: String; TextLineBreakStyle: TTextLineBreakStyle): String;
  225. function mbCompareText(const s1, s2: String): PtrInt;
  226. function StrNewW(const mbString: String): PWideChar;
  227. procedure StrDisposeW(var pStr : PWideChar);
  228. function StrLCopyW(Dest, Source: PWideChar; MaxLen: SizeInt): PWideChar;
  229. function StrPCopyW(Dest: PWideChar; const Source: WideString): PWideChar;
  230. function StrPLCopyW(Dest: PWideChar; const Source: WideString; MaxLen: Cardinal): PWideChar;
  231. function RPos(const Substr : UnicodeString; const Source : UnicodeString) : Integer; overload;
  232. {en
  233. Checks if a string begins with another string.
  234. @returns(@true if StringToCheck begins with StringToMatch.
  235. StringToCheck may be longer than StringToMatch.)
  236. }
  237. function StrBegins(const StringToCheck, StringToMatch: String): Boolean;
  238. {en
  239. Checks if a string ends with another string.
  240. @returns(@true if StringToCheck ends with StringToMatch.
  241. StringToCheck may be longer than StringToMatch.)
  242. }
  243. function StrEnds(const StringToCheck, StringToMatch: String): Boolean;
  244. {en
  245. Adds a string to another string. If the source string is not empty adds
  246. a separator before adding the string.
  247. }
  248. procedure AddStrWithSep(var SourceString: String; const StringToAdd: String; const Separator: Char = ' ');
  249. procedure AddStrWithSep(var SourceString: String; const StringToAdd: String; const Separator: String);
  250. procedure ParseLineToList(sLine: String; ssItems: TStrings);
  251. function ParseLineToFileFilter(sFilterPair: array of string): string;
  252. {en
  253. Convert a number specified as an octal number to it's decimal value.
  254. @param(Value Octal number as string)
  255. @returns(Decimal number)
  256. }
  257. function OctToDec(Value: String): LongInt;
  258. {en
  259. Convert a number specified as an decimal number to it's octal value.
  260. @param(Value Decimal number)
  261. @returns(Octal number as string)
  262. }
  263. function DecToOct(Value: LongInt): String;
  264. procedure AddString(var anArray: TDynamicStringArray; const sToAdd: String);
  265. {en
  266. Splits a string into different parts delimited by the specified delimiter character.
  267. }
  268. function SplitString(const S: String; Delimiter: AnsiChar): TDynamicStringArray;
  269. {en
  270. Checks if the second array is the beginning of first.
  271. If BothWays is @true then also checks the other way around,
  272. if the first array is the beginning of second.
  273. For Array1=[1,2] Array2=[1,2] returns @true.
  274. For Array1=[1,2,...] Array2=[1,2] returns @true.
  275. For Array1=[1,3,...] Array2=[1,2] returns @false.
  276. If BothWays = True then also
  277. For Array1=[1] Array2=[1,2] returns @true.
  278. For Array1=[1] Array2=[2] returns @false.
  279. }
  280. function ArrBegins(const Array1, Array2: array of String; BothWays: Boolean): Boolean;
  281. function ArrayToString(const anArray: TDynamicStringArray; const Separator: Char = ' '): String;
  282. {en
  283. Compares length and contents of the arrays.
  284. If lengths differ or individual elements differ returns @false, otherwise @true.
  285. }
  286. function Compare(const Array1, Array2: array of String): Boolean;
  287. {en
  288. Copies open array to dynamic array.
  289. }
  290. function CopyArray(const anArray: array of String): TDynamicStringArray;
  291. function ContainsOneOf(const ArrayToSearch, StringsToSearch: array of String): Boolean;
  292. function Contains(const ArrayToSearch: array of String; const StringToSearch: String): Boolean;
  293. procedure DeleteString(var anArray: TDynamicStringArray; const Index: Integer);
  294. procedure DeleteString(var anArray: TDynamicStringArray; const sToDelete: String);
  295. function GetArrayFromStrings(Strings: TStrings): TDynamicStringArray;
  296. procedure SetStringsFromArray(Strings: TStrings; const anArray: TDynamicStringArray);
  297. {en
  298. Replaces old value of Key or adds a new Key=NewValue string to the array.
  299. }
  300. procedure SetValue(var anArray: TDynamicStringArray; Key, NewValue: String);
  301. procedure SetValue(var anArray: TDynamicStringArray; Key: String; NewValue: Boolean);
  302. function ShortcutsToText(const Shortcuts: TDynamicStringArray): String;
  303. function GetDateTimeInStrEZSortable(DateTime:TDateTime):string;
  304. function WrapTextSimple(const S: String; MaxCol: Integer = 100): String;
  305. {en
  306. Escapes characters to be inserted between single quotes (')
  307. and passed to shell command line.
  308. The resulting string is not enclosed with '', only escaped.
  309. For example <cmd1> needs to be escaped with this function:
  310. sh -c '<cmd1>' "<cmd2>" <cmd3>
  311. }
  312. function EscapeSingleQuotes(const Str: String): String;
  313. {en
  314. Escapes characters to be inserted between double quotes (")
  315. and passed to shell command line.
  316. The resulting string is not enclosed with "", only escaped.
  317. For example <cmd2> needs to be escaped with this function:
  318. sh -c '<cmd1>' "<cmd2>" <cmd3>
  319. }
  320. function EscapeDoubleQuotes(const Str: String): String;
  321. {en
  322. Escapes characters to be passed to shell command line when no quoting is used.
  323. For example <cmd3> needs to be escaped with this function:
  324. sh -c '<cmd1>' "<cmd2>" <cmd3>
  325. }
  326. function EscapeNoQuotes(const Str: String): String;
  327. {en
  328. Reads a line of text from a string
  329. @param(Value Input text string)
  330. @param(S Output text line)
  331. @param(N Current position in the input text string)
  332. @returns(@true if line-ending found, @false otherwise)
  333. }
  334. function GetNextLine(const Value: String; var S: String; var N: Integer): Boolean;
  335. implementation
  336. uses
  337. DCOSUtils, DCConvertEncoding, StrUtils;
  338. function ReplaceDirectorySeparator(const Path: String; const Separator : Char): String;
  339. const
  340. AllowPathDelimiters : set of char = ['\','/'];
  341. var
  342. I : LongInt;
  343. begin
  344. Result := Path;
  345. if (Separator in AllowPathDelimiters) then
  346. begin
  347. for I:= 1 to Length(Path) do
  348. if Path[I] in AllowPathDelimiters then
  349. Result[I]:= Separator
  350. end
  351. end;
  352. function NormalizePathDelimiters(const Path: String): String;
  353. {$IFDEF UNIX}
  354. begin
  355. Result:= Path;
  356. end;
  357. {$ELSE}
  358. const
  359. AllowPathDelimiters : set of char = ['\','/'];
  360. var
  361. I : LongInt;
  362. uriPos : Integer;
  363. begin
  364. Result:= Path;
  365. // If path is not URI
  366. uriPos := Pos('://', Result);
  367. if (uriPos = 0)
  368. {$IF DEFINED(MSWINDOWS)}
  369. or ( (uriPos = 2) and (Path[1] in ['A'..'z']) )
  370. {$ENDIF} then
  371. begin
  372. for I:= 1 to Length(Path) do
  373. if Path[I] in AllowPathDelimiters then
  374. Result[I]:= DirectorySeparator;
  375. end;
  376. end;
  377. {$ENDIF}
  378. function GetLastDir(Path : String) : String;
  379. begin
  380. Result:= ExtractFileName(ExcludeTrailingPathDelimiter(Path));
  381. if Result = '' then
  382. Result:= ExtractFileDrive(Path);
  383. if Result = '' then
  384. Result:= PathDelim;
  385. end;
  386. function GetRootDir(sPath : String) : String;
  387. begin
  388. {$IF DEFINED(MSWINDOWS)}
  389. Result := ExtractFileDrive(sPath);
  390. if Result <> '' then
  391. Result := Result + PathDelim;
  392. {$ELSEIF DEFINED(UNIX)}
  393. Result := PathDelim; // Hardcoded
  394. {$ELSE}
  395. Result := '';
  396. {$ENDIF}
  397. end;
  398. function GetParentDir(sPath : String) : String;
  399. var
  400. i : Integer;
  401. begin
  402. Result := '';
  403. sPath := ExcludeTrailingPathDelimiter(sPath);
  404. // Start from one character before last.
  405. for i := length(sPath) - 1 downto 1 do
  406. if sPath[i] = DirectorySeparator then
  407. begin
  408. Result := Copy(sPath, 1, i);
  409. Break;
  410. end;
  411. end;
  412. function GetDeepestExistingPath(const sPath : String) : String;
  413. begin
  414. Result := sPath;
  415. while Result <> EmptyStr do
  416. begin
  417. if not mbDirectoryExists(Result) then
  418. Result := GetParentDir(Result)
  419. else
  420. Break;
  421. end;
  422. end;
  423. function GetSplitFileName(var sFileName, sPath : String) : String;
  424. begin
  425. if Pos(PathDelim, sFileName) <> 0 then
  426. begin
  427. Result := sFileName;
  428. sPath := ExtractFilePath(sFileName);
  429. sFileName := ExtractFileName(sFileName);
  430. end
  431. else
  432. Result := sPath + sFileName;
  433. end;
  434. function MakeFileName(const sPath, sFileNameDef : String) : String;
  435. begin
  436. Result:= ExtractFileName(ExcludeTrailingBackslash(sPath));
  437. if Result = EmptyStr then
  438. Result:= sFileNameDef;
  439. end;
  440. function GetDirs (DirName : String; var Dirs : TStringList) : Longint;
  441. var
  442. I : Longint;
  443. len : Integer;
  444. sDir : String;
  445. begin
  446. I:= 1;
  447. Result:= -1;
  448. len := Length(DirName);
  449. while I <= len do
  450. begin
  451. if DirName[I]=PathDelim then
  452. begin
  453. Inc(Result);
  454. sDir := Copy(DirName, 1, len - (len - I + 1));
  455. if dirs.IndexOf(sDir) < 0 then
  456. dirs.Add(sDir);
  457. end;
  458. Inc(I);
  459. end;
  460. if Result > -1 then inc(Result);
  461. end;
  462. function GetAbsoluteFileName(const sPath, sRelativeFileName : String) : String;
  463. begin
  464. case GetPathType(sRelativeFileName) of
  465. ptNone:
  466. Result := sPath + sRelativeFileName;
  467. ptRelative:
  468. Result := ExpandAbsolutePath(sPath + sRelativeFileName);
  469. ptAbsolute:
  470. Result := sRelativeFileName;
  471. end;
  472. end;
  473. function GetPathType(const sPath : String): TPathType;
  474. begin
  475. if sPath <> EmptyStr then
  476. begin
  477. {$IFDEF MSWINDOWS}
  478. { Absolute path in Windows }
  479. if { X:\... [Disk] ":" is reserved otherwise }
  480. ( Pos( DriveDelim, sPath ) > 0 ) or
  481. { \\... [UNC]
  482. \... [Root of current drive] }
  483. ( sPath[1] = PathDelim ) then
  484. {$ENDIF MSWINDOWS}
  485. {$IFDEF UNIX}
  486. { UNIX absolute paths start with a slash }
  487. if (sPath[1] = PathDelim) then
  488. {$ENDIF UNIX}
  489. Result := ptAbsolute
  490. else if ( Pos( PathDelim, sPath ) > 0 ) then
  491. Result := ptRelative
  492. else if (sPath = '..') then
  493. Result := ptRelative
  494. else
  495. Result := ptNone;
  496. end
  497. else
  498. Result := ptNone;
  499. end;
  500. function ExtractFileDirEx(const FileName: String): String;
  501. var
  502. i : longint;
  503. begin
  504. I := Length(FileName);
  505. while (I > 0) and not CharInSet(FileName[I],AllowDirectorySeparators) do
  506. Dec(I);
  507. if (I > 1) and CharInSet(FileName[I],AllowDirectorySeparators) and
  508. not CharInSet(FileName[I - 1],AllowDirectorySeparators) then
  509. Dec(I);
  510. Result := Copy(FileName, 1, I);
  511. end;
  512. function ExtractFilePathEx(const FileName: String): String;
  513. var
  514. i : longint;
  515. begin
  516. i := Length(FileName);
  517. while (i > 0) and not CharInSet(FileName[i],AllowDirectorySeparators) do
  518. Dec(i);
  519. If I>0 then
  520. Result := Copy(FileName, 1, i)
  521. else
  522. Result:='';
  523. end;
  524. function ExtractFileNameEx(const FileName: String): String;
  525. var
  526. i : longint;
  527. begin
  528. I := Length(FileName);
  529. while (I > 0) and not CharInSet(FileName[I],AllowDirectorySeparators) do
  530. Dec(I);
  531. Result := Copy(FileName, I + 1, MaxInt);
  532. end;
  533. function ExtractOnlyFileName(const FileName: string): string;
  534. var
  535. SOF : Boolean;
  536. I, Index : LongInt;
  537. EndSep : Set of Char;
  538. begin
  539. Index := MaxInt;
  540. // Find a dot index
  541. I := Length(FileName);
  542. EndSep:= AllowDirectorySeparators + AllowDriveSeparators + [ExtensionSeparator];
  543. while (I > 0) and not (FileName[I] in EndSep) do Dec(I);
  544. if (I > 0) and (FileName[I] = ExtensionSeparator) then
  545. begin
  546. SOF:= (I = 1) or (FileName[I - 1] in AllowDirectorySeparators);
  547. if (not SOF) or FirstDotAtFileNameStartIsExtension then
  548. Index := I
  549. end;
  550. // Find file name index
  551. EndSep := EndSep - [ExtensionSeparator];
  552. while (I > 0) and not (FileName[I] in EndSep) do Dec(I);
  553. Result := Copy(FileName, I + 1, Index - I - 1);
  554. end;
  555. function ExtractOnlyFileExt(const FileName: string): string;
  556. var
  557. I : LongInt;
  558. SOF : Boolean;
  559. EndSep : Set of Char;
  560. begin
  561. Result := EmptyStr;
  562. I := Length(FileName);
  563. EndSep:= AllowDirectorySeparators + AllowDriveSeparators + [ExtensionSeparator];
  564. while (I > 0) and not (FileName[I] in EndSep) do Dec(I);
  565. if (I > 0) and (FileName[I] = ExtensionSeparator) then
  566. begin
  567. SOF:= (I = 1) or (FileName[I - 1] in AllowDirectorySeparators);
  568. if (not SOF) or FirstDotAtFileNameStartIsExtension then
  569. Result := Copy(FileName, I + 1, MaxInt)
  570. end;
  571. end;
  572. function RemoveFileExt(const FileName: String): String;
  573. var
  574. I : LongInt;
  575. SOF : Boolean;
  576. EndSep : Set of Char;
  577. begin
  578. Result := FileName;
  579. I := Length(FileName);
  580. EndSep:= AllowDirectorySeparators + AllowDriveSeparators + [ExtensionSeparator];
  581. while (I > 0) and not (FileName[I] in EndSep) do Dec(I);
  582. if (I > 0) and (FileName[I] = ExtensionSeparator) then
  583. begin
  584. SOF:= (I = 1) or (FileName[I - 1] in AllowDirectorySeparators);
  585. if (not SOF) or FirstDotAtFileNameStartIsExtension then
  586. Result := Copy(FileName, 1, I - 1)
  587. end;
  588. end;
  589. function ContainsWildcards(const Path: String): Boolean;
  590. begin
  591. Result := ContainsOneOf(Path, '*?');
  592. end;
  593. function ReplaceInvalidChars(const FileName: String): String;
  594. const
  595. {$IFDEF MSWINDOWS}
  596. ForbiddenChars : set of char = [#00..#31, '<','>',':','"','/','|','?','*'];
  597. {$ELSE}
  598. ForbiddenChars : set of char = [#0];
  599. {$ENDIF}
  600. var
  601. I : LongInt;
  602. begin
  603. Result:= EmptyStr;
  604. for I:= 1 to Length(FileName) do
  605. begin
  606. if not (FileName[I] in ForbiddenChars) then
  607. Result:= Result + FileName[I]
  608. else
  609. Result+= '%' + HexStr(Ord(FileName[I]), 2);
  610. end;
  611. end;
  612. { RemoveInvalidCharsFromFileName }
  613. function RemoveInvalidCharsFromFileName(const FileName: String): String;
  614. const
  615. {$IFDEF MSWINDOWS}
  616. ForbiddenChars : set of char = [#00..#31, '<','>',':','"','/','\','|','?','*'];
  617. {$ELSE}
  618. ForbiddenChars : set of char = ['/'];
  619. {$ENDIF}
  620. var
  621. I : LongInt;
  622. begin
  623. Result:= '';
  624. for I:= 1 to Length(FileName) do
  625. if not (FileName[I] in ForbiddenChars) then
  626. Result:=Result+FileName[I];
  627. end;
  628. function ExpandAbsolutePath(const Path: String): String;
  629. const
  630. PATH_DELIM_POS = {$IFDEF MSWINDOWS}3{$ELSE}1{$ENDIF};
  631. var
  632. I, J: Integer;
  633. begin
  634. Result := Path;
  635. // Remove all references to '\.\'
  636. I := Pos(DirectorySeparator + '.' + DirectorySeparator, Result);
  637. while I <> 0 do
  638. begin
  639. Delete(Result, I, 2);
  640. I := Pos(DirectorySeparator + '.' + DirectorySeparator, Result, I);
  641. end;
  642. // Remove all references to '\..\'
  643. I := Pos(DirectorySeparator + '..' + DirectorySeparator, Result);
  644. while I <> 0 do
  645. begin
  646. J := Pred(I);
  647. while (J > 0) and (Result[J] <> DirectorySeparator) do Dec (J);
  648. Delete(Result, J + 1, I - J + 3);
  649. I := Pos(DirectorySeparator + '..' + DirectorySeparator, Result);
  650. end;
  651. // Remove a reference to '\..' at the end of line
  652. if StrEnds(Result, DirectorySeparator + '..') then
  653. begin
  654. J := Length(Result) - 3;
  655. while (J > 0) and (Result[J] <> DirectorySeparator) do Dec(J);
  656. if (J = 0) then
  657. Result := EmptyStr
  658. else if (J > PATH_DELIM_POS) then
  659. Delete(Result, J, MaxInt)
  660. else
  661. Delete(Result, J + 1, MaxInt);
  662. end;
  663. // Remove a reference to '\.' at the end of line
  664. if Length(Result) = 1 then
  665. begin
  666. if Result[1] = '.' then Result := EmptyStr;
  667. end
  668. else if StrEnds(Result, DirectorySeparator + '.') then
  669. begin
  670. if Length(Result) = (PATH_DELIM_POS + 1) then
  671. Delete(Result, Length(Result), 1)
  672. else
  673. Delete(Result, Length(Result) - 1, 2);
  674. end;
  675. end;
  676. function HasPathInvalidCharacters(Path: String): Boolean;
  677. begin
  678. Result := ContainsOneOf(Path, '*?');
  679. end;
  680. function IsInPath(sBasePath : String; sPathToCheck : String;
  681. AllowSubDirs : Boolean; AllowSame : Boolean) : Boolean;
  682. var
  683. BasePathLength, PathToCheckLength: Integer;
  684. DelimiterPos: Integer;
  685. begin
  686. if sBasePath = '' then Exit(False);
  687. sBasePath := IncludeTrailingPathDelimiter(sBasePath);
  688. BasePathLength := UTF8Length(sBasePath);
  689. PathToCheckLength := UTF8Length(sPathToCheck);
  690. if PathToCheckLength > BasePathLength then
  691. begin
  692. if mbCompareFileNames(UTF8Copy(sPathToCheck, 1, BasePathLength), sBasePath) then
  693. begin
  694. if AllowSubDirs then
  695. Result := True
  696. else
  697. begin
  698. // Additionally check if the remaining path is a relative path.
  699. // Look for a path delimiter in the middle of the filepath.
  700. sPathToCheck := UTF8Copy(sPathToCheck, 1 + BasePathLength,
  701. PathToCheckLength - BasePathLength);
  702. DelimiterPos := UTF8Pos(DirectorySeparator, sPathToCheck);
  703. // If no delimiter was found or it was found at then end (directories
  704. // may end with it), then the 'sPathToCheck' is in 'sBasePath'.
  705. Result := (DelimiterPos = 0) or (DelimiterPos = PathToCheckLength - BasePathLength);
  706. end;
  707. end
  708. else
  709. Result := False;
  710. end
  711. else
  712. Result := AllowSame and
  713. (((PathToCheckLength = BasePathLength) and
  714. (mbCompareFileNames(sPathToCheck, sBasePath))) or
  715. ((PathToCheckLength = BasePathLength - 1) and
  716. (mbCompareFileNames(UTF8Copy(sBasePath, 1, PathToCheckLength), sPathToCheck))));
  717. end;
  718. function ExtractDirLevel(const sPrefix, sPath: String): String;
  719. var
  720. PrefixLength: Integer;
  721. begin
  722. if IsInPath(sPrefix, sPath, True, True) then
  723. begin
  724. PrefixLength := Length(sPrefix);
  725. Result := Copy(sPath, 1 + PrefixLength, Length(sPath) - PrefixLength)
  726. end
  727. else
  728. Result := sPath;
  729. end;
  730. function IncludeFrontPathDelimiter(s: String): String;
  731. begin
  732. if (Length(s) > 0) and (s[1] = PathDelim) then
  733. Result:= s
  734. else
  735. Result:= PathDelim + s;
  736. end;
  737. function ExcludeFrontPathDelimiter(s: String): String;
  738. begin
  739. if (Length(s) > 0) and (s[1] = PathDelim) then
  740. Result := Copy(s, 2, Length(s) - 1)
  741. else
  742. Result := s;
  743. end;
  744. function ExcludeBackPathDelimiter(const Path: String): String;
  745. var
  746. L: Integer;
  747. begin
  748. L:= Length(Path);
  749. if (L > 1) and (Path[L] in AllowDirectorySeparators) and (Path[L - 1] <> DriveSeparator) then
  750. Result:= Copy(Path, 1, L - 1)
  751. else
  752. Result:= Path;
  753. end;
  754. procedure DivFileName(const sFileName:String; out n,e:String);
  755. var
  756. i:Integer;
  757. begin
  758. for i:= length(sFileName) downto 1 do
  759. if sFileName[i]='.' then
  760. begin
  761. // if i>1 then // hidden files??
  762. e:=Copy(sFileName,i,Length(sFileName)-i+1);
  763. n:=Copy(sFileName,1,i-1);
  764. Exit;
  765. end;
  766. e:='';
  767. n:=sFileName;
  768. end;
  769. function SplitPath(const Path: String): TDynamicStringArray;
  770. const
  771. cDelta = {$IF DEFINED(UNIX)}1{$ELSE}2{$ENDIF};
  772. cDelimiter = {$IF DEFINED(UNIX)}'/'{$ELSE}':'{$ENDIF};
  773. var
  774. L, F: Integer;
  775. S: Integer = 1;
  776. begin
  777. L:= Length(Path);
  778. SetLength(Result, 0);
  779. for F:= 1 to L - cDelta do
  780. begin
  781. if (Path[F] = ';') and (Path[F + cDelta] = cDelimiter) then
  782. begin
  783. AddString(Result, Copy(Path, S, F - S));
  784. S:= F + 1;
  785. end;
  786. end;
  787. if S <= L then
  788. begin
  789. AddString(Result, Copy(Path, S, L - S + 1));
  790. end;
  791. end;
  792. procedure SplitFileMask(const DestMask: String; out DestNameMask: String; out DestExtMask: String);
  793. var
  794. iPos: LongInt;
  795. begin
  796. // Special case for mask that contains '*.*' ('*.*.old' for example)
  797. iPos:= Pos('*.*', DestMask);
  798. if (iPos = 0) then
  799. DivFileName(DestMask, DestNameMask, DestExtMask)
  800. else
  801. begin
  802. DestNameMask := Copy(DestMask, 1, iPos);
  803. DestExtMask := Copy(DestMask, iPos + 1, MaxInt);
  804. end;
  805. // Treat empty mask as '*.*'.
  806. if (DestNameMask = '') and (DestExtMask = '') then
  807. begin
  808. DestNameMask := '*';
  809. DestExtMask := '.*';
  810. end;
  811. end;
  812. function ApplyRenameMask(aFileName: String; NameMask: String; ExtMask: String): String;
  813. function ApplyMask(const TargetString, Mask: String): String;
  814. var
  815. I: Integer;
  816. ALen: Integer;
  817. begin
  818. Result:= EmptyStr;
  819. ALen:= UTF8Length(TargetString);
  820. for I:= 1 to Length(Mask) do
  821. begin
  822. if Mask[I] = '?' then
  823. begin
  824. if I <= ALen then
  825. Result:= Result + UTF8Copy(TargetString, I, 1)
  826. else
  827. Exit(TargetString);
  828. end
  829. else if Mask[I] = '*' then
  830. Result:= Result + UTF8Copy(TargetString, I, MaxInt)
  831. else
  832. Result:= Result + Mask[I];
  833. end;
  834. end;
  835. var
  836. sDstExt: String;
  837. sDstName: String;
  838. begin
  839. if ((NameMask = '*') and (ExtMask = '.*')) then
  840. Result := aFileName
  841. else
  842. begin
  843. DivFileName(aFileName, sDstName, sDstExt);
  844. sDstName := ApplyMask(sDstName, NameMask);
  845. sDstExt := ApplyMask(sDstExt, ExtMask);
  846. Result := sDstName;
  847. if sDstExt <> '.' then
  848. Result := Result + sDstExt;
  849. end;
  850. end;
  851. function CharPos(C: Char; const S: string; StartPos: Integer = 1): Integer;
  852. var
  853. sNewStr : String;
  854. begin
  855. if StartPos <> 1 then
  856. begin
  857. sNewStr := Copy(S, StartPos, Length(S) - StartPos + 1);
  858. Result := Pos(C, sNewStr);
  859. if Result <> 0 then
  860. Result := Result + StartPos - 1;
  861. end
  862. else
  863. Result := Pos(C, S);
  864. end;
  865. function TagPos(T: string; const S: string; StartPos: Integer;
  866. SearchBackward: boolean): Integer;
  867. var
  868. ch: AnsiChar;
  869. i, cnt: Integer;
  870. begin
  871. Result:= 0;
  872. i:= StartPos;
  873. if i = 0 then i:= 1;
  874. cnt:= UTF8Length(S);
  875. if SearchBackward then
  876. begin
  877. while (i > 0) do
  878. begin
  879. ch:= S[UTF8CharToByteIndex(PAnsiChar(S), Length(S), i)];
  880. if Pos(ch, T) = 0 then
  881. Dec(i)
  882. else
  883. Break;
  884. end;
  885. end
  886. else
  887. while (i <= cnt) do
  888. begin
  889. ch:= S[UTF8CharToByteIndex(PAnsiChar(S), Length(S), i)];
  890. if Pos(ch, T) = 0 then
  891. Inc(i)
  892. else
  893. Break;
  894. end;
  895. Result:= i;
  896. end;
  897. function NumCountChars(const Char: char; const S: String): Integer;
  898. var
  899. I : Integer;
  900. begin
  901. Result := 0;
  902. if Length(S) > 0 then
  903. for I := 1 to Length(S) do
  904. if S[I] = Char then Inc(Result);
  905. end;
  906. function TrimPath(const Path: String): String;
  907. const
  908. WhiteSpace = [#0..' '{$IFDEF MSWINDOWS},'.'{$ENDIF}];
  909. var
  910. Index: Integer;
  911. S: TStringArray;
  912. begin
  913. S:= TrimRightSet(Path, WhiteSpace).Split([PathDelim]);
  914. if Length(S) = 0 then
  915. Result:= EmptyStr
  916. else begin
  917. Result:= TrimRightSet(S[0], WhiteSpace);
  918. for Index := Low(S) + 1 to High(S) do
  919. begin
  920. Result+= PathDelim + TrimRightSet(S[Index], WhiteSpace);
  921. end;
  922. end;
  923. end;
  924. function TrimRightLineEnding(const sText: String; TextLineBreakStyle: TTextLineBreakStyle): String;
  925. const
  926. TextLineBreakArray: array[TTextLineBreakStyle] of Integer = (1, 2, 1);
  927. var
  928. I, L: Integer;
  929. begin
  930. L:= Length(sText);
  931. I:= TextLineBreakArray[TextLineBreakStyle];
  932. Result:= Copy(sText, 1, L - I); // Copy without last line ending
  933. end;
  934. function mbCompareText(const s1, s2: String): PtrInt; inline;
  935. begin
  936. // From 0.9.31 LazUtils can be used but this package does not exist in 0.9.30.
  937. // Result := LazUTF8.UTF8CompareText(s1, s2);
  938. Result := WideCompareText(CeUtf8ToUtf16(s1), CeUtf8ToUtf16(s2));
  939. end;
  940. function StrNewW(const mbString: String): PWideChar;
  941. var
  942. wsString: WideString;
  943. iLength: PtrInt;
  944. begin
  945. Result:= nil;
  946. wsString:= CeUtf8ToUtf16(mbString);
  947. iLength:= (Length(wsString) * SizeOf(WideChar)) + 1;
  948. Result:= GetMem(iLength);
  949. if Result <> nil then
  950. Move(PWideChar(wsString)^, Result^, iLength);
  951. end;
  952. procedure StrDisposeW(var pStr : PWideChar);
  953. begin
  954. FreeMem(pStr);
  955. pStr := nil;
  956. end;
  957. function StrLCopyW(Dest, Source: PWideChar; MaxLen: SizeInt): PWideChar;
  958. var
  959. I: SizeInt;
  960. begin
  961. Result := Dest;
  962. for I:= 0 to MaxLen - 1 do
  963. begin
  964. if Source^ = #0 then Break;
  965. Dest^ := Source^;
  966. Inc(Source);
  967. Inc(Dest);
  968. end;
  969. Dest^ := #0;
  970. end;
  971. function StrPCopyW(Dest: PWideChar; const Source: WideString): PWideChar;
  972. begin
  973. Result := StrLCopyW(Dest, PWideChar(Source), Length(Source));
  974. end;
  975. function StrPLCopyW(Dest: PWideChar; const Source: WideString; MaxLen: Cardinal): PWideChar;
  976. begin
  977. Result := StrLCopyW(Dest, PWideChar(Source), MaxLen);
  978. end;
  979. function RPos(const Substr: UnicodeString; const Source: UnicodeString): Integer;
  980. var
  981. c : WideChar;
  982. pc, pc2 : PWideChar;
  983. MaxLen, llen : Integer;
  984. begin
  985. Result:= 0;
  986. llen:= Length(SubStr);
  987. maxlen:= Length(Source);
  988. if (llen > 0) and (maxlen > 0) and (llen <= maxlen) then
  989. begin
  990. pc:= @Source[maxlen];
  991. pc2:= @Source[llen - 1];
  992. c:= Substr[llen];
  993. while pc >= pc2 do
  994. begin
  995. if (c = pc^) and
  996. (CompareByte(Substr[1], PByte(pc - llen + 1)^, llen * SizeOf(WideChar)) = 0) then
  997. begin
  998. Result:= PWideChar(pc - llen + 1) - PWideChar(@source[1]) + 1;
  999. Exit;
  1000. end;
  1001. Dec(pc);
  1002. end;
  1003. end;
  1004. end;
  1005. function StrBegins(const StringToCheck, StringToMatch: String): Boolean;
  1006. begin
  1007. Result := (Length(StringToMatch) > 0) and
  1008. (Length(StringToCheck) >= Length(StringToMatch)) and
  1009. (CompareChar(StringToCheck[1], StringToMatch[1], Length(StringToMatch)) = 0);
  1010. end;
  1011. function StrEnds(const StringToCheck, StringToMatch: String): Boolean;
  1012. begin
  1013. Result := (Length(StringToMatch) > 0) and
  1014. (Length(StringToCheck) >= Length(StringToMatch)) and
  1015. (CompareChar(StringToCheck[1 + Length(StringToCheck) - Length(StringToMatch)],
  1016. StringToMatch[1], Length(StringToMatch)) = 0);
  1017. end;
  1018. procedure AddStrWithSep(var SourceString: String; const StringToAdd: String; const Separator: Char);
  1019. begin
  1020. if (Length(SourceString) > 0) and (Length(StringToAdd) > 0) then
  1021. SourceString := SourceString + Separator;
  1022. SourceString := SourceString + StringToAdd;
  1023. end;
  1024. procedure AddStrWithSep(var SourceString: String; const StringToAdd: String; const Separator: String);
  1025. begin
  1026. if (Length(SourceString) > 0) and (Length(StringToAdd) > 0) then
  1027. SourceString := SourceString + Separator;
  1028. SourceString := SourceString + StringToAdd;
  1029. end;
  1030. procedure ParseLineToList(sLine: String; ssItems: TStrings);
  1031. var
  1032. xPos: Integer;
  1033. begin
  1034. ssItems.Clear;
  1035. while sLine <> '' do
  1036. begin
  1037. xPos:= Pos(';', sLine);
  1038. if xPos > 0 then
  1039. begin
  1040. ssItems.Add(Copy(sLine, 1, xPos - 1));
  1041. Delete(sLine, 1, xPos);
  1042. end
  1043. else
  1044. begin
  1045. ssItems.Add(sLine);
  1046. Exit;
  1047. end;
  1048. end;
  1049. end;
  1050. function ParseLineToFileFilter(sFilterPair: array of string): string;
  1051. var
  1052. iPairIndex: integer;
  1053. begin
  1054. result:='';
  1055. for iPairIndex := 0 to pred(length(sFilterPair) div 2) do
  1056. result := result + sFilterPair[iPairIndex*2] + '|' + sFilterPair[succ(iPairIndex*2)] + '|';
  1057. if length(result)>0 then
  1058. result := LeftStr(result, pred(length(result)));
  1059. end;
  1060. function ContainsOneOf(StringToCheck: String; PossibleCharacters: String): Boolean;
  1061. var
  1062. i, j: SizeInt;
  1063. pc : PChar;
  1064. begin
  1065. pc := Pointer(StringToCheck);
  1066. for i := 1 to Length(StringToCheck) do
  1067. begin
  1068. for j := 1 to Length(PossibleCharacters) do
  1069. if pc^ = PossibleCharacters[j] then
  1070. Exit(True);
  1071. Inc(pc);
  1072. end;
  1073. Result := False;
  1074. end;
  1075. function OctToDec(Value: String): LongInt;
  1076. var
  1077. I: Integer;
  1078. begin
  1079. Result:= 0;
  1080. for I:= 1 to Length(Value) do
  1081. Result:= Result * 8 + StrToInt(Copy(Value, I, 1));
  1082. end;
  1083. function DecToOct(Value: LongInt): String;
  1084. var
  1085. iMod: Integer;
  1086. begin
  1087. Result := '';
  1088. while Value >= 8 do
  1089. begin
  1090. iMod:= Value mod 8;
  1091. Value:= Value div 8;
  1092. Result:= IntToStr(iMod) + Result;
  1093. end;
  1094. Result:= IntToStr(Value) + Result;
  1095. end;
  1096. procedure AddString(var anArray: TDynamicStringArray; const sToAdd: String);
  1097. var
  1098. Len: Integer;
  1099. begin
  1100. Len := Length(anArray);
  1101. SetLength(anArray, Len + 1);
  1102. anArray[Len] := sToAdd;
  1103. end;
  1104. function SplitString(const S: String; Delimiter: AnsiChar): TDynamicStringArray;
  1105. var
  1106. Start: Integer = 1;
  1107. Len, Finish: Integer;
  1108. begin
  1109. Len:= Length(S);
  1110. SetLength(Result, 0);
  1111. for Finish:= 1 to Len do
  1112. begin
  1113. if S[Finish] = Delimiter then
  1114. begin
  1115. AddString(Result, Copy(S, Start, Finish - Start));
  1116. Start:= Finish + 1;
  1117. end;
  1118. end;
  1119. if Start <= Len then
  1120. begin
  1121. AddString(Result, Copy(S, Start, Len - Start + 1));
  1122. end;
  1123. end;
  1124. function ArrBegins(const Array1, Array2: array of String; BothWays: Boolean): Boolean;
  1125. var
  1126. Len1, Len2: Integer;
  1127. i: Integer;
  1128. begin
  1129. Len1 := Length(Array1);
  1130. Len2 := Length(Array2);
  1131. if not BothWays and (Len1 < Len2) then
  1132. Result := False
  1133. else
  1134. begin
  1135. if Len1 > Len2 then
  1136. Len1 := Len2;
  1137. for i := 0 to Len1 - 1 do
  1138. if Array1[i] <> Array2[i] then
  1139. Exit(False);
  1140. Result := True;
  1141. end;
  1142. end;
  1143. function ArrayToString(const anArray: TDynamicStringArray; const Separator: Char): String;
  1144. var
  1145. i: Integer;
  1146. begin
  1147. Result := '';
  1148. for i := Low(anArray) to High(anArray) do
  1149. AddStrWithSep(Result, anArray[i], Separator);
  1150. end;
  1151. function Compare(const Array1, Array2: array of String): Boolean;
  1152. var
  1153. Len1, Len2: Integer;
  1154. i: Integer;
  1155. begin
  1156. Len1 := Length(Array1);
  1157. Len2 := Length(Array2);
  1158. if Len1 <> Len2 then
  1159. Result := False
  1160. else
  1161. begin
  1162. for i := 0 to Len1 - 1 do
  1163. if Array1[i] <> Array2[i] then
  1164. Exit(False);
  1165. Result := True;
  1166. end;
  1167. end;
  1168. function CopyArray(const anArray: array of String): TDynamicStringArray;
  1169. var
  1170. i: Integer;
  1171. begin
  1172. SetLength(Result, Length(anArray));
  1173. for i := Low(anArray) to High(anArray) do
  1174. Result[i] := anArray[i];
  1175. end;
  1176. function ContainsOneOf(const ArrayToSearch, StringsToSearch: array of String): Boolean;
  1177. var
  1178. i: Integer;
  1179. begin
  1180. for i := Low(StringsToSearch) to High(StringsToSearch) do
  1181. if Contains(ArrayToSearch, StringsToSearch[i]) then
  1182. Exit(True);
  1183. Result := False;
  1184. end;
  1185. function Contains(const ArrayToSearch: array of String; const StringToSearch: String): Boolean;
  1186. var
  1187. i: Integer;
  1188. begin
  1189. for i := Low(ArrayToSearch) to High(ArrayToSearch) do
  1190. if ArrayToSearch[i] = StringToSearch then
  1191. Exit(True);
  1192. Result := False;
  1193. end;
  1194. procedure DeleteString(var anArray: TDynamicStringArray; const Index: Integer);
  1195. var
  1196. Len: Integer;
  1197. i: Integer;
  1198. begin
  1199. Len := Length(anArray);
  1200. for i := Index + 1 to Len - 1 do
  1201. anArray[i - 1] := anArray[i];
  1202. SetLength(anArray, Len - 1);
  1203. end;
  1204. procedure DeleteString(var anArray: TDynamicStringArray; const sToDelete: String);
  1205. var
  1206. i: Integer;
  1207. begin
  1208. for i := Low(anArray) to High(anArray) do
  1209. if anArray[i] = sToDelete then
  1210. begin
  1211. DeleteString(anArray, i);
  1212. Exit;
  1213. end;
  1214. end;
  1215. function GetArrayFromStrings(Strings: TStrings): TDynamicStringArray;
  1216. var
  1217. LinesCount: Integer;
  1218. i: Integer;
  1219. begin
  1220. LinesCount := Strings.Count;
  1221. if LinesCount > 0 then
  1222. begin
  1223. if Strings[LinesCount-1] = '' then
  1224. Dec(LinesCount);
  1225. SetLength(Result, LinesCount);
  1226. for i := 0 to LinesCount - 1 do
  1227. Result[i] := Strings[i];
  1228. end;
  1229. end;
  1230. procedure SetStringsFromArray(Strings: TStrings; const anArray: TDynamicStringArray);
  1231. var
  1232. s: String;
  1233. begin
  1234. Strings.Clear;
  1235. for s in anArray do
  1236. Strings.Add(s);
  1237. end;
  1238. procedure SetValue(var anArray: TDynamicStringArray; Key, NewValue: String);
  1239. var
  1240. i: Integer;
  1241. begin
  1242. Key := Key + '=';
  1243. for i := Low(anArray) to High(anArray) do
  1244. if StrBegins(anArray[i], Key) then
  1245. begin
  1246. anArray[i] := Key + NewValue;
  1247. Exit;
  1248. end;
  1249. AddString(anArray, Key + NewValue);
  1250. end;
  1251. procedure SetValue(var anArray: TDynamicStringArray; Key: String; NewValue: Boolean);
  1252. begin
  1253. if NewValue then
  1254. SetValue(anArray, Key, 'true')
  1255. else
  1256. SetValue(anArray, Key, 'false');
  1257. end;
  1258. function ShortcutsToText(const Shortcuts: TDynamicStringArray): String;
  1259. begin
  1260. Result := ArrayToString(Shortcuts, ' ');
  1261. end;
  1262. { GetDateTimeInStrEZSortable: Return the date and time in string format with YYYY-MM-DD@HH-MM-SS
  1263. so it can be integrate in a filename. Also, because of the order of the terms, it make it
  1264. useful when things are sorted BECAUSE it will also sort by date/time at the same time}
  1265. function GetDateTimeInStrEZSortable(DateTime:TDateTime):string;
  1266. var
  1267. MyYear, MyMonth, MyDay, MyHour, MyMin, MySec, MyMilSec: word;
  1268. begin
  1269. DecodeDate(DateTime, MyYear, MyMonth, MyDay);
  1270. DecodeTime(DateTime, MyHour, MyMin, MySec, MyMilSec);
  1271. result:=Format('%d-%2.2d-%2.2d@%2.2d-%2.2d-%2.2d', [MyYear, MyMonth, MyDay, MyHour, MyMin, MySec]);
  1272. end;
  1273. function WrapTextSimple(const S: String; MaxCol: Integer): String;
  1274. var
  1275. Len, Index: Integer;
  1276. begin
  1277. Index:= 1;
  1278. Result:= EmptyStr;
  1279. Len:= UTF8Length(S);
  1280. while (Len > 0) do
  1281. begin
  1282. Result:= Result + UTF8Copy(S, Index, MaxCol) + LineEnding;
  1283. Inc(Index, MaxCol); Dec(Len, MaxCol);
  1284. end;
  1285. SetLength(Result, Length(Result) - Length(LineEnding));
  1286. end;
  1287. function EscapeString(const Str: String; const EscapeChars: TCharSet; const EscapeWith: String): String;
  1288. var
  1289. StartPos: Integer = 1;
  1290. CurPos: Integer = 1;
  1291. begin
  1292. Result := '';
  1293. while CurPos <= Length(Str) do
  1294. begin
  1295. if Str[CurPos] in EscapeChars then
  1296. begin
  1297. Result := Result + Copy(Str, StartPos, CurPos - StartPos) + EscapeWith;
  1298. // The character being quoted will be copied later.
  1299. StartPos := CurPos;
  1300. end;
  1301. Inc(CurPos);
  1302. end;
  1303. Result := Result + Copy(Str, StartPos, CurPos - StartPos);
  1304. end;
  1305. function EscapeSingleQuotes(const Str: String): String;
  1306. begin
  1307. // Single quotes are strong quotes - no special characters are recognized
  1308. // inside those quotes, so only ' needs to be escaped.
  1309. Result := EscapeString(Str, [''''], '''\''');
  1310. end;
  1311. function EscapeDoubleQuotes(const Str: String): String;
  1312. begin
  1313. // Double quotes are weak quotes and a few special characters are allowed
  1314. // which need to be escaped.
  1315. Result := EscapeString(Str, DoubleQuotesSpecialChars, '\');
  1316. end;
  1317. function EscapeNoQuotes(const Str: String): String;
  1318. begin
  1319. // When neither single nor double quotes are used several special characters
  1320. // need to be escaped with backslash (single character quote).
  1321. Result := EscapeString(Str, NoQuotesSpecialChars, '\');
  1322. end;
  1323. function GetNextLine(const Value: String; var S: String; var N: Integer): Boolean;
  1324. var
  1325. PS: PChar;
  1326. IP, L, P, K: Integer;
  1327. begin
  1328. P:= N;
  1329. S:= '';
  1330. Result:= False;
  1331. L:= Length(Value);
  1332. if ((L - P) < 0) then Exit;
  1333. if ((L - P) = 0) and (not (Value[P] in [#10, #13])) then Exit;
  1334. PS:= PChar(Value) + P - 1;
  1335. IP:= P;
  1336. while ((L - P) >= 0) and (not (PS^ in [#10, #13])) do
  1337. begin
  1338. P:= P + 1;
  1339. Inc(PS);
  1340. end;
  1341. K:= P;
  1342. // Point to character after #13
  1343. if (P <= L) and (Value[P] = #13) then
  1344. begin
  1345. Inc(P);
  1346. Result:= True;
  1347. end;
  1348. // Point to character after #10
  1349. if (P <= L) and (Value[P] = #10) then
  1350. begin
  1351. Inc(P);
  1352. Result:= True;
  1353. end;
  1354. if Result then
  1355. begin
  1356. N:= P;
  1357. SetLength(S, K - IP);
  1358. System.Move(Value[IP], Pointer(S)^, K - IP);
  1359. end;
  1360. end;
  1361. end.