BrookStringMap.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  1. (* _ _
  2. * | |__ _ __ ___ ___ | | __
  3. * | '_ \| '__/ _ \ / _ \| |/ /
  4. * | |_) | | | (_) | (_) | <
  5. * |_.__/|_| \___/ \___/|_|\_\
  6. *
  7. * Microframework which helps to develop web Pascal applications.
  8. *
  9. * Copyright (c) 2012-2021 Silvio Clecio <[email protected]>
  10. *
  11. * Brook framework is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU Lesser General Public
  13. * License as published by the Free Software Foundation; either
  14. * version 2.1 of the License, or (at your option) any later version.
  15. *
  16. * Brook framework is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. * Lesser General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Lesser General Public
  22. * License along with Brook framework; if not, write to the Free Software
  23. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  24. *)
  25. { String map used to represent HTML fields, query-string parameters and more. }
  26. unit BrookStringMap;
  27. {$I BrookDefines.inc}
  28. interface
  29. uses
  30. RTLConsts,
  31. SysUtils,
  32. StrUtils,
  33. Classes,
  34. TypInfo,
  35. Platform,
  36. Marshalling,
  37. libsagui,
  38. BrookUtility,
  39. BrookHandledClasses;
  40. type
  41. TBrookStringMap = class;
  42. { Identifies the kind of operation in the map.
  43. @value(sgmoNone None operation or map cleaned.)
  44. @value(sgmoAdd Pair added to the map.)
  45. @value(sgmoAddOrSet Pair added or set to the map.)
  46. @value(sgmoRemove Pair removed from the map.) }
  47. TBrookStringMapOperation = (sgmoNone, sgmoAdd, sgmoAddOrSet, sgmoRemove);
  48. { Event signature used to notify a change in the map.
  49. @param(ASender Event caller.)
  50. @param(AOperation Operation kind.) }
  51. TBrookStringMapChangeEvent = procedure(ASender: TObject;
  52. AOperation: TBrookStringMapOperation) of object;
  53. { Pair item of @code(TBrookStringMap). }
  54. TBrookStringPair = packed record
  55. private
  56. FName: string;
  57. FValue: string;
  58. public
  59. { Initializes a variable of @code(TBrookStringPair).
  60. @param(AName[in] Name of the pair.)
  61. @param(AValue[in] Value of the pair.) }
  62. constructor Create(const AName, AValue: string);
  63. { Name of the pair. }
  64. property Name: string read FName;
  65. { Value of the pair. }
  66. property Value: string read FValue;
  67. end;
  68. { Enumerator used to iterate the map @code(TBrookStringMap). }
  69. TBrookStringMapEnumerator = class
  70. private
  71. FMap: TBrookStringMap;
  72. FCurr: TBrookStringPair;
  73. FBOF: Boolean;
  74. public
  75. { Creates an instance of @code(TBrookStringMapEnumerator).
  76. @param(AMap[in] Pairs map.) }
  77. constructor Create(AMap: TBrookStringMap);
  78. { Gets the current pair.
  79. @returns(Current pair.) }
  80. function GetCurrent: TBrookStringPair;
  81. { Moves to the next pair.
  82. @returns(@True when move next reaches the EOF.) }
  83. function MoveNext: Boolean;
  84. { Same to @code(GetCurrent). }
  85. property Current: TBrookStringPair read GetCurrent;
  86. end;
  87. { Function signature used by @code(TBrookStringMap.Iterate).
  88. @param(AData[in,out] User-defined data.)
  89. @param(APair[out] Current iterated pair.) }
  90. TBrookStringMapIterator = function(AData: Pointer;
  91. APair: TBrookStringPair): Integer;
  92. { Function signature used by @code(TBrookStringMap.Sort).
  93. @param(AData[in,out] User-defined data.)
  94. @param(APairA[out] Current left pair (A).)
  95. @param(APairB[out] Current right pair (B).) }
  96. TBrookStringMapComparator = function(AData: Pointer;
  97. APairA, APairB: TBrookStringPair): Integer;
  98. { String map class and its related methods. }
  99. TBrookStringMap = class(TBrookHandledPersistent)
  100. private
  101. FClearOnDestroy: Boolean;
  102. FNextHandle: Psg_strmap;
  103. FHandle: PPsg_strmap;
  104. FOnChange: TBrookStringMapChangeEvent;
  105. function GetCount: Integer;
  106. function GetValue(const AName: string): string;
  107. procedure SetValue(const AName, AValue: string);
  108. protected
  109. class function CreatePair(
  110. Apair: Psg_strmap): TBrookStringPair; static;
  111. {$IFNDEF DEBUG}inline;{$ENDIF}
  112. class function DoIterate(Acls: Pcvoid;
  113. Apair: Psg_strmap): cint; cdecl; static;
  114. class function DoSort(Acls: Pcvoid; Apair_a: Psg_strmap;
  115. Apair_b: Psg_strmap): cint; cdecl; static;
  116. function GetHandle: Pointer; override;
  117. function IsEOF: Boolean; virtual;
  118. procedure DoChange(AOperation: TBrookStringMapOperation); virtual;
  119. public
  120. { Creates an instance of @code(TBrookStringMap).
  121. @param(AHandle[in] Pointer to store the string map handle.) }
  122. constructor Create(AHandle: Pointer); virtual;
  123. { Frees an instance of @code(TBrookStringMap). }
  124. destructor Destroy; override;
  125. { Copies the properties of the source string map.
  126. @param(ASource[in] String map source to be copied.) }
  127. procedure Assign(ASource: TPersistent); override;
  128. { Checks if the map is empty.
  129. @returns(@True when map is empty, @False otherwise.) }
  130. function IsEmpty: Boolean; virtual;
  131. { Gets an instance of @code(TBrookStringMapEnumerator). }
  132. function GetEnumerator: TBrookStringMapEnumerator;
  133. { Adds a pair of strings to the map.
  134. @param(AName[in] Name of the pair.)
  135. @param(AValue[in] Value of the pair.) }
  136. procedure Add(const AName, AValue: string); virtual;
  137. { Adds or sets a pair of strings to the map.
  138. @param(AName[in] Name of the pair.)
  139. @param(AValue[in] Value of the pair.) }
  140. procedure AddOrSet(const AName, AValue: string); virtual;
  141. { Removes a pair by its name.
  142. @param(AName[in] Name of the pair.) }
  143. procedure Remove(const AName: string); virtual;
  144. { Clears the entire map. }
  145. procedure Clear; virtual;
  146. { Finds a pair by its name.
  147. @param(AName[in] Name of the pair.)
  148. @param(APair[out] Reference to store found pair.)
  149. @returns(@True when pair is found, @False otherwise.) }
  150. function Find(const AName: string;
  151. out APair: TBrookStringPair): Boolean; virtual;
  152. { Checks if map contains a pair by its name.
  153. @param(AName[in] Name of the pair.)
  154. @returns(@True when map contains the pair, @False otherwise.) }
  155. function Contains(const AName: string): Boolean; virtual;
  156. { Gets a pair by name and returns its value.
  157. @param(AName[in] Name of the pair.)
  158. @returns(Pair value.) }
  159. function Get(const AName: string): string; virtual;
  160. { Tries to find a pair value by its name.
  161. @param(AName[in] Name of the pair.)
  162. @param(AValue[out] Reference to store found value.)
  163. @returns(@True when pair is found, @False otherwise.) }
  164. function TryValue(const AName: string;
  165. out AValue: string): Boolean; virtual;
  166. { Retrieves the first pair in the map.
  167. @param(APair[out] First pair returned.)
  168. @returns(@True when pair is found, @False otherwise.) }
  169. function First(out APair: TBrookStringPair): Boolean; virtual;
  170. { Retrieves the next pair in the map.
  171. @param(APair[out] Next pair returned.) }
  172. function Next(out APair: TBrookStringPair): Boolean; virtual;
  173. { Iterates over pairs map.
  174. @param(AIterator[in] Function to iterate the pairs.)
  175. @param(AData[in,out] User-specified value.) }
  176. procedure Iterate(AIterator: TBrookStringMapIterator;
  177. AData: Pointer); virtual;
  178. { Sorts the pairs map.
  179. @param(AComparator[in] Function to sort the pairs.)
  180. @param(AData[in,out] User-specified value.) }
  181. procedure Sort(AComparator: TBrookStringMapComparator;
  182. AData: Pointer); virtual;
  183. { Fetches a string map as an object.
  184. @param(AObject[in] Object with properties that correspond to the fetched
  185. string map.)
  186. @param(AAllowed[in] Array of properties to be allowed when fetching.)
  187. @param(AIgnored[in] Array of properties to be ignored when fetching.) }
  188. procedure Fetch(AObject: TObject; const AAllowed,
  189. AIgnored: array of string); overload; virtual;
  190. { Fetches a string map as an object. }
  191. procedure Fetch(AObject: TObject); overload; virtual;
  192. { Gets the map as big string using equal sign to separate each pair and
  193. ending lines using line break. }
  194. function ToString: string; override;
  195. { Counts the total pairs present in the map. }
  196. property Count: Integer read GetCount;
  197. { Adds or gets the pair value. }
  198. property Values[const AName: string]: string read GetValue
  199. write SetValue; default;
  200. { Indicates the end of map. }
  201. property EOF: Boolean read IsEOF; //FI:C110
  202. { Indicates if the map is empty. }
  203. property Empty: Boolean read IsEmpty; //FI:C110
  204. { Clears the list on destroy. }
  205. property ClearOnDestroy: Boolean read FClearOnDestroy write FClearOnDestroy;
  206. { Notifies a change in the map. }
  207. property OnChange: TBrookStringMapChangeEvent read FOnChange write FOnChange;
  208. end;
  209. implementation
  210. { TBrookStringPair }
  211. constructor TBrookStringPair.Create(const AName, AValue: string);
  212. begin
  213. FName := AName;
  214. FValue := AValue;
  215. end;
  216. { TBrookStringMapEnumerator }
  217. constructor TBrookStringMapEnumerator.Create(AMap: TBrookStringMap);
  218. begin
  219. inherited Create;
  220. FMap := AMap;
  221. FMap.First(FCurr);
  222. FBOF := True;
  223. end;
  224. function TBrookStringMapEnumerator.GetCurrent: TBrookStringPair;
  225. begin
  226. Result := FCurr;
  227. end;
  228. function TBrookStringMapEnumerator.MoveNext: Boolean;
  229. begin
  230. if FBOF then
  231. FBOF := False
  232. else
  233. FMap.Next(FCurr);
  234. Result := not FMap.EOF;
  235. end;
  236. { TBrookStringMap }
  237. constructor TBrookStringMap.Create(AHandle: Pointer);
  238. begin
  239. inherited Create;
  240. if not Assigned(AHandle) then
  241. raise EArgumentNilException.CreateFmt(SParamIsNil, ['AHandle']);
  242. FHandle := AHandle;
  243. FClearOnDestroy := True;
  244. end;
  245. destructor TBrookStringMap.Destroy;
  246. begin
  247. try
  248. if FClearOnDestroy then
  249. Clear;
  250. finally
  251. inherited Destroy;
  252. end;
  253. end;
  254. procedure TBrookStringMap.Assign(ASource: TPersistent);
  255. var
  256. VSource: TBrookStringMap;
  257. VPair: TBrookStringPair;
  258. begin
  259. if ASource is TBrookStringMap then
  260. begin
  261. VSource := ASource as TBrookStringMap;
  262. Clear;
  263. for VPair in VSource do
  264. Add(VPair.Name, VPair.Value);
  265. FClearOnDestroy := VSource.ClearOnDestroy;
  266. end
  267. else
  268. inherited Assign(ASource);
  269. end;
  270. class function TBrookStringMap.CreatePair(Apair: Psg_strmap): TBrookStringPair;
  271. begin
  272. SgLib.Check;
  273. Result := TBrookStringPair.Create(TMarshal.ToString(sg_strmap_name(Apair)),
  274. TMarshal.ToString(sg_strmap_val(Apair)));
  275. end;
  276. class function TBrookStringMap.DoIterate(Acls: Pcvoid; Apair: Psg_strmap): cint;
  277. var
  278. M: PMethod;
  279. begin
  280. M := Acls;
  281. if not Assigned(M.Code) then
  282. Exit(-1);
  283. Result := TBrookStringMapIterator(M.Code)(M.Data, CreatePair(Apair));
  284. end;
  285. class function TBrookStringMap.DoSort(Acls: Pcvoid; Apair_a: Psg_strmap;
  286. Apair_b: Psg_strmap): cint;
  287. var
  288. M: PMethod;
  289. begin
  290. M := Acls;
  291. if not Assigned(M.Code) then
  292. Exit(0);
  293. Result := TBrookStringMapComparator(M.Code)(M.Data, CreatePair(Apair_a),
  294. CreatePair(Apair_b));
  295. end;
  296. function TBrookStringMap.GetEnumerator: TBrookStringMapEnumerator;
  297. begin
  298. Result := TBrookStringMapEnumerator.Create(Self);
  299. end;
  300. function TBrookStringMap.IsEmpty: Boolean;
  301. begin
  302. Result := (not Assigned(FHandle)) or (not Assigned(FHandle^));
  303. end;
  304. function TBrookStringMap.GetCount: Integer;
  305. begin
  306. SgLib.Check;
  307. Result := sg_strmap_count(FHandle^);
  308. end;
  309. function TBrookStringMap.GetValue(const AName: string): string;
  310. begin
  311. if not TryValue(AName, Result) then
  312. Result := '';
  313. end;
  314. procedure TBrookStringMap.SetValue(const AName, AValue: string);
  315. begin
  316. AddOrSet(AName, AValue);
  317. end;
  318. function TBrookStringMap.GetHandle: Pointer;
  319. begin
  320. Result := FHandle;
  321. end;
  322. function TBrookStringMap.IsEOF: Boolean;
  323. begin
  324. Result := not Assigned(FNextHandle);
  325. end;
  326. procedure TBrookStringMap.DoChange(AOperation: TBrookStringMapOperation);
  327. begin
  328. if Assigned(FOnChange) then
  329. FOnChange(Self, AOperation);
  330. end;
  331. procedure TBrookStringMap.Add(const AName, AValue: string);
  332. var
  333. M: TMarshaller;
  334. begin
  335. SgLib.Check;
  336. SgLib.CheckLastError(sg_strmap_add(FHandle, M.ToCString(AName),
  337. M.ToCString(AValue)));
  338. DoChange(sgmoAdd);
  339. end;
  340. procedure TBrookStringMap.AddOrSet(const AName, AValue: string);
  341. var
  342. M: TMarshaller;
  343. begin
  344. SgLib.Check;
  345. SgLib.CheckLastError(sg_strmap_set(FHandle, M.ToCString(AName),
  346. M.ToCString(AValue)));
  347. DoChange(sgmoAddOrSet);
  348. end;
  349. procedure TBrookStringMap.Remove(const AName: string);
  350. var
  351. R: cint;
  352. M: TMarshaller;
  353. begin
  354. SgLib.Check;
  355. R := sg_strmap_rm(FHandle, M.ToCString(AName));
  356. if (R <> 0) and (R <> ENOENT) then
  357. SgLib.CheckLastError(R);
  358. DoChange(sgmoRemove);
  359. end;
  360. procedure TBrookStringMap.Clear;
  361. begin
  362. if not Assigned(FHandle^) then
  363. Exit;
  364. SgLib.Check;
  365. sg_strmap_cleanup(FHandle);
  366. DoChange(sgmoNone);
  367. end;
  368. function TBrookStringMap.Find(const AName: string;
  369. out APair: TBrookStringPair): Boolean;
  370. var
  371. R: cint;
  372. P: Psg_strmap;
  373. M: TMarshaller;
  374. begin
  375. SgLib.Check;
  376. if not Assigned(FHandle^) then
  377. Exit(False);
  378. R := sg_strmap_find(FHandle^, M.ToCString(AName), @P);
  379. Result := R = 0;
  380. if Result then
  381. APair := TBrookStringPair.Create(AName, TMarshal.ToString(sg_strmap_val(P)))
  382. else
  383. if R <> ENOENT then
  384. SgLib.CheckLastError(R);
  385. end;
  386. function TBrookStringMap.Contains(const AName: string): Boolean;
  387. var
  388. P: Psg_strmap;
  389. M: TMarshaller;
  390. begin
  391. Result := sg_strmap_find(FHandle^, M.ToCString(AName), @P) = 0;
  392. end;
  393. function TBrookStringMap.Get(const AName: string): string;
  394. var
  395. M: TMarshaller;
  396. begin
  397. SgLib.Check;
  398. Result := TMarshal.ToString(sg_strmap_get(FHandle^, M.ToCString(AName)));
  399. end;
  400. function TBrookStringMap.TryValue(const AName: string;
  401. out AValue: string): Boolean;
  402. var
  403. P: Pcchar;
  404. M: TMarshaller;
  405. begin
  406. SgLib.Check;
  407. P := sg_strmap_get(FHandle^, M.ToCString(AName));
  408. Result := Assigned(P);
  409. if Result then
  410. AValue := TMarshal.ToString(P);
  411. end;
  412. function TBrookStringMap.First(out APair: TBrookStringPair): Boolean;
  413. begin
  414. FNextHandle := FHandle^;
  415. Result := Assigned(FNextHandle);
  416. if Result then
  417. APair := CreatePair(FNextHandle);
  418. end;
  419. function TBrookStringMap.Next(out APair: TBrookStringPair): Boolean;
  420. begin
  421. SgLib.Check;
  422. SgLib.CheckLastError(sg_strmap_next(@FNextHandle));
  423. Result := Assigned(FNextHandle);
  424. if Result then
  425. APair := CreatePair(FNextHandle);
  426. end;
  427. procedure TBrookStringMap.Iterate(AIterator: TBrookStringMapIterator;
  428. AData: Pointer);
  429. var
  430. R: cint;
  431. M: TMethod;
  432. begin
  433. SgLib.Check;
  434. if not Assigned(FHandle^) then
  435. Exit;
  436. M.Code := @AIterator;
  437. M.Data := AData;
  438. R := sg_strmap_iter(FHandle^, DoIterate, @M);
  439. if R <> -1 then
  440. SgLib.CheckLastError(R);
  441. end;
  442. procedure TBrookStringMap.Sort(AComparator: TBrookStringMapComparator;
  443. AData: Pointer);
  444. var
  445. M: TMethod;
  446. begin
  447. SgLib.Check;
  448. M.Code := @AComparator;
  449. M.Data := AData;
  450. SgLib.CheckLastError(sg_strmap_sort(FHandle, DoSort, @M));
  451. end;
  452. {$IFDEF FPC}
  453. {$PUSH}{$WARN 6058 OFF}
  454. {$ENDIF}
  455. procedure TBrookStringMap.Fetch(AObject: TObject; const AAllowed,
  456. AIgnored: array of string);
  457. var
  458. VPair: TBrookStringPair;
  459. VProp: PPropInfo;
  460. begin
  461. if not Assigned(AObject) then
  462. raise EArgumentNilException.CreateFmt(SParamIsNil, ['AObject']);
  463. for VPair in Self do
  464. begin
  465. VProp := GetPropInfo(AObject, VPair.Name, tkPrimitives);
  466. if Assigned(VProp) and Assigned(VProp^.SetProc) and (not
  467. (((Length(AAllowed) > 0) and (not AnsiMatchText(VPair.Name, AAllowed))) or
  468. ((Length(AIgnored) > 0) and AnsiMatchText(VPair.Name, AIgnored)))) then
  469. SetPropValue(AObject, VProp, VPair.Value);
  470. end;
  471. end;
  472. {$IFDEF FPC}
  473. {$POP}
  474. {$ENDIF}
  475. procedure TBrookStringMap.Fetch(AObject: TObject);
  476. begin
  477. Fetch(AObject, [], []);
  478. end;
  479. function TBrookStringMap.ToString: string;
  480. var
  481. P: TBrookStringPair;
  482. begin
  483. Result := '';
  484. for P in Self do
  485. Result := Concat(Result, P.Name, '=', P.Value, sLineBreak);
  486. end;
  487. end.