maskutils.pp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599
  1. {
  2. /***************************************************************************
  3. maskutils.pas
  4. ---------
  5. ***************************************************************************/
  6. *****************************************************************************
  7. * *
  8. * This file is part of the Lazarus Component Library (LCL) *
  9. * *
  10. * See the file COPYING.modifiedLGPL, 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. Author: Boguslaw Brandys
  19. Abstract:
  20. FormatMaskText implementation
  21. }
  22. unit maskutils;
  23. {$mode objfpc}{$H+}
  24. {.$define DebugMaskUtils}
  25. interface
  26. uses
  27. Classes
  28. ,SysUtils
  29. {$ifdef DebugMaskUtils}
  30. ,lclproc
  31. {$endif};
  32. function FormatMaskText(const EditMask: string; const Value: string): string;
  33. function FormatMaskInput(const EditMask: string): string;
  34. function MaskDoFormatText(const EditMask: string; const Value: string; Blank: Char): string;
  35. type
  36. TStepState =
  37. (
  38. stLeading, //? not used currently
  39. stUpper, //use uppercase
  40. stLower, //use lowercase
  41. stSpecial, //use escape character
  42. stArbitrary //put arbitrary character
  43. );
  44. TParseState = set of TStepState;
  45. { TMaskUtils }
  46. type
  47. TMaskUtils = class(TObject)
  48. private
  49. FValue: string;
  50. SourcePosition,Position : Integer;
  51. FEditMask,FMask : string;
  52. SourceVal,ExitVal : string;
  53. FMatched : Boolean;
  54. FMissChar : Char;
  55. State : TParseState;
  56. procedure EvaluateExit;
  57. procedure EvaluateMissing;
  58. procedure DoFillRest;
  59. procedure DoLiteral;
  60. procedure DoLiteralInputMask;
  61. procedure DoToken;
  62. procedure DoTokenInputMask;
  63. procedure DoUpper;
  64. procedure DoLower;
  65. procedure DoNumeric(Required : Boolean);
  66. procedure DoAlpha(Required : Boolean);
  67. procedure DoAlphaNumeric(Required : Boolean);
  68. procedure DoNumericPlusMinus;
  69. procedure DoArbitrary(Required : Boolean);
  70. procedure DoTime;
  71. procedure DoDate;
  72. function GetInputMask: string;
  73. procedure SetMask(const AValue: string);
  74. procedure SetValue(const AValue: string);
  75. protected
  76. procedure RaiseError;
  77. procedure ExtractMask;
  78. function MaskPtr : Char;
  79. function SourcePtr : Char;
  80. property Matched: Boolean read FMatched write FMatched;
  81. property MissChar: Char read FMissChar write FMissChar;
  82. public
  83. function ValidateInput : string;
  84. property Mask : string read FEditMask write SetMask;
  85. property Value : string read FValue write SetValue;
  86. property InputMask : string read GetInputMask;
  87. end;
  88. implementation
  89. resourcestring
  90. //exInvalidMaskValue = 'Input mask value incorrect';
  91. exInvalidMaskValue = 'FormatMaskText function failed!';
  92. //replace above text when all bugs will be fixed!
  93. function IsNumeric(const C : Char) : Boolean;
  94. begin
  95. Result := C In ['0'..'9'];
  96. end;
  97. function IsAlpha(const C : Char) : Boolean;
  98. begin
  99. //Fix it later if better way is possible
  100. Result := AnsiUpperCase(C) <> AnsiLowerCase(C);
  101. end;
  102. function IsToken(const C : Char) : Boolean;
  103. begin
  104. Result := C In ['!','>','<','\','L','l','A','a','C','c','0','9','#',':',
  105. '/',';'];
  106. end;
  107. { TMaskUtils }
  108. procedure TMaskUtils.SetMask(const AValue: string);
  109. begin
  110. if FEditMask = AValue then Exit;
  111. FEditMask := AValue;
  112. ExtractMask;
  113. end;
  114. procedure TMaskUtils.SetValue(const AValue: string);
  115. begin
  116. if SourceVal=AValue then exit;
  117. SourceVal := AValue;
  118. end;
  119. function TMaskUtils.ValidateInput : string;
  120. begin
  121. {Prepare}
  122. ExitVal := '';
  123. Position := 1;
  124. SourcePosition := 1;
  125. State := [];
  126. {Process}
  127. while (Position <= Length(FMask)) do
  128. begin
  129. if (IsToken(MaskPtr) and not (stSpecial In State)) then
  130. DoToken
  131. else
  132. DoLiteral;
  133. Inc(Position);
  134. end;
  135. DoFillRest;
  136. Result := ExitVal;
  137. end;
  138. procedure TMaskUtils.EvaluateMissing;
  139. begin
  140. ExitVal := ExitVal + MissChar;
  141. Inc(SourcePosition);
  142. end;
  143. procedure TMaskUtils.RaiseError;inline;
  144. begin
  145. if SourcePosition > Length(SourceVal) then
  146. EvaluateMissing
  147. else
  148. raise Exception.CreateFmtHelp(exInvalidMaskValue,[],Position);
  149. end;
  150. function TMaskUtils.MaskPtr : Char;
  151. begin
  152. Result := FMask[Position];
  153. end;
  154. function TMaskUtils.SourcePtr : Char;
  155. begin
  156. if SourcePosition <= Length(SourceVal) then
  157. Result := SourceVal[SourcePosition]
  158. else Result := #0;
  159. end;
  160. {Extract mask from input parameter}
  161. procedure TMaskUtils.ExtractMask;
  162. var
  163. P : Integer;
  164. s : string;
  165. begin
  166. { TODO: Implement clear, UTF8 compliant parsing ? }
  167. MissChar := #32;
  168. Matched := false;
  169. s := Copy(FEditMask,1,Length(FEditMask));
  170. P := LastDelimiter(';',s);
  171. if P = 0 then FMask := s
  172. else
  173. begin
  174. MissChar := PChar(Copy(s,P+1,1))^;
  175. Delete(s,P,2);
  176. P := LastDelimiter(';',s);
  177. Matched := (Copy(s,P+1,1) <> '0');
  178. Delete(s,P,2);
  179. FMask := s;
  180. end;
  181. end;
  182. procedure TMaskUtils.EvaluateExit;
  183. begin
  184. if stUpper in State then
  185. ExitVal := ExitVal + UpperCase(SourcePtr)
  186. else
  187. if stLower in State then
  188. ExitVal := ExitVal + LowerCase(SourcePtr)
  189. else
  190. ExitVal := ExitVal + SourcePtr;
  191. Inc(SourcePosition);
  192. end;
  193. procedure TMaskUtils.DoUpper;
  194. begin
  195. {$ifdef DebugMaskUtils}
  196. DebugLn(['DoUpper',',Position=',Position]);
  197. {$endif}
  198. if stLower in State then
  199. Exclude(State,stLower)
  200. else
  201. Include(State,stUpper);
  202. {Ugly check for '<>' sequence. Is that required ?}
  203. if (Position > 1) and (FMask[Position-1] = '<') then
  204. begin
  205. Exclude(State,stLower);
  206. Exclude(State,stUpper);
  207. end;
  208. end;
  209. procedure TMaskUtils.DoLower;
  210. begin
  211. {$ifdef DebugMaskUtils}
  212. DebugLn(['DoLower',',Position=',Position]);
  213. {$endif}
  214. if stUpper in State then
  215. Exclude(State,stUpper)
  216. else
  217. Include(State,stLower);
  218. end;
  219. procedure TMaskUtils.DoAlphaNumeric(Required : Boolean);
  220. begin
  221. {$ifdef DebugMaskUtils}
  222. DebugLn(['DoAlphaNumeric',',Position=',Position]);
  223. {$endif}
  224. if Required then
  225. begin
  226. if (IsAlpha(SourcePtr) or IsNumeric(SourcePtr)) then EvaluateExit
  227. else
  228. RaiseError;
  229. end
  230. else
  231. begin
  232. if (IsAlpha(SourcePtr) or IsNumeric(SourcePtr)) then EvaluateExit
  233. else
  234. EvaluateMissing;
  235. end;
  236. end;
  237. procedure TMaskUtils.DoArbitrary(Required : Boolean);
  238. begin
  239. {$ifdef DebugMaskUtils}
  240. DebugLn(['DoArbitrary',',Position=',Position]);
  241. {$endif}
  242. Include(State,stArbitrary);
  243. if Required then
  244. begin
  245. if Position > Length(SourceVal) then RaiseError;
  246. end
  247. else
  248. begin
  249. if Position > Length(SourceVal) then EvaluateMissing
  250. else
  251. EvaluateExit;
  252. end;
  253. end;
  254. procedure TMaskUtils.DoNumeric(Required : Boolean);
  255. begin
  256. {$ifdef DebugMaskUtils}
  257. DebugLn(['DoNumeric',',Position=',Position]);
  258. {$endif}
  259. if Required then
  260. begin
  261. if IsNumeric(SourcePtr) then EvaluateExit
  262. else
  263. RaiseError;
  264. end
  265. else
  266. begin
  267. if IsNumeric(SourcePtr) then EvaluateExit
  268. else
  269. EvaluateMissing;
  270. end;
  271. end;
  272. procedure TMaskUtils.DoNumericPlusMinus;
  273. begin
  274. {$ifdef DebugMaskUtils}
  275. DebugLn(['DoNumericPlusMinus',',Position=',Position]);
  276. {$endif}
  277. if (IsNumeric(SourcePtr)) or
  278. (SourcePtr = '+') or
  279. (SourcePtr = '-') then
  280. EvaluateExit
  281. else
  282. EvaluateMissing;
  283. end;
  284. procedure TMaskUtils.DoTime;
  285. begin
  286. {$ifdef DebugMaskUtils}
  287. DebugLn(['DoTime',',Position=',Position]);
  288. {$endif}
  289. ExitVal := ExitVal + TimeSeparator;
  290. end;
  291. procedure TMaskUtils.DoDate;
  292. begin
  293. {$ifdef DebugMaskUtils}
  294. DebugLn(['DoDate',',Position=',Position]);
  295. {$endif}
  296. ExitVal := ExitVal + DateSeparator;
  297. end;
  298. function TMaskUtils.GetInputMask: string;
  299. begin
  300. {Prepare}
  301. ExitVal := '';
  302. Position := 1;
  303. State := [];
  304. {Process}
  305. while (Position <= Length(FMask)) do
  306. begin
  307. if (IsToken(MaskPtr) and not (stSpecial In State)) then
  308. DoTokenInputMask
  309. else
  310. DoLiteralInputMask;
  311. Inc(Position);
  312. end;
  313. Result := ExitVal;
  314. end;
  315. procedure TMaskUtils.DoAlpha(Required : Boolean);
  316. begin
  317. {$ifdef DebugMaskUtils}
  318. DebugLn(['DoAlpha',',Position=',Position]);
  319. {$endif}
  320. if Required then
  321. begin
  322. if IsAlpha(SourcePtr) then
  323. EvaluateExit
  324. else
  325. RaiseError;
  326. end
  327. else
  328. begin
  329. if IsAlpha(SourcePtr) then
  330. EvaluateExit
  331. else
  332. EvaluateMissing;
  333. end;
  334. end;
  335. procedure TMaskUtils.DoToken;
  336. begin
  337. if stArbitrary in State then Exclude(State,stArbitrary);
  338. case MaskPtr of
  339. '!' : Include(State,stLeading);
  340. '>' : DoUpper;
  341. '<' : DoLower;
  342. '\' : Include(State,stSpecial);
  343. 'L' : DoAlpha(true);
  344. 'l' : DoAlpha(false);
  345. 'A' : DoAlphaNumeric(true);
  346. 'a' : DoAlphaNumeric(false);
  347. 'C' : DoArbitrary(true);
  348. 'c' : DoArbitrary(false);
  349. '0' : DoNumeric(true);
  350. '9' : DoNumeric(false);
  351. '#' : DoNumericPlusMinus;
  352. ':' : DoTime;
  353. '/' : DoDate;
  354. end;
  355. end;
  356. procedure TMaskUtils.DoTokenInputMask;
  357. begin
  358. case MaskPtr of
  359. '!',
  360. '>',
  361. '<' : ;{nothing}
  362. '\' : Include(State,stSpecial);
  363. 'L',
  364. 'l',
  365. 'A',
  366. 'a',
  367. 'C',
  368. 'c',
  369. '0',
  370. '9',
  371. '#' : ExitVal := ExitVal + MissChar;
  372. ':' : DoTime;
  373. '/' : DoDate;
  374. end;
  375. end;
  376. procedure TMaskUtils.DoLiteral;
  377. begin
  378. {$ifdef DebugMaskUtils}
  379. DebugLn(['DoLiteral',',Position=',Position]);
  380. {$endif}
  381. if stSpecial in State then
  382. Exclude(State,stSpecial);
  383. if Matched and (MaskPtr <> SourcePtr) then
  384. RaiseError;
  385. if Matched or not (IsAlpha(SourcePtr) or IsNumeric(SourcePtr)) then
  386. Inc(SourcePosition);
  387. ExitVal := ExitVal + MaskPtr;
  388. end;
  389. procedure TMaskUtils.DoLiteralInputMask;
  390. begin
  391. if stSpecial in State then
  392. Exclude(State,stSpecial);
  393. ExitVal := ExitVal + MaskPtr;
  394. end;
  395. procedure TMaskUtils.DoFillRest;
  396. var
  397. i : Integer;
  398. begin
  399. {Fill rest of exit value because source is longer then mask
  400. and the last mask character permit arbitrary char.
  401. Compatibility with delphi}
  402. if (stArbitrary in State) then
  403. begin
  404. i := Length(SourceVal) - Length(FMask);
  405. while i >= 0 do
  406. begin
  407. EvaluateExit;
  408. Dec(i);
  409. end;
  410. end;
  411. end;
  412. function FormatMaskText(const EditMask: string; const Value: string): string;
  413. var
  414. msk : TMaskUtils;
  415. begin
  416. Result := '';
  417. msk := TMaskUtils.Create;
  418. try
  419. msk.Mask := EditMask;
  420. msk.Value := Value;
  421. Result := msk.ValidateInput;
  422. finally
  423. msk.Free;
  424. end;
  425. end;
  426. {Returns preprocessed mask (without escape characters, with currect locale date
  427. and time separators) }
  428. function FormatMaskInput(const EditMask: string): string;
  429. var
  430. msk : TMaskUtils;
  431. begin
  432. Result := '';
  433. msk := TMaskUtils.Create;
  434. try
  435. msk.Mask := EditMask;
  436. Result := msk.InputMask;
  437. finally
  438. msk.Free;
  439. end;
  440. end;
  441. {
  442. Format Value string using EditMask, dont use 2d and 3d fields of EditMask,
  443. set own Blank char and Matched = False
  444. }
  445. function MaskDoFormatText(const EditMask: string; const Value: string; Blank: Char): string;
  446. var
  447. msk : TMaskUtils;
  448. begin
  449. Result := '';
  450. msk := TMaskUtils.Create;
  451. try
  452. msk.Mask := EditMask;
  453. msk.Value := Value;
  454. msk.Matched := False;
  455. msk.MissChar := Blank;
  456. Result := msk.ValidateInput;
  457. finally
  458. msk.Free;
  459. end;
  460. end;
  461. end.