maskutils.pp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. {
  2. ***************************************************************************
  3. maskutils.pas: Author: Bart Broersma
  4. ***************************************************************************}
  5. {
  6. *****************************************************************************
  7. * *
  8. * This file is part of the Free Component Library (FCL) *
  9. * *
  10. * See the file COPYING.FPC, included in this distribution, *
  11. * for details about the copyright. *
  12. * *
  13. * This program is distributed in the hope that it will be useful, *
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of *
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
  16. * *
  17. *****************************************************************************
  18. }
  19. unit MaskUtils;
  20. {$mode objfpc}{$H+}
  21. {.$define debug_maskutils}
  22. interface
  23. uses
  24. SysUtils;
  25. function FormatMaskText(const EditMask: string; const AValue: string): string;
  26. function FormatMaskInput(const EditMask: string): string;
  27. function MaskDoFormatText(const EditMask: string; const AValue: string; ASpaceChar: Char): string;
  28. type
  29. TEditMask = type string;
  30. TMaskeditTrimType = (metTrimLeft, metTrimRight);
  31. { Type for mask (internal) }
  32. tMaskedType = (Char_Start,
  33. Char_Number,
  34. Char_NumberFixed,
  35. Char_NumberPlusMin,
  36. Char_Letter,
  37. Char_LetterFixed,
  38. Char_LetterUpCase,
  39. Char_LetterDownCase,
  40. Char_LetterFixedUpCase,
  41. Char_LetterFixedDownCase,
  42. Char_AlphaNum,
  43. Char_AlphaNumFixed,
  44. Char_AlphaNumUpCase,
  45. Char_AlphaNumDownCase,
  46. Char_AlphaNumFixedUpCase,
  47. Char_AlphaNumFixedDownCase,
  48. Char_All,
  49. Char_AllFixed,
  50. Char_AllUpCase,
  51. Char_AllDownCase,
  52. Char_AllFixedUpCase,
  53. Char_AllFixedDownCase,
  54. Char_HourSeparator,
  55. Char_DateSeparator,
  56. Char_Stop);
  57. { TMaskUtils }
  58. type
  59. TMaskUtils = class(TObject)
  60. private
  61. FRealMask: String;
  62. FMask: String; // internal representatio of the mask
  63. FValue: String;
  64. FMaskLength: Integer;
  65. FMaskSave: Boolean;
  66. FSpaceChar: Char;
  67. FTrimType: TMaskeditTrimType;
  68. procedure AddToMask(Ch: Char);
  69. function MaskToChar(AValue: tMaskedType) : Char;
  70. function CharToMask(Ch: Char) : tMaskedType;
  71. function CharMatchesMask(const Ch: Char; const Position: Integer): Boolean;
  72. function ClearChar(Position : Integer) : Char;
  73. procedure SetMask(AValue: String);
  74. function GetInputMask: String;
  75. function GetTextWithoutMask(AValue: String) : String;
  76. function GetTextWithoutSpaceChar(AValue: String) : String;
  77. function IsLiteral(Ch: Char): Boolean;
  78. function IsMaskChar(Ch : Char) : Boolean;
  79. procedure SetValue(AValue: String);
  80. function TextIsValid(const AValue: String): Boolean;
  81. protected
  82. function ApplyMaskToText(AValue: String): String;
  83. public
  84. function ValidateInput : String;
  85. function TryValidateInput(out ValidatedString: String): Boolean;
  86. property Mask : String read FRealMask write SetMask;
  87. property Value : String read FValue write SetValue;
  88. property InputMask : String read GetInputMask;
  89. end;
  90. implementation
  91. resourcestring
  92. exValidationFailed = 'TMaskUtils.ValidateInput failed.';
  93. const
  94. { Mask Type }
  95. cMask_SpecialChar = '\'; // after this you can set an arbitrary char
  96. cMask_UpperCase = '>'; // after this the chars is in upper case
  97. cMask_LowerCase = '<'; // after this the chars is in lower case
  98. cMask_Letter = 'l'; // only a letter but not necessary
  99. cMask_LetterFixed = 'L'; // only a letter
  100. cMask_AlphaNum = 'a'; // an alphanumeric char (['A'..'Z','a..'z','0'..'9']) but not necessary
  101. cMask_AlphaNumFixed = 'A'; // an alphanumeric char
  102. cMask_AllChars = 'c'; // any Utf8 char but not necessary
  103. cMask_AllCharsFixed = 'C'; // any Utf8 char #32 - #255
  104. cMask_Number = '9'; // only a number but not necessary
  105. cMask_NumberFixed = '0'; // only a number
  106. cMask_NumberPlusMin = '#'; // only a number or + or -, but not necessary
  107. cMask_HourSeparator = ':'; // automatically put the hour separator char
  108. cMask_DateSeparator = '/'; // automatically but the date separator char
  109. { cMask_SpaceOnly = '_'; // automatically put a space //not Delphi compatible }
  110. cMask_NoLeadingBlanks = '!'; //Trim leading blanks, otherwise trim trailing blanks from the data
  111. {Delphi compatibility: user can change these at runtime}
  112. DefaultBlank: Char = '_';
  113. MaskFieldSeparator: Char = ';';
  114. MaskNoSave: Char = '0';
  115. procedure SplitEditMask(AEditMask: String; out AMaskPart: String; out AMaskSave: Boolean; out ASpaceChar: Char);
  116. {
  117. Retrieve the separate fields for a given EditMask:
  118. Given an AEditMask of '999.999;0;_' it will return
  119. - AMaskPart = '999.999'
  120. - AMaskSave = False
  121. - ASpaceChar = '_'
  122. }
  123. begin
  124. {
  125. First see if AEditMask is multifield and if we can extract a value for
  126. AMaskSave and/or ASpaceChar
  127. If so, extract and remove from AMask (so we know that the remaining part of
  128. AMask _IS_ the mask to be set)
  129. A value for SpaceChar is only valid if also a value for MaskSave is specified
  130. (as by Delphi specifications), so Mask must be at least 4 characters
  131. These must be the last 2 or 4 characters of EditMask (and there must not be
  132. an escape character in front!)
  133. }
  134. //Assume no SpaceChar and no MaskSave is defined in new mask, so first set it to DefaultBlank and True
  135. ASpaceChar := DefaultBlank;
  136. AMaskSave := True;
  137. //MaskFieldseparator, MaskNoSave, SpaceChar and cMask_SpecialChar are defined as Char (=AnsiChar)
  138. //so in this case we can use Length (instead of Utf8length) and iterate single chars in the string
  139. if (Length(AEditMask) >= 4) and (AEditMask[Length(AEditMask)-1] = MaskFieldSeparator) and
  140. (AEditMask[Length(AEditMask)-3] = MaskFieldSeparator) and
  141. (AEditMask[Length(AEditMask)-2] <> cMask_SpecialChar) and
  142. //Length = 4 is OK (AEditMask = ";1;_" for example), but if Length > 4 there must be no escape charater in front
  143. ((Length(AEditMask) = 4) or ((Length(AEditMask) > 4) and (AEditMask[Length(AEditMask)-4] <> cMask_SpecialChar))) then
  144. begin
  145. ASpaceChar := AEditMask[Length(AEditMask)];
  146. AMaskSave := (AEditMask[Length(AEditMask)-2] <> MaskNosave);
  147. System.Delete(AEditMask,Length(AEditMask)-3,4);
  148. end
  149. //If not both FMaskSave and FSPaceChar are specified, then see if only FMaskSave is specified
  150. else if (Length(AEditMask) >= 2) and (AEditMask[Length(AEditMask)-1] = MaskFieldSeparator) and
  151. //Length = 2 is OK, but if Length > 2 there must be no escape charater in front
  152. ((Length(AEditMask) = 2) or ((Length(AEditMask) > 2) and (AEditMask[Length(AEditMask)-2] <> cMask_SpecialChar))) then
  153. begin
  154. AMaskSave := (AEditMask[Length(AEditMask)] <> MaskNoSave);
  155. //Remove this bit from Mask
  156. System.Delete(AEditMask,Length(AEditMask)-1,2);
  157. end;
  158. //Whatever is left of AEditMask at this point is the MaskPart
  159. AMaskPart := AEditMask;
  160. end;
  161. function FormatMaskText(const EditMask: string; const AValue: string): string;
  162. var
  163. Mu: TMaskUtils;
  164. begin
  165. Mu := TMaskUtils.Create;
  166. try
  167. Mu.Mask := EditMask;
  168. Mu.Value := AValue;
  169. Result := Mu.ApplyMaskToText(AValue);
  170. Result := Mu.GetTextWithoutSpaceChar(Result);
  171. finally
  172. Mu.Free;
  173. end;
  174. end;
  175. function FormatMaskInput(const EditMask: string): string;
  176. var
  177. Mu : TMaskUtils;
  178. begin
  179. Result := '';
  180. Mu := TMaskUtils.Create;
  181. try
  182. Mu.Mask := EditMask;
  183. Result := Mu.InputMask;
  184. finally
  185. Mu.Free;
  186. end;
  187. end;
  188. {
  189. Format Value string using EditMask, dont use 2d and 3d fields of EditMask,
  190. set own SpaceChar and MaskSave = True ('1')
  191. }
  192. function MaskDoFormatText(const EditMask: string; const AValue: string; ASpaceChar: Char): string;
  193. var
  194. Mu : TMaskUtils;
  195. AMaskPart: String;
  196. OldMaskSave: Boolean;
  197. OldSpaceChar: Char;
  198. begin
  199. Result := '';
  200. SplitEditMask(EditMask, AMaskPart, OldMaskSave, OldSpaceChar);
  201. Mu := TMaskUtils.Create;
  202. try
  203. Mu.Mask := AMaskPart + ';1;'+ASpaceChar;
  204. Mu.Value := AValue;
  205. Result := Mu.ValidateInput;
  206. finally
  207. Mu.Free;
  208. end;
  209. end;
  210. { TMaskUtils }
  211. procedure TMaskUtils.AddToMask(Ch: Char);
  212. begin
  213. FMask := FMask + Ch;
  214. FMaskLength := Length(FMask);
  215. end;
  216. function TMaskUtils.MaskToChar(AValue: tMaskedType): Char;
  217. begin
  218. Result := Char(Ord(AValue));
  219. end;
  220. function TMaskUtils.CharToMask(Ch: Char): tMaskedType;
  221. begin
  222. Result := Char_Start;
  223. if (Ord(Ch) > Ord(Char_Start)) and
  224. (Ord(Ch) < Ord(Char_Stop) )
  225. then
  226. Result := tMaskedType(Ord(Ch));
  227. end;
  228. function TMaskUtils.CharMatchesMask(const Ch: Char; const Position: Integer): Boolean;
  229. var
  230. Current: tMaskedType;
  231. Ok: Boolean;
  232. begin
  233. Result := False;
  234. if (Position < 1) or (Position > FMaskLength) then Exit;
  235. Current := CharToMask(FMask[Position]);
  236. case Current Of
  237. Char_Number : OK := (Ch in ['0'..'9',#32]);
  238. Char_NumberFixed : OK := (Ch in ['0'..'9']);
  239. Char_NumberPlusMin : OK := (Ch in ['0'..'9','+','-',#32]);
  240. Char_Letter : OK := (Ch in ['a'..'z', 'A'..'Z',#32]);
  241. Char_LetterFixed : OK := (Ch in ['a'..'z', 'A'..'Z']);
  242. Char_LetterUpCase : OK := (Ch in ['A'..'Z',#32]);
  243. Char_LetterDownCase : OK := (Ch in ['a'..'z',#32]);
  244. Char_LetterFixedUpCase : OK := (Ch in ['A'..'Z']);
  245. Char_LetterFixedDownCase : OK := (Ch in ['a'..'z']);
  246. Char_AlphaNum : OK := (Ch in ['a'..'z', 'A'..'Z', '0'..'9',#32]);
  247. Char_AlphaNumFixed : OK := (Ch in ['a'..'z', 'A'..'Z', '0'..'9']);
  248. Char_AlphaNumUpCase : OK := (Ch in ['A'..'Z', '0'..'9',#32]);
  249. Char_AlphaNumDownCase : OK := (Ch in ['a'..'z', '0'..'9',#32]);
  250. Char_AlphaNumFixedUpCase : OK := (Ch in ['A'..'Z', '0'..'9']);
  251. Char_AlphaNumFixedDowncase:OK := (Ch in ['a'..'z', '0'..'9']);
  252. Char_All : OK := True; //Ch in [#32..#126]; //True;
  253. Char_AllFixed : OK := True; //Ch in [#32..#126]; //True;
  254. Char_AllUpCase : OK := True; //Ch in [#32..#126]; // (Utf8UpperCase(Ch) = Ch); ???????
  255. Char_AllDownCase : OK := True; //Ch in [#32..#126]; // (Utf8LowerCase(Ch) = Ch); ???????
  256. Char_AllFixedUpCase : OK := True; //Ch in [#32..#126]; // (Utf8UpperCase(Ch) = Ch); ???????
  257. Char_AllFixedDownCase : OK := True; //Ch in [#32..#126]; // (Utf8LowerCase(Ch) = Ch); ???????
  258. {Char_Space : OK := (Length(Ch) = 1) and (Ch in [' ', '_']); //not Delphi compatible, see notes above}
  259. Char_HourSeparator : OK := (Ch = DefaultFormatSettings.TimeSeparator);
  260. Char_DateSeparator : OK := (Ch = DefaultFormatSettings.DateSeparator);
  261. else//it's a literal
  262. begin
  263. OK := (Ch = FMask[Position]);
  264. end;
  265. end;//case
  266. //DebugLn('Position = ',DbgS(Position),' Current = ',MaskCharToChar[Current],' Ch = "',Ch,'" Ok = ',DbgS(Ok));
  267. Result := Ok;
  268. end;
  269. // Clear (virtually) a single char in position Position
  270. function TMaskUtils.ClearChar(Position: Integer): Char;
  271. begin
  272. //For Delphi compatibilty, only literals remain, all others will be blanked
  273. case CharToMask(FMask[Position]) Of
  274. Char_Number,
  275. Char_NumberFixed,
  276. Char_NumberPlusMin,
  277. Char_Letter,
  278. Char_LetterFixed,
  279. Char_LetterUpCase,
  280. Char_LetterDownCase,
  281. Char_LetterFixedUpCase,
  282. Char_LetterFixedDownCase,
  283. Char_AlphaNum,
  284. Char_AlphaNumFixed,
  285. Char_AlphaNumUpCase,
  286. Char_AlphaNumDownCase,
  287. Char_AlphaNumFixedUpcase,
  288. Char_AlphaNuMFixedDownCase,
  289. Char_All,
  290. Char_AllFixed,
  291. Char_AllUpCase,
  292. Char_AllDownCase,
  293. Char_AllFixedUpCase,
  294. Char_AllFixedDownCase: Result := FSpaceChar;
  295. Char_HourSeparator: Result := DefaultFormatSettings.TimeSeparator;
  296. Char_DateSeparator: Result := DefaultFormatSettings.DateSeparator;
  297. else
  298. Result := FMask[Position];
  299. end;
  300. end;
  301. procedure TMaskUtils.SetMask(AValue: String);
  302. Var
  303. S, AMaskPart : String;
  304. I : Integer;
  305. InUp, InDown : Boolean;
  306. Special : Boolean;
  307. Ch : Char;
  308. begin
  309. if FRealMask <> AValue then
  310. begin
  311. FRealMask := AValue;
  312. FMask := '';
  313. SplitEditMask(FRealMask, AMaskPart, FMaskSave, FSpaceChar);
  314. // Construct Actual Internal Mask
  315. // init
  316. FTrimType := metTrimRight;
  317. // Init: No UpCase, No LowerCase, No Special Char
  318. InUp := False;
  319. InDown := False;
  320. Special := False;
  321. S := AMaskPart;
  322. for I := 1 to Length(S) do
  323. begin
  324. Ch := S[I];
  325. // Must insert a special char
  326. if Special then
  327. begin
  328. AddToMask(Ch);
  329. Special := False;
  330. end
  331. else
  332. begin
  333. // Check the char to insert
  334. case Ch Of
  335. cMask_SpecialChar: Special := True;
  336. cMask_UpperCase: begin
  337. if (I > 1) and (S[I-1] = cMask_LowerCase) then
  338. begin// encountered <>, so no case checking after this
  339. InUp := False;
  340. InDown := False
  341. end else
  342. begin
  343. InUp := True;
  344. InDown := False;
  345. end;
  346. end;
  347. cMask_LowerCase: begin
  348. InDown := True;
  349. InUp := False;
  350. // <> is catched by next cMask_Uppercase
  351. end;
  352. cMask_Letter: begin
  353. if InUp
  354. then
  355. AddToMask(MaskToChar(Char_LetterUpCase))
  356. else
  357. if InDown
  358. then
  359. AddToMask(MaskToChar(Char_LetterDownCase))
  360. else
  361. AddToMask(MaskToChar(Char_Letter))
  362. end;
  363. cMask_LetterFixed: begin
  364. if InUp
  365. then
  366. AddToMask(MaskToChar(Char_LetterFixedUpCase))
  367. else
  368. if InDown
  369. then
  370. AddToMask(MaskToChar(Char_LetterFixedDownCase))
  371. else
  372. AddToMask(MaskToChar(Char_LetterFixed))
  373. end;
  374. cMask_AlphaNum: begin
  375. if InUp
  376. then
  377. AddToMask(MaskToChar(Char_AlphaNumUpcase))
  378. else
  379. if InDown
  380. then
  381. AddToMask(MaskToChar(Char_AlphaNumDownCase))
  382. else
  383. AddToMask(MaskToChar(Char_AlphaNum))
  384. end;
  385. cMask_AlphaNumFixed: begin
  386. if InUp
  387. then
  388. AddToMask(MaskToChar(Char_AlphaNumFixedUpcase))
  389. else
  390. if InDown
  391. then
  392. AddToMask(MaskToChar(Char_AlphaNumFixedDownCase))
  393. else
  394. AddToMask(MaskToChar(Char_AlphaNumFixed))
  395. end;
  396. cMask_AllChars: begin
  397. if InUp
  398. then
  399. AddToMask(MaskToChar(Char_AllUpCase))
  400. else
  401. if InDown
  402. then
  403. AddToMask(MaskToChar(Char_AllDownCase))
  404. else
  405. AddToMask(MaskToChar(Char_All))
  406. end;
  407. cMask_AllCharsFixed: begin
  408. if InUp
  409. then
  410. AddToMask(MaskToChar(Char_AllFixedUpCase))
  411. else
  412. if InDown
  413. then
  414. AddToMask(MaskToChar(Char_AllFixedDownCase))
  415. else
  416. AddToMask(MaskToChar(Char_AllFixed))
  417. end;
  418. cMask_Number: AddToMask(MaskToChar(Char_Number));
  419. cMask_NumberFixed: AddToMask(MaskToChar(Char_NumberFixed));
  420. cMask_NumberPlusMin: AddToMask(MaskToChar(Char_NumberPlusMin));
  421. cMask_HourSeparator: AddToMask(MaskToChar(Char_HourSeparator));
  422. cMask_DateSeparator: AddToMask(MaskToChar(Char_DateSeparator));
  423. cMask_NoLeadingBlanks:
  424. begin
  425. FTrimType := metTrimLeft;
  426. end;
  427. else
  428. begin
  429. //It's a MaskLiteral
  430. AddToMask(Ch);
  431. end;
  432. end;
  433. end;
  434. end;
  435. end;
  436. end;
  437. function TMaskUtils.GetInputMask: String;
  438. var
  439. i: Integer;
  440. begin
  441. Result := '';
  442. for i := 1 to length(FMask) do
  443. begin
  444. case CharToMask(FMask[i]) of
  445. Char_Number,
  446. Char_NumberFixed,
  447. Char_NumberPlusMin,
  448. Char_Letter,
  449. Char_LetterFixed,
  450. Char_LetterUpCase,
  451. Char_LetterDownCase,
  452. Char_LetterFixedUpCase,
  453. Char_LetterFixedDownCase,
  454. Char_AlphaNum,
  455. Char_AlphaNumFixed,
  456. Char_AlphaNumUpCase,
  457. Char_AlphaNumDownCase,
  458. Char_AlphaNumFixedUpCase,
  459. Char_AlphaNumFixedDownCase,
  460. Char_All,
  461. Char_AllFixed,
  462. Char_AllUpCase,
  463. Char_AllDownCase,
  464. Char_AllFixedUpCase,
  465. Char_AllFixedDownCase: Result := Result + #32;
  466. Char_HourSeparator: Result := Result + DefaultFormatSettings.TimeSeparator;
  467. Char_DateSeparator: Result := Result + DefaultFormatSettings.DateSeparator;
  468. else Result := Result + FMask[i]; //it's a literal
  469. end;
  470. end;
  471. end;
  472. function TMaskUtils.GetTextWithoutMask(AValue: String): String;
  473. {
  474. Replace al FSPaceChars with #32
  475. If FMaskSave = False then do trimming of spaces and remove all maskliterals
  476. }
  477. var
  478. S: String;
  479. i: Integer;
  480. Begin
  481. S := StringReplace(AValue, FSpaceChar, #32, [rfReplaceAll]);
  482. //FSpaceChar can be used as a literal in the mask, so put it back
  483. for i := 1 to FMaskLength do
  484. begin
  485. if IsLiteral(FMask[i]) and (FMask[i] = FSpaceChar) then
  486. begin
  487. S[i] := FSpaceChar;
  488. end;
  489. end;
  490. if not FMaskSave then
  491. begin
  492. for i := 1 to FMaskLength do
  493. begin
  494. if IsLiteral(FMask[i]) then S[i] := #1;
  495. end;
  496. S := StringReplace(S, #1, '', [rfReplaceAll]);
  497. //Trimming only occurs if FMaskSave = False
  498. case FTrimType of
  499. metTrimLeft : S := TrimLeft(S);
  500. metTrimRight: S := TrimRight(S);
  501. end;//case
  502. end;
  503. Result := S;
  504. End;
  505. function TMaskUtils.GetTextWithoutSpaceChar(AValue: String): String;
  506. var
  507. i: Integer;
  508. begin
  509. Result := StringReplace(AValue, FSpaceChar, #32, [rfReplaceAll]);
  510. //FSpaceChar can be used as a literal in the mask, so put it back
  511. for i := 1 to FMaskLength do
  512. begin
  513. if IsLiteral(FMask[i]) and (FMask[i] = FSpaceChar) then
  514. begin
  515. Result[i] := FSpaceChar;
  516. end;
  517. end;
  518. end;
  519. function TMaskUtils.IsLiteral(Ch: Char): Boolean;
  520. begin
  521. Result := (not IsMaskChar(Ch)) or
  522. (IsMaskChar(Ch) and (CharToMask(Ch) in [Char_HourSeparator, Char_DateSeparator]))
  523. end;
  524. function TMaskUtils.IsMaskChar(Ch: Char): Boolean;
  525. begin
  526. Result := (CharToMask(Ch) <> Char_Start);
  527. end;
  528. procedure TMaskUtils.SetValue(AValue: String);
  529. begin
  530. if FValue = AValue then Exit;
  531. FValue := AValue;
  532. end;
  533. function TMaskUtils.TextIsValid(const AValue: String): Boolean;
  534. var
  535. i: Integer;
  536. begin
  537. Result := False;
  538. if (Length(AValue) <> FMaskLength) then
  539. begin
  540. {$ifdef debug_maskutils}
  541. writeln('Length(AValue) = ',Length(AValue),' FMaskLength = ',FMaskLength);
  542. {$endif}
  543. Exit; //Actually should never happen??
  544. end;
  545. for i := 1 to FMaskLength do
  546. begin
  547. if not CharMatchesMask(AValue[i], i) then
  548. begin
  549. {$ifdef debug_maskutils}
  550. writeln('Fail: CharMatchesMask(',AValue[i],',',i,') [',AValue,']');
  551. {$endif}
  552. Exit;
  553. end;
  554. end;
  555. Result := True;
  556. end;
  557. function TMaskUtils.ApplyMaskToText(AValue: String): String;
  558. { This tries to mimic Delphi behaviour (D3):
  559. - if mask contains no literals text is set, if necessary padded with blanks,
  560. LTR or RTL depending on FTrimType
  561. - if mask contains literals then we search for matching literals in text and
  562. process each "segment" between matching maskliterals, trimming or padding
  563. LTR or RTL depending on FTrimType, until there is no more matching maskliteral
  564. Some examples to clarify:
  565. EditMask Text to be set Result
  566. 99 1 1_
  567. !99 1 _1
  568. cc-cc 1-2 1_-2_
  569. !cc-cc 1-2 _1-_2
  570. cc-cc@cc 1-2@3 1_-2_@3_
  571. 12@3 12-__@__
  572. cc-cc@cc 123-456@789 12-45@78
  573. !cc-cc@cc 123-456@789 23-56@89
  574. This feauture seems to be invented for easy use of dates:
  575. 99/99/00 23/1/2009 23/1_/20 <- if your locale DateSeparator = '/'
  576. !99/99/00 23/1/2009 23/_1/09 <- if your locale DateSeparator = '/'
  577. - The resulting text will always have length = FMaskLength
  578. - The text that is set, does not need to validate
  579. }
  580. //Helper functions
  581. Function FindNextMaskLiteral(const StartAt: Integer; out FoundAt: Integer; out ALiteral: Char): Boolean;
  582. var i: Integer;
  583. begin
  584. Result := False;
  585. for i := StartAt to FMaskLength do
  586. begin
  587. if IsLiteral(FMask[i]) then
  588. begin
  589. FoundAt := i;
  590. ALiteral := ClearChar(i);
  591. Result := True;
  592. Exit;
  593. end;
  594. end;
  595. end;
  596. Function FindMatchingLiteral(const Value: String; const ALiteral: Char; out FoundAt: Integer): Boolean;
  597. begin
  598. FoundAt := Pos(ALiteral, Value);
  599. Result := (FoundAt > 0);
  600. end;
  601. Var
  602. S : String;
  603. I, J : Integer;
  604. mPrevLit, mNextLit : Integer; //Position of Previous and Next literal in FMask
  605. vNextLit : Integer; //Position of next matching literal in AValue
  606. HasNextLiteral,
  607. HasMatchingLiteral,
  608. Stop : Boolean;
  609. Literal : Char;
  610. Sub : String;
  611. begin
  612. //First setup a "blank" string that contains all literals in the mask
  613. S := '';
  614. for I := 1 To FMaskLength do S := S + ClearChar(I);
  615. if FMaskSave then
  616. begin
  617. mPrevLit := 0;
  618. Stop := False;
  619. HasNextLiteral := FindNextMaskLiteral(mPrevLit+1, mNextLit, Literal);
  620. //if FMask starts with a literal, then the first CodePoint of AValue must be that literal
  621. if HasNextLiteral and (mNextLit = 1) and (AValue[1] <> Literal) then Stop := True;
  622. //debugln('HasNextLiteral = ',dbgs(hasnextliteral),', Stop = ',dbgs(stop));
  623. While not Stop do
  624. begin
  625. if HasNextLiteral then
  626. begin
  627. HasMatchingLiteral := FindMatchingLiteral(AValue, Literal, vNextLit);
  628. //debugln('mPrevLit = ',dbgs(mprevlit),' mNextLit = ',dbgs(mnextlit));
  629. //debugln('HasMatchingLiteral = ',dbgs(hasmatchingliteral));
  630. if HasMatchingLiteral then
  631. begin
  632. //debugln('vNextLit = ',dbgs(vnextlit));
  633. Sub := Copy(AValue, 1, vNextLit - 1); //Copy up to, but not including matching literal
  634. Delete(AValue, 1, vNextLit); //Remove this bit from AValue (including matching literal)
  635. if (Length(AValue) = 0) then Stop := True;
  636. //debugln('Sub = "',Sub,'", Value = "',AValue,'"');
  637. end
  638. else
  639. begin//HasMatchingLiteral = False
  640. Stop := True;
  641. Sub := AValue;
  642. AValue := '';
  643. //debugln('Sub = "',Sub,'", Value = "',AValue,'"');
  644. end;
  645. //fill S between vPrevLit + 1 and vNextLit - 1, LTR or RTL depending on FTrimType
  646. if (FTrimType = metTrimRight) then
  647. begin
  648. j := 1;
  649. for i := (mPrevLit + 1) to (mNextLit - 1) do
  650. begin
  651. if (J > Length(Sub)) then Break;
  652. if (Sub[j] = #32) then S[i] := FSpaceChar else S[i] := Sub[j];
  653. Inc(j);
  654. end;
  655. end
  656. else
  657. begin//FTrimType = metTrimLeft
  658. j := Length(Sub);
  659. for i := (mNextLit - 1) downto (mPrevLit + 1) do
  660. begin
  661. if (j < 1) then Break;
  662. if (Sub[j] = #32) then S[i] := FSpaceChar else S[i] := Sub[j];
  663. Dec(j);
  664. end;
  665. end;
  666. //debugln('S = ',S);
  667. end
  668. else
  669. begin//HasNextLiteral = False
  670. //debugln('No more MaskLiterals at this point');
  671. //debugln('mPrevLit = ',dbgs(mprevlit));
  672. Stop := True;
  673. Sub := AValue;
  674. AValue := '';
  675. //debugln('Sub = "',Sub,'", Value = "',AValue,'"');
  676. //fill S from vPrevLit + 1 until end of FMask, LTR or RTL depending on FTrimType
  677. if (FTrimType = metTrimRight) then
  678. begin
  679. j := 1;
  680. for i := (mPrevLit + 1) to FMaskLength do
  681. begin
  682. //debugln(' i = ',dbgs(i),' j = ',dbgs(j));
  683. if (j > Length(Sub)) then Break;
  684. if (Sub[j] = #32) then S[i] := FSpaceChar else S[i] := Sub[j];
  685. //debugln(' Sub[j] = "',Sub[j],'" -> S = ',S);
  686. Inc(j);
  687. end;
  688. end
  689. else
  690. begin//FTrimType = metTrimLeft
  691. j := Length(Sub);
  692. for i := FMaskLength downto (mPrevLit + 1) do
  693. begin
  694. //debugln(' i = ',dbgs(i),' j = ',dbgs(j));
  695. if (j < 1) then Break;
  696. if (Sub[j] = #32) then S[i] := FSpaceChar else S[i] := Sub[j];
  697. //debugln(' Sub[j] = "',Sub[j],'" -> S = ',S);
  698. Dec(j);
  699. end;
  700. end;
  701. //debugln('S = ',S);
  702. end;
  703. //debugln('Stop = ',dbgs(stop));
  704. if not Stop then
  705. begin
  706. mPrevLit := mNextLit;
  707. HasNextLiteral := FindNextMaskLiteral(mPrevLit + 1, mNextLit, Literal);
  708. end;
  709. end;//while not Stop
  710. end//FMaskSave = True
  711. else
  712. begin//FMaskSave = False
  713. if AValue<>'' then
  714. begin
  715. if FTrimType = metTrimRight then
  716. begin
  717. //fill text from left to rigth, skipping MaskLiterals
  718. j := 1;
  719. for i := 1 to FMaskLength do
  720. begin
  721. if not IsLiteral(FMask[i]) then
  722. begin
  723. if (AValue[j] = #32) then S[i]:= FSpaceChar else S[i] := AValue[j];
  724. Inc(j);
  725. if j > Length(AValue) then Break;
  726. end;
  727. end;
  728. end
  729. else
  730. begin
  731. //fill text from right to left, skipping MaskLiterals
  732. j := Length(AValue);
  733. for i := FMaskLength downto 1 do
  734. begin
  735. if not IsLiteral(FMask[i]) then
  736. begin
  737. if (AValue[j] = #32) then S[i] := FSpaceChar else S[i] := AValue[j];
  738. Dec(j);
  739. if j < 1 then Break;
  740. end;
  741. end;
  742. end;
  743. end;
  744. end;//FMaskSave = False
  745. Result := S;
  746. end;
  747. function TMaskUtils.ValidateInput: String;
  748. begin
  749. if not TryValidateInput(Result) then
  750. raise Exception.Create(exValidationFailed);
  751. end;
  752. function TMaskUtils.TryValidateInput(out ValidatedString: String): Boolean;
  753. var
  754. SMaskApplied, SMaskRemoved: String;
  755. _MaskSave: Boolean;
  756. begin
  757. _MaskSave := FMaskSave;
  758. //Note: applying the mask and then removing it is not reciprocal!
  759. SMaskApplied := ApplyMaskToText(Value);
  760. FMaskSave := True;
  761. SMaskRemoved := GetTextWithoutMask(SMaskApplied);
  762. FMaskSave := _MaskSave;
  763. Result := TextIsValid(SMaskRemoved);
  764. if Result then
  765. ValidatedString := GetTextWithoutSpaceChar(SMaskApplied);
  766. end;
  767. end.