fpmasks.pp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. {
  2. /***************************************************************************
  3. fpmasks.pas
  4. ---------
  5. Moved here from LCL
  6. ***************************************************************************/
  7. *****************************************************************************
  8. * *
  9. * This file is part of the Lazarus Component Library (LCL) *
  10. * *
  11. * See the file COPYING.modifiedLGPL.txt, included in this distribution, *
  12. * for details about the copyright. *
  13. * *
  14. * This program is distributed in the hope that it will be useful, *
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
  17. * *
  18. *****************************************************************************
  19. }
  20. unit FPMasks;
  21. {$mode objfpc}{$H+}
  22. interface
  23. uses
  24. // For Smart Linking: Do not use the LCL!
  25. Classes, SysUtils, Contnrs;
  26. type
  27. TMaskCharType = (mcChar, mcCharSet, mcAnyChar, mcAnyText);
  28. TCharSet = set of Char;
  29. PCharSet = ^TCharSet;
  30. TMaskChar = record
  31. case CharType: TMaskCharType of
  32. mcChar: (CharValue: Char);
  33. mcCharSet: (Negative: Boolean; SetValue: PCharSet);
  34. mcAnyChar, mcAnyText: ();
  35. end;
  36. TMaskString = record
  37. MinLength: Integer;
  38. MaxLength: Integer;
  39. Chars: Array of TMaskChar;
  40. end;
  41. { TMask }
  42. TMask = class
  43. private
  44. FMask: TMaskString;
  45. public
  46. constructor Create(const AValue: UTF8string);
  47. destructor Destroy; override;
  48. function Matches(const AFileName: UTF8string): Boolean;
  49. end;
  50. { TParseStringList }
  51. TParseStringList = class(TStringList)
  52. public
  53. constructor Create(const AText, ASeparators: UTF8string);
  54. end;
  55. { TMaskList }
  56. TMaskList = class
  57. private
  58. FMasks: TObjectList;
  59. function GetCount: Integer;
  60. function GetItem(Index: Integer): TMask;
  61. public
  62. constructor Create(const AValue: UTF8string; ASeparator: Char = ';');
  63. destructor Destroy; override;
  64. function Matches(const AFileName: UTF8string): Boolean;
  65. property Count: Integer read GetCount;
  66. property Items[Index: Integer]: TMask read GetItem;
  67. end;
  68. function MatchesMask(const FileName, Mask: UTF8string): Boolean;
  69. function MatchesMaskList(const FileName, Mask: UTF8string; Separator: Char = ';'): Boolean;
  70. implementation
  71. function MatchesMask(const FileName, Mask: UTF8string): Boolean;
  72. var
  73. AMask: TMask;
  74. begin
  75. AMask := TMask.Create(Mask);
  76. try
  77. Result := AMask.Matches(FileName);
  78. finally
  79. AMask.Free;
  80. end;
  81. end;
  82. function MatchesMaskList(const FileName, Mask: UTF8string; Separator: Char): Boolean;
  83. var
  84. AMaskList: TMaskList;
  85. begin
  86. AMaskList := TMaskList.Create(Mask, Separator);
  87. try
  88. Result := AMaskList.Matches(FileName);
  89. finally
  90. AMaskList.Free;
  91. end;
  92. end;
  93. { TMask }
  94. constructor TMask.Create(const AValue: UTF8string);
  95. var
  96. I: Integer;
  97. SkipAnyText: Boolean;
  98. procedure CharSetError;
  99. begin
  100. raise EConvertError.CreateFmt('Invalid charset %s', [AValue]);
  101. end;
  102. procedure AddAnyText;
  103. begin
  104. if SkipAnyText then
  105. begin
  106. Inc(I);
  107. Exit;
  108. end;
  109. SetLength(FMask.Chars, Length(FMask.Chars) + 1);
  110. FMask.Chars[High(FMask.Chars)].CharType := mcAnyText;
  111. FMask.MaxLength := MaxInt;
  112. SkipAnyText := True;
  113. Inc(I);
  114. end;
  115. procedure AddAnyChar;
  116. begin
  117. SkipAnyText := False;
  118. SetLength(FMask.Chars, Length(FMask.Chars) + 1);
  119. FMask.Chars[High(FMask.Chars)].CharType := mcAnyChar;
  120. Inc(FMask.MinLength);
  121. if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
  122. Inc(I);
  123. end;
  124. procedure AddCharSet;
  125. var
  126. CharSet: TCharSet;
  127. Valid: Boolean;
  128. C, Last: Char;
  129. begin
  130. SkipAnyText := False;
  131. SetLength(FMask.Chars, Length(FMask.Chars) + 1);
  132. FMask.Chars[High(FMask.Chars)].CharType := mcCharSet;
  133. Inc(I);
  134. if (I <= Length(AValue)) and (AValue[I] = '!') then
  135. begin
  136. FMask.Chars[High(FMask.Chars)].Negative := True;
  137. Inc(I);
  138. end
  139. else FMask.Chars[High(FMask.Chars)].Negative := False;
  140. Last := '-';
  141. CharSet := [];
  142. Valid := False;
  143. while I <= Length(AValue) do
  144. begin
  145. case AValue[I] of
  146. '-':
  147. begin
  148. if Last = '-' then CharSetError;
  149. Inc(I);
  150. if (I > Length(AValue)) then CharSetError;
  151. //DebugLn('Set: ' + Last + '-' + UpCase(AValue[I]));
  152. for C := Last to UpCase(AValue[I]) do Include(CharSet, C);
  153. Inc(I);
  154. end;
  155. ']':
  156. begin
  157. Valid := True;
  158. Break;
  159. end;
  160. else
  161. begin
  162. Last := UpCase(AValue[I]);
  163. Include(CharSet, Last);
  164. Inc(I);
  165. end;
  166. end;
  167. end;
  168. if (not Valid) or (CharSet = []) then CharSetError;
  169. New(FMask.Chars[High(FMask.Chars)].SetValue);
  170. FMask.Chars[High(FMask.Chars)].SetValue^ := CharSet;
  171. Inc(FMask.MinLength);
  172. if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
  173. Inc(I);
  174. end;
  175. procedure AddChar;
  176. begin
  177. SkipAnyText := False;
  178. SetLength(FMask.Chars, Length(FMask.Chars) + 1);
  179. with FMask.Chars[High(FMask.Chars)] do
  180. begin
  181. CharType := mcChar;
  182. CharValue := UpCase(AValue[I]);
  183. end;
  184. Inc(FMask.MinLength);
  185. if FMask.MaxLength < MaxInt then Inc(FMask.MaxLength);
  186. Inc(I);
  187. end;
  188. begin
  189. SetLength(FMask.Chars, 0);
  190. FMask.MinLength := 0;
  191. FMask.MaxLength := 0;
  192. SkipAnyText := False;
  193. I := 1;
  194. while I <= Length(AValue) do
  195. begin
  196. case AValue[I] of
  197. '*': AddAnyText;
  198. '?': AddAnyChar;
  199. '[': AddCharSet;
  200. else AddChar;
  201. end;
  202. end;
  203. end;
  204. destructor TMask.Destroy;
  205. var
  206. I: Integer;
  207. begin
  208. for I := 0 to High(FMask.Chars) do
  209. if FMask.Chars[I].CharType = mcCharSet then
  210. Dispose(FMask.Chars[I].SetValue);
  211. inherited Destroy;
  212. end;
  213. function TMask.Matches(const AFileName: UTF8string): Boolean;
  214. var
  215. L: Integer;
  216. S: UTF8string;
  217. function MatchToEnd(MaskIndex, CharIndex: Integer): Boolean;
  218. var
  219. I, J: Integer;
  220. begin
  221. Result := False;
  222. for I := MaskIndex to High(FMask.Chars) do
  223. begin
  224. case FMask.Chars[I].CharType of
  225. mcChar:
  226. begin
  227. if CharIndex > L then Exit;
  228. //DebugLn('Match ' + S[CharIndex] + '<?>' + FMask.Chars[I].CharValue);
  229. if S[CharIndex] <> FMask.Chars[I].CharValue then Exit;
  230. Inc(CharIndex);
  231. end;
  232. mcCharSet:
  233. begin
  234. if CharIndex > L then Exit;
  235. if FMask.Chars[I].Negative xor
  236. (S[CharIndex] in FMask.Chars[I].SetValue^) then Inc(CharIndex)
  237. else Exit;
  238. end;
  239. mcAnyChar:
  240. begin
  241. if CharIndex > L then Exit;
  242. Inc(CharIndex);
  243. end;
  244. mcAnyText:
  245. begin
  246. if I = High(FMask.Chars) then
  247. begin
  248. Result := True;
  249. Exit;
  250. end;
  251. for J := CharIndex to L do
  252. if MatchToEnd(I + 1, J) then
  253. begin
  254. Result := True;
  255. Exit;
  256. end;
  257. end;
  258. end;
  259. end;
  260. Result := CharIndex > L;
  261. end;
  262. begin
  263. Result := False;
  264. L := Length(AFileName);
  265. if L = 0 then
  266. begin
  267. if FMask.MinLength = 0 then Result := True;
  268. Exit;
  269. end;
  270. if (L < FMask.MinLength) or (L > FMask.MaxLength) then Exit;
  271. S := UpperCase(AFileName);
  272. Result := MatchToEnd(0, 1);
  273. end;
  274. { TParseStringList }
  275. constructor TParseStringList.Create(const AText, ASeparators: UTF8string);
  276. var
  277. I, S: Integer;
  278. begin
  279. inherited Create;
  280. S := 1;
  281. for I := 1 to Length(AText) do
  282. begin
  283. if Pos(AText[I], ASeparators) > 0 then
  284. begin
  285. if I > S then Add(Copy(AText, S, I - S));
  286. S := I + 1;
  287. end;
  288. end;
  289. if Length(AText) >= S then Add(Copy(AText, S, Length(AText) - S + 1));
  290. end;
  291. { TMaskList }
  292. function TMaskList.GetItem(Index: Integer): TMask;
  293. begin
  294. Result := TMask(FMasks.Items[Index]);
  295. end;
  296. function TMaskList.GetCount: Integer;
  297. begin
  298. Result := FMasks.Count;
  299. end;
  300. constructor TMaskList.Create(const AValue: UTF8string; ASeparator: Char);
  301. var
  302. S: TParseStringList;
  303. I: Integer;
  304. begin
  305. FMasks := TObjectList.Create(True);
  306. S := TParseStringList.Create(AValue, ASeparator + ' ');
  307. try
  308. for I := 0 to S.Count - 1 do
  309. FMasks.Add(TMask.Create(S[I]));
  310. finally
  311. S.Free;
  312. end;
  313. end;
  314. destructor TMaskList.Destroy;
  315. begin
  316. FMasks.Free;
  317. inherited Destroy;
  318. end;
  319. function TMaskList.Matches(const AFileName: UTF8string): Boolean;
  320. var
  321. I: Integer;
  322. begin
  323. Result := False;
  324. for I := 0 to FMasks.Count - 1 do
  325. begin
  326. if TMask(FMasks.Items[I]).Matches(AFileName) then
  327. begin
  328. Result := True;
  329. Exit;
  330. end;
  331. end;
  332. end;
  333. end.