BrookStringMap.pas 15 KB

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