BrookStringMap.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. (* _ _
  2. * | |__ _ __ ___ ___ | | __
  3. * | '_ \| '__/ _ \ / _ \| |/ /
  4. * | |_) | | | (_) | (_) | <
  5. * |_.__/|_| \___/ \___/|_|\_\
  6. *
  7. * Microframework which helps to develop web Pascal applications.
  8. *
  9. * Copyright (c) 2012-2020 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; inline;
  111. class function DoIterate(Acls: Pcvoid;
  112. Apair: Psg_strmap): cint; cdecl; static;
  113. class function DoSort(Acls: Pcvoid; Apair_a: Psg_strmap;
  114. Apair_b: Psg_strmap): cint; cdecl; static;
  115. function GetHandle: Pointer; override;
  116. function IsEOF: Boolean; virtual;
  117. procedure DoChange(AOperation: TBrookStringMapOperation); virtual;
  118. public
  119. { Creates an instance of @code(TBrookStringMap).
  120. @param(AHandle[in] Pointer to store the string map handle.) }
  121. constructor Create(AHandle: Pointer); virtual;
  122. { Frees an instance of @code(TBrookStringMap). }
  123. destructor Destroy; override;
  124. { Copies the properties of the source string map.
  125. @param(ASource[in] String map source to be copied.) }
  126. procedure Assign(ASource: TPersistent); override;
  127. { Checks if the map is empty.
  128. @returns(@True when map is empty, @False otherwise.) }
  129. function IsEmpty: Boolean; virtual;
  130. { Gets an instance of @code(TBrookStringMapEnumerator). }
  131. function GetEnumerator: TBrookStringMapEnumerator;
  132. { Adds a pair of strings to the map.
  133. @param(AName[in] Name of the pair.)
  134. @param(AValue[in] Value of the pair.) }
  135. procedure Add(const AName, AValue: string); virtual;
  136. { Adds or sets a pair of strings to the map.
  137. @param(AName[in] Name of the pair.)
  138. @param(AValue[in] Value of the pair.) }
  139. procedure AddOrSet(const AName, AValue: string); virtual;
  140. { Removes a pair by its name.
  141. @param(AName[in] Name of the pair.) }
  142. procedure Remove(const AName: string); virtual;
  143. { Clears the entire map. }
  144. procedure Clear; virtual;
  145. { Finds a pair by its name.
  146. @param(AName[in] Name of the pair.)
  147. @param(APair[out] Reference to store found pair.)
  148. @returns(@True when pair is found, @False otherwise.) }
  149. function Find(const AName: string;
  150. out APair: TBrookStringPair): Boolean; virtual;
  151. { Checks if map contains a pair by its name.
  152. @param(AName[in] Name of the pair.)
  153. @returns(@True when map contains the pair, @False otherwise.) }
  154. function Contains(const AName: string): Boolean; virtual;
  155. { Gets a pair by name and returns its value.
  156. @param(AName[in] Name of the pair.)
  157. @returns(Pair value.) }
  158. function Get(const AName: string): string; virtual;
  159. { Tries to find a pair value by its name.
  160. @param(AName[in] Name of the pair.)
  161. @param(AValue[out] Reference to store found value.)
  162. @returns(@True when pair is found, @False otherwise.) }
  163. function TryValue(const AName: string;
  164. out AValue: string): Boolean; virtual;
  165. { Retrieves the first pair in the map.
  166. @param(APair[out] First pair returned.)
  167. @returns(@True when pair is found, @False otherwise.) }
  168. function First(out APair: TBrookStringPair): Boolean; virtual;
  169. { Retrieves the next pair in the map.
  170. @param(APair[out] Next pair returned.) }
  171. function Next(out APair: TBrookStringPair): Boolean; virtual;
  172. { Iterates over pairs map.
  173. @param(AIterator[in] Function to iterate the pairs.)
  174. @param(AData[in,out] User-specified value.) }
  175. procedure Iterate(AIterator: TBrookStringMapIterator;
  176. AData: Pointer); virtual;
  177. { Sorts the pairs map.
  178. @param(AComparator[in] Function to sort the pairs.)
  179. @param(AData[in,out] User-specified value.) }
  180. procedure Sort(AComparator: TBrookStringMapComparator;
  181. AData: Pointer); virtual;
  182. { Fetches a string map as an object.
  183. @param(AObject[in] Object with properties that correspond to the fetched
  184. string map.)
  185. @param(AAllowed[in] Array of properties to be allowed when fetching.)
  186. @param(AIgnored[in] Array of properties to be ignored when fetching.) }
  187. procedure Fetch(AObject: TObject; const AAllowed,
  188. AIgnored: array of string); overload; virtual;
  189. { Fetches a string map as an object. }
  190. procedure Fetch(AObject: TObject); overload; virtual;
  191. { Gets the map as big string using equal sign to separate each pair and
  192. ending lines using line break. }
  193. function ToString: string; override;
  194. { Counts the total pairs present in the map. }
  195. property Count: Integer read GetCount;
  196. { Adds or gets the pair value. }
  197. property Values[const AName: string]: string read GetValue
  198. write SetValue; default;
  199. { Indicates the end of map. }
  200. property EOF: Boolean read IsEOF; //FI:C110
  201. { Indicates if the map is empty. }
  202. property Empty: Boolean read IsEmpty; //FI:C110
  203. { Clears the list on destroy. }
  204. property ClearOnDestroy: Boolean read FClearOnDestroy write FClearOnDestroy;
  205. { Notifies a change in the map. }
  206. property OnChange: TBrookStringMapChangeEvent read FOnChange write FOnChange;
  207. end;
  208. implementation
  209. { TBrookStringPair }
  210. constructor TBrookStringPair.Create(const AName, AValue: string);
  211. begin
  212. FName := AName;
  213. FValue := AValue;
  214. end;
  215. { TBrookStringMapEnumerator }
  216. constructor TBrookStringMapEnumerator.Create(AMap: TBrookStringMap);
  217. begin
  218. inherited Create;
  219. FMap := AMap;
  220. FMap.First(FCurr);
  221. FBOF := True;
  222. end;
  223. function TBrookStringMapEnumerator.GetCurrent: TBrookStringPair;
  224. begin
  225. Result := FCurr;
  226. end;
  227. function TBrookStringMapEnumerator.MoveNext: Boolean;
  228. begin
  229. if FBOF then
  230. FBOF := False
  231. else
  232. FMap.Next(FCurr);
  233. Result := not FMap.EOF;
  234. end;
  235. { TBrookStringMap }
  236. constructor TBrookStringMap.Create(AHandle: Pointer);
  237. begin
  238. inherited Create;
  239. if not Assigned(AHandle) then
  240. raise EArgumentNilException.CreateFmt(SParamIsNil, ['AHandle']);
  241. FHandle := AHandle;
  242. FClearOnDestroy := True;
  243. end;
  244. destructor TBrookStringMap.Destroy;
  245. begin
  246. try
  247. if FClearOnDestroy then
  248. Clear;
  249. finally
  250. inherited Destroy;
  251. end;
  252. end;
  253. procedure TBrookStringMap.Assign(ASource: TPersistent);
  254. var
  255. VSource: TBrookStringMap;
  256. VPair: TBrookStringPair;
  257. begin
  258. if ASource is TBrookStringMap then
  259. begin
  260. VSource := ASource as TBrookStringMap;
  261. Clear;
  262. for VPair in VSource do
  263. Add(VPair.Name, VPair.Value);
  264. FClearOnDestroy := VSource.ClearOnDestroy;
  265. end
  266. else
  267. inherited Assign(ASource);
  268. end;
  269. class function TBrookStringMap.CreatePair(Apair: Psg_strmap): TBrookStringPair;
  270. begin
  271. SgLib.Check;
  272. Result := TBrookStringPair.Create(TMarshal.ToString(sg_strmap_name(Apair)),
  273. TMarshal.ToString(sg_strmap_val(Apair)));
  274. end;
  275. class function TBrookStringMap.DoIterate(Acls: Pcvoid; Apair: Psg_strmap): cint;
  276. var
  277. M: PMethod;
  278. begin
  279. M := Acls;
  280. if not Assigned(M.Code) then
  281. Exit(-1);
  282. Result := TBrookStringMapIterator(M.Code)(M.Data, CreatePair(Apair));
  283. end;
  284. class function TBrookStringMap.DoSort(Acls: Pcvoid; Apair_a: Psg_strmap;
  285. Apair_b: Psg_strmap): cint;
  286. var
  287. M: PMethod;
  288. begin
  289. M := Acls;
  290. if not Assigned(M.Code) then
  291. Exit(0);
  292. Result := TBrookStringMapComparator(M.Code)(M.Data, CreatePair(Apair_a),
  293. CreatePair(Apair_b));
  294. end;
  295. function TBrookStringMap.GetEnumerator: TBrookStringMapEnumerator;
  296. begin
  297. Result := TBrookStringMapEnumerator.Create(Self);
  298. end;
  299. function TBrookStringMap.IsEmpty: Boolean;
  300. begin
  301. Result := (not Assigned(FHandle)) or (not Assigned(FHandle^));
  302. end;
  303. function TBrookStringMap.GetCount: Integer;
  304. begin
  305. SgLib.Check;
  306. Result := sg_strmap_count(FHandle^);
  307. end;
  308. function TBrookStringMap.GetValue(const AName: string): string;
  309. begin
  310. if not TryValue(AName, Result) then
  311. Result := '';
  312. end;
  313. procedure TBrookStringMap.SetValue(const AName, AValue: string);
  314. begin
  315. AddOrSet(AName, AValue);
  316. end;
  317. function TBrookStringMap.GetHandle: Pointer;
  318. begin
  319. Result := FHandle;
  320. end;
  321. function TBrookStringMap.IsEOF: Boolean;
  322. begin
  323. Result := not Assigned(FNextHandle);
  324. end;
  325. procedure TBrookStringMap.DoChange(AOperation: TBrookStringMapOperation);
  326. begin
  327. if Assigned(FOnChange) then
  328. FOnChange(Self, AOperation);
  329. end;
  330. procedure TBrookStringMap.Add(const AName, AValue: string);
  331. var
  332. M: TMarshaller;
  333. begin
  334. SgLib.Check;
  335. SgLib.CheckLastError(sg_strmap_add(FHandle, M.ToCString(AName),
  336. M.ToCString(AValue)));
  337. DoChange(sgmoAdd);
  338. end;
  339. procedure TBrookStringMap.AddOrSet(const AName, AValue: string);
  340. var
  341. M: TMarshaller;
  342. begin
  343. SgLib.Check;
  344. SgLib.CheckLastError(sg_strmap_set(FHandle, M.ToCString(AName),
  345. M.ToCString(AValue)));
  346. DoChange(sgmoAddOrSet);
  347. end;
  348. procedure TBrookStringMap.Remove(const AName: string);
  349. var
  350. R: cint;
  351. M: TMarshaller;
  352. begin
  353. SgLib.Check;
  354. R := sg_strmap_rm(FHandle, M.ToCString(AName));
  355. if (R <> 0) and (R <> ENOENT) then
  356. SgLib.CheckLastError(R);
  357. DoChange(sgmoRemove);
  358. end;
  359. procedure TBrookStringMap.Clear;
  360. begin
  361. if not Assigned(FHandle^) then
  362. Exit;
  363. SgLib.Check;
  364. sg_strmap_cleanup(FHandle);
  365. DoChange(sgmoNone);
  366. end;
  367. function TBrookStringMap.Find(const AName: string;
  368. out APair: TBrookStringPair): Boolean;
  369. var
  370. R: cint;
  371. P: Psg_strmap;
  372. M: TMarshaller;
  373. begin
  374. SgLib.Check;
  375. if not Assigned(FHandle^) then
  376. Exit(False);
  377. R := sg_strmap_find(FHandle^, M.ToCString(AName), @P);
  378. Result := R = 0;
  379. if Result then
  380. APair := TBrookStringPair.Create(AName, TMarshal.ToString(sg_strmap_val(P)))
  381. else
  382. if R <> ENOENT then
  383. SgLib.CheckLastError(R);
  384. end;
  385. function TBrookStringMap.Contains(const AName: string): Boolean;
  386. var
  387. P: Psg_strmap;
  388. M: TMarshaller;
  389. begin
  390. Result := sg_strmap_find(FHandle^, M.ToCString(AName), @P) = 0;
  391. end;
  392. function TBrookStringMap.Get(const AName: string): string;
  393. var
  394. M: TMarshaller;
  395. begin
  396. SgLib.Check;
  397. Result := TMarshal.ToString(sg_strmap_get(FHandle^, M.ToCString(AName)));
  398. end;
  399. function TBrookStringMap.TryValue(const AName: string;
  400. out AValue: string): Boolean;
  401. var
  402. P: Pcchar;
  403. M: TMarshaller;
  404. begin
  405. SgLib.Check;
  406. P := sg_strmap_get(FHandle^, M.ToCString(AName));
  407. Result := Assigned(P);
  408. if Result then
  409. AValue := TMarshal.ToString(P);
  410. end;
  411. function TBrookStringMap.First(out APair: TBrookStringPair): Boolean;
  412. begin
  413. FNextHandle := FHandle^;
  414. Result := Assigned(FNextHandle);
  415. if Result then
  416. APair := CreatePair(FNextHandle);
  417. end;
  418. function TBrookStringMap.Next(out APair: TBrookStringPair): Boolean;
  419. begin
  420. SgLib.Check;
  421. SgLib.CheckLastError(sg_strmap_next(@FNextHandle));
  422. Result := Assigned(FNextHandle);
  423. if Result then
  424. APair := CreatePair(FNextHandle);
  425. end;
  426. procedure TBrookStringMap.Iterate(AIterator: TBrookStringMapIterator;
  427. AData: Pointer);
  428. var
  429. R: cint;
  430. M: TMethod;
  431. begin
  432. SgLib.Check;
  433. if not Assigned(FHandle^) then
  434. Exit;
  435. M.Code := @AIterator;
  436. M.Data := AData;
  437. R := sg_strmap_iter(FHandle^, DoIterate, @M);
  438. if R <> -1 then
  439. SgLib.CheckLastError(R);
  440. end;
  441. procedure TBrookStringMap.Sort(AComparator: TBrookStringMapComparator;
  442. AData: Pointer);
  443. var
  444. M: TMethod;
  445. begin
  446. SgLib.Check;
  447. M.Code := @AComparator;
  448. M.Data := AData;
  449. SgLib.CheckLastError(sg_strmap_sort(FHandle, DoSort, @M));
  450. end;
  451. {$IFDEF FPC}
  452. {$PUSH}{$WARN 6058 OFF}
  453. {$ENDIF}
  454. procedure TBrookStringMap.Fetch(AObject: TObject; const AAllowed,
  455. AIgnored: array of string);
  456. var
  457. VPair: TBrookStringPair;
  458. VProp: PPropInfo;
  459. begin
  460. if not Assigned(AObject) then
  461. raise EArgumentNilException.CreateFmt(SParamIsNil, ['AObject']);
  462. for VPair in Self do
  463. begin
  464. VProp := GetPropInfo(AObject, VPair.Name, tkPrimitives);
  465. if Assigned(VProp) and Assigned(VProp^.SetProc) and (not
  466. (((Length(AAllowed) > 0) and (not AnsiMatchText(VPair.Name, AAllowed))) or
  467. ((Length(AIgnored) > 0) and AnsiMatchText(VPair.Name, AIgnored)))) then
  468. SetPropValue(AObject, VProp, VPair.Value);
  469. end;
  470. end;
  471. {$IFDEF FPC}
  472. {$POP}
  473. {$ENDIF}
  474. procedure TBrookStringMap.Fetch(AObject: TObject);
  475. begin
  476. Fetch(AObject, [], []);
  477. end;
  478. function TBrookStringMap.ToString: string;
  479. var
  480. P: TBrookStringPair;
  481. begin
  482. Result := '';
  483. for P in Self do
  484. Result := Concat(Result, P.Name, '=', P.Value, sLineBreak);
  485. end;
  486. end.