inifiles.pp 27 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 1999-2000 Erik WachtMeester.
  4. File which provides TIniFile and friends.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {* Original disclaimer:
  12. * FCL inifiles.pp rewrite by Erik Wachtmeester ([email protected])
  13. *
  14. * Proposed replacement for inifiles.pp v 1.8
  15. *
  16. * This version is Borland Delphi 5 compatible, implementing the classes
  17. * TCustomIniFile, TIniFile and TMemIniFile, with all the public
  18. * properties and methods that Delphi 5 implements.
  19. *
  20. * (inifiles.pp v 1.8 only implements TIniFile with some properties and
  21. * methods missing, and some functionality added)
  22. *
  23. * In order to stay compatible with v 1.8, I added:
  24. * - TIniFile can be created and loaded from, and saved to a stream.
  25. * - ReadSectionRaw method (although it doesn't add empty lines to the
  26. * TStrings recipient like v 1.8, since empty lines aren't stored in
  27. * the SectionList object structure)
  28. * - ReadInteger supports '0x' type hex formats
  29. * - Comment support (this isn't standard in ini files)
  30. * - EscapeLineFeeds creation parameter
  31. *
  32. * Since the SectionList object structure is very different from the
  33. * way Delphi 5 accesses ini files (Delphi mostly uses Windows calls
  34. * like GetPrivateProfileString, etc.) it's completely platform
  35. * independant, and probably faster.
  36. * The only drawback is memory consumption: all sections, keys and
  37. * values are kept in memory. But same goes for inifiles.pp v 1.8
  38. * (the FFileBuffer member) and for Delphi's TMemIniFile.
  39. * Anyway, Windows restricts ini files to 64K max, so this shouldn't be
  40. * too much of a problem.
  41. *
  42. *}
  43. unit IniFiles;
  44. {$mode objfpc}
  45. {$H+}
  46. interface
  47. uses classes, sysutils, contnrs;
  48. type
  49. { THashedStringList }
  50. THashedStringList = class(TStringList)
  51. private
  52. FValueHash: TFPHashList;
  53. FNameHash: TFPHashList;
  54. FValueHashValid: Boolean;
  55. FNameHashValid: Boolean;
  56. procedure UpdateValueHash;
  57. procedure UpdateNameHash;
  58. protected
  59. procedure Changed; override;
  60. public
  61. constructor Create;
  62. destructor Destroy; override;
  63. function IndexOf(const S: String): Integer; override;
  64. function IndexOfName(const Name: String): Integer; override;
  65. end;
  66. TIniFileKey = class
  67. Private
  68. FIdent: string;
  69. FValue: string;
  70. public
  71. constructor Create(const AIdent, AValue: string);
  72. property Ident: string read FIdent write FIdent;
  73. property Value: string read FValue write FValue;
  74. end;
  75. TIniFileKeyList = class(TList)
  76. private
  77. function GetItem(Index: integer): TIniFileKey;
  78. function KeyByName(const AName: string; CaseSensitive : Boolean): TIniFileKey;
  79. public
  80. destructor Destroy; override;
  81. procedure Clear; override;
  82. property Items[Index: integer]: TIniFileKey read GetItem; default;
  83. end;
  84. TIniFileSection = class
  85. private
  86. FName: string;
  87. FKeyList: TIniFileKeyList;
  88. public
  89. Function Empty : Boolean;
  90. constructor Create(const AName: string);
  91. destructor Destroy; override;
  92. property Name: string read FName;
  93. property KeyList: TIniFileKeyList read FKeyList;
  94. end;
  95. TIniFileSectionList = class(TList)
  96. private
  97. function GetItem(Index: integer): TIniFileSection;
  98. function SectionByName(const AName: string; CaseSensitive : Boolean): TIniFileSection;
  99. public
  100. destructor Destroy; override;
  101. procedure Clear;override;
  102. property Items[Index: integer]: TIniFileSection read GetItem; default;
  103. end;
  104. TCustomIniFile = class
  105. Private
  106. FFileName: string;
  107. FSectionList: TIniFileSectionList;
  108. FEscapeLineFeeds: boolean;
  109. FCaseSensitive : Boolean;
  110. FStripQuotes : Boolean;
  111. public
  112. constructor Create(const AFileName: string; AEscapeLineFeeds : Boolean = False); virtual;
  113. destructor Destroy; override;
  114. function SectionExists(const Section: string): Boolean; virtual;
  115. function ReadString(const Section, Ident, Default: string): string; virtual; abstract;
  116. procedure WriteString(const Section, Ident, Value: String); virtual; abstract;
  117. function ReadInteger(const Section, Ident: string; Default: Longint): Longint; virtual;
  118. procedure WriteInteger(const Section, Ident: string; Value: Longint); virtual;
  119. function ReadBool(const Section, Ident: string; Default: Boolean): Boolean; virtual;
  120. procedure WriteBool(const Section, Ident: string; Value: Boolean); virtual;
  121. function ReadDate(const Section, Ident: string; Default: TDateTime): TDateTime; virtual;
  122. function ReadDateTime(const Section, Ident: string; Default: TDateTime): TDateTime; virtual;
  123. function ReadFloat(const Section, Ident: string; Default: Double): Double; virtual;
  124. function ReadTime(const Section, Ident: string; Default: TDateTime): TDateTime; virtual;
  125. function ReadBinaryStream(const Section, Name: string; Value: TStream): Integer; virtual;
  126. procedure WriteDate(const Section, Ident: string; Value: TDateTime); virtual;
  127. procedure WriteDateTime(const Section, Ident: string; Value: TDateTime); virtual;
  128. procedure WriteFloat(const Section, Ident: string; Value: Double); virtual;
  129. procedure WriteTime(const Section, Ident: string; Value: TDateTime); virtual;
  130. procedure WriteBinaryStream(const Section, Name: string; Value: TStream); virtual;
  131. procedure ReadSection(const Section: string; Strings: TStrings); virtual; abstract;
  132. procedure ReadSections(Strings: TStrings); virtual; abstract;
  133. procedure ReadSectionValues(const Section: string; Strings: TStrings); virtual; abstract;
  134. procedure EraseSection(const Section: string); virtual; abstract;
  135. procedure DeleteKey(const Section, Ident: String); virtual; abstract;
  136. procedure UpdateFile; virtual; abstract;
  137. function ValueExists(const Section, Ident: string): Boolean; virtual;
  138. property FileName: string read FFileName;
  139. property EscapeLineFeeds: boolean read FEscapeLineFeeds;
  140. Property CaseSensitive : Boolean Read FCaseSensitive Write FCaseSensitive;
  141. Property StripQuotes : Boolean Read FStripQuotes Write FStripQuotes;
  142. end;
  143. { TIniFile }
  144. TIniFile = class(TCustomIniFile)
  145. Private
  146. FStream: TStream;
  147. FCacheUpdates: Boolean;
  148. FDirty : Boolean;
  149. procedure FillSectionList(AStrings: TStrings);
  150. Procedure DeleteSection(ASection : TIniFileSection);
  151. Procedure MaybeDeleteSection(ASection : TIniFileSection);
  152. procedure SetCacheUpdates(const AValue: Boolean);
  153. protected
  154. procedure MaybeUpdateFile;
  155. property Dirty : Boolean Read FDirty;
  156. public
  157. constructor Create(const AFileName: string; AEscapeLineFeeds : Boolean = False); override;
  158. constructor Create(AStream: TStream; AEscapeLineFeeds : Boolean = False);
  159. destructor Destroy; override;
  160. function ReadString(const Section, Ident, Default: string): string; override;
  161. procedure WriteString(const Section, Ident, Value: String); override;
  162. procedure ReadSection(const Section: string; Strings: TStrings); override;
  163. procedure ReadSectionRaw(const Section: string; Strings: TStrings);
  164. procedure ReadSections(Strings: TStrings); override;
  165. procedure ReadSectionValues(const Section: string; Strings: TStrings); override;
  166. procedure EraseSection(const Section: string); override;
  167. procedure DeleteKey(const Section, Ident: String); override;
  168. procedure UpdateFile; override;
  169. property Stream: TStream read FStream;
  170. property CacheUpdates : Boolean read FCacheUpdates write SetCacheUpdates;
  171. end;
  172. TMemIniFile = class(TIniFile)
  173. public
  174. constructor Create(const AFileName: string; AEscapeLineFeeds : Boolean = False); override;
  175. procedure Clear;
  176. procedure GetStrings(List: TStrings);
  177. procedure Rename(const AFileName: string; Reload: Boolean);
  178. procedure SetStrings(List: TStrings);
  179. end;
  180. implementation
  181. const
  182. Brackets : array[0..1] of Char = ('[', ']');
  183. Separator : Char = '=';
  184. Comment : Char = ';';
  185. LF_Escape : Char = '\';
  186. function CharToBool(AChar: char): boolean;
  187. begin
  188. Result := (Achar = '1');
  189. end;
  190. function BoolToChar(ABool: boolean): char;
  191. begin
  192. if ABool then
  193. Result := '1'
  194. else
  195. Result := '0';
  196. end;
  197. function IsComment(const AString: string): boolean;
  198. begin
  199. Result := False;
  200. if AString > '' then
  201. Result := (Copy(AString, 1, 1) = Comment);
  202. end;
  203. { THashedStringList }
  204. constructor THashedStringList.Create;
  205. begin
  206. inherited;
  207. FValueHash := nil;
  208. FNameHash := nil;
  209. FValueHashValid := False;
  210. FNameHashValid := False;
  211. end;
  212. destructor THashedStringList.Destroy;
  213. begin
  214. if Assigned(FValueHash) then
  215. FValueHash.Free;
  216. if Assigned(FNameHash) then
  217. FNameHash.Free;
  218. inherited Destroy;
  219. end;
  220. function THashedStringList.IndexOf(const S: String): Integer;
  221. var
  222. I: Integer;
  223. begin
  224. if not FValueHashValid then
  225. UpdateValueHash;
  226. I := FValueHash.FindIndexOf(S);
  227. if I >= 0 then
  228. Result := Integer(FValueHash[I])-1
  229. else
  230. Result := -1;
  231. end;
  232. function THashedStringList.IndexOfName(const Name: String): Integer;
  233. var
  234. I: Integer;
  235. begin
  236. if not FNameHashValid then
  237. UpdateNameHash;
  238. I := FNameHash.FindIndexOf(Name);
  239. if I >= 0 then
  240. Result := Integer(FNameHash[I])-1
  241. else
  242. Result := -1;
  243. end;
  244. procedure THashedStringList.Changed;
  245. begin
  246. FValueHashValid := False;
  247. FNameHashValid := False;
  248. inherited Changed;
  249. end;
  250. procedure THashedStringList.UpdateValueHash;
  251. var
  252. I: Integer;
  253. begin
  254. if not Assigned(FValueHash) then
  255. FValueHash := TFPHashList.Create
  256. else
  257. FValueHash.Clear;
  258. for I := 0 to Count - 1 do
  259. FValueHash.Add(Strings[I], Pointer(I+1));
  260. FValueHashValid := True;
  261. end;
  262. procedure THashedStringList.UpdateNameHash;
  263. var
  264. I: Integer;
  265. begin
  266. if not Assigned(FNameHash) then
  267. FNameHash := TFPHashList.Create
  268. else
  269. FNameHash.Clear;
  270. for I := 0 to Count - 1 do
  271. FNameHash.Add(Names[I], Pointer(I+1));
  272. FNameHashValid := True;
  273. end;
  274. { TIniFileKey }
  275. constructor TIniFileKey.Create(const AIdent, AValue: string);
  276. begin
  277. FIdent := AIdent;
  278. FValue := AValue;
  279. end;
  280. { TIniFileKeyList }
  281. function TIniFileKeyList.GetItem(Index: integer): TIniFileKey;
  282. begin
  283. Result := nil;
  284. if (Index >= 0) and (Index < Count) then
  285. Result := TIniFileKey(inherited Items[Index]);
  286. end;
  287. function TIniFileKeyList.KeyByName(const AName: string; CaseSensitive : Boolean): TIniFileKey;
  288. var
  289. i: integer;
  290. begin
  291. Result := nil;
  292. if (AName > '') and not IsComment(AName) then
  293. If CaseSensitive then
  294. begin
  295. for i := 0 to Count-1 do
  296. if Items[i].Ident=AName then
  297. begin
  298. Result := Items[i];
  299. Break;
  300. end;
  301. end
  302. else
  303. for i := 0 to Count-1 do
  304. if CompareText(Items[i].Ident, AName) = 0 then begin
  305. Result := Items[i];
  306. Break;
  307. end;
  308. end;
  309. destructor TIniFileKeyList.Destroy;
  310. begin
  311. Clear;
  312. inherited Destroy;
  313. end;
  314. procedure TIniFileKeyList.Clear;
  315. var
  316. i: integer;
  317. begin
  318. for i := Count-1 downto 0 do
  319. Items[i].Free;
  320. inherited Clear;
  321. end;
  322. Function TIniFileSection.Empty : Boolean;
  323. Var
  324. I : Integer;
  325. begin
  326. Result:=True;
  327. I:=0;
  328. While Result and (I<KeyList.Count) do
  329. begin
  330. result:=IsComment(KeyList[i].Ident);
  331. Inc(i);
  332. end;
  333. end;
  334. { TIniFileSection }
  335. constructor TIniFileSection.Create(const AName: string);
  336. begin
  337. FName := AName;
  338. FKeyList := TIniFileKeyList.Create;
  339. end;
  340. destructor TIniFileSection.Destroy;
  341. begin
  342. FKeyList.Free;
  343. end;
  344. { TIniFileSectionList }
  345. function TIniFileSectionList.GetItem(Index: integer): TIniFileSection;
  346. begin
  347. Result := nil;
  348. if (Index >= 0) and (Index < Count) then
  349. Result := TIniFileSection(inherited Items[Index]);
  350. end;
  351. function TIniFileSectionList.SectionByName(const AName: string; CaseSensitive : Boolean): TIniFileSection;
  352. var
  353. i: integer;
  354. begin
  355. Result := nil;
  356. if (AName > '') and not IsComment(AName) then
  357. If CaseSensitive then
  358. begin
  359. for i:=0 to Count-1 do
  360. if (Items[i].Name=AName) then
  361. begin
  362. Result := Items[i];
  363. Break;
  364. end;
  365. end
  366. else
  367. for i := 0 to Count-1 do
  368. if CompareText(Items[i].Name, AName) = 0 then
  369. begin
  370. Result := Items[i];
  371. Break;
  372. end;
  373. end;
  374. destructor TIniFileSectionList.Destroy;
  375. begin
  376. Clear;
  377. inherited Destroy;
  378. end;
  379. procedure TIniFileSectionList.Clear;
  380. var
  381. i: integer;
  382. begin
  383. for i := Count-1 downto 0 do
  384. Items[i].Free;
  385. inherited Clear;
  386. end;
  387. { TCustomIniFile }
  388. constructor TCustomIniFile.Create(const AFileName: string; AEscapeLineFeeds : Boolean = False);
  389. begin
  390. FFileName := AFileName;
  391. FSectionList := TIniFileSectionList.Create;
  392. FEscapeLineFeeds := AEscapeLineFeeds;
  393. end;
  394. destructor TCustomIniFile.Destroy;
  395. begin
  396. FSectionList.Free;
  397. inherited Destroy;
  398. end;
  399. function TCustomIniFile.SectionExists(const Section: string): Boolean;
  400. Var
  401. S : TIniFileSection;
  402. begin
  403. S:=FSectionList.SectionByName(Section,CaseSensitive);
  404. Result:=Assigned(S) and Not S.Empty;
  405. end;
  406. function TCustomIniFile.ReadInteger(const Section, Ident: string; Default: Longint): Longint;
  407. begin
  408. // StrToInfDef() supports hex numbers prefixed with '0x' via val()
  409. Result := StrToIntDef(ReadString(Section, Ident, ''), Default);
  410. end;
  411. procedure TCustomIniFile.WriteInteger(const Section, Ident: string; Value: Longint);
  412. begin
  413. WriteString(Section, Ident, IntToStr(Value));
  414. end;
  415. function TCustomIniFile.ReadBool(const Section, Ident: string; Default: Boolean): Boolean;
  416. var
  417. s: string;
  418. begin
  419. Result := Default;
  420. s := ReadString(Section, Ident, '');
  421. if s > '' then
  422. Result := CharToBool(s[1]);
  423. end;
  424. procedure TCustomIniFile.WriteBool(const Section, Ident: string; Value: Boolean);
  425. begin
  426. WriteString(Section, Ident, BoolToChar(Value));
  427. end;
  428. function TCustomIniFile.ReadDate(const Section, Ident: string; Default: TDateTime): TDateTime;
  429. begin
  430. Result := StrToDateDef(ReadString(Section, Ident, ''),Default);
  431. end;
  432. function TCustomIniFile.ReadDateTime(const Section, Ident: string; Default: TDateTime): TDateTime;
  433. begin
  434. Result := StrToDateTimeDef(ReadString(Section, Ident, ''),Default);
  435. end;
  436. function TCustomIniFile.ReadFloat(const Section, Ident: string; Default: Double): Double;
  437. begin
  438. Result:=StrToFloatDef(ReadString(Section, Ident, ''),Default);
  439. end;
  440. function TCustomIniFile.ReadTime(const Section, Ident: string; Default: TDateTime): TDateTime;
  441. begin
  442. Result := StrToTimeDef(ReadString(Section, Ident, ''),Default);
  443. end;
  444. procedure TCustomIniFile.WriteDate(const Section, Ident: string; Value: TDateTime);
  445. begin
  446. WriteString(Section, Ident, DateToStr(Value));
  447. end;
  448. procedure TCustomIniFile.WriteDateTime(const Section, Ident: string; Value: TDateTime);
  449. begin
  450. WriteString(Section, Ident, DateTimeToStr(Value));
  451. end;
  452. procedure TCustomIniFile.WriteFloat(const Section, Ident: string; Value: Double);
  453. begin
  454. WriteString(Section, Ident, FloatToStr(Value));
  455. end;
  456. procedure TCustomIniFile.WriteTime(const Section, Ident: string; Value: TDateTime);
  457. begin
  458. WriteString(Section, Ident, TimeToStr(Value));
  459. end;
  460. function TCustomIniFile.ValueExists(const Section, Ident: string): Boolean;
  461. var
  462. oSection: TIniFileSection;
  463. begin
  464. Result := False;
  465. oSection := FSectionList.SectionByName(Section,CaseSensitive);
  466. if oSection <> nil then
  467. Result := (oSection.KeyList.KeyByName(Ident,CaseSensitive) <> nil);
  468. end;
  469. function TCustomIniFile.ReadBinaryStream(const Section, Name: string; Value: TStream): Integer;
  470. Var
  471. M : TMemoryStream;
  472. S : String;
  473. PB,PR : PByte;
  474. PC : PChar;
  475. H : String[3];
  476. i,l2,code : Integer;
  477. begin
  478. S:=ReadString(Section,Name,'');
  479. Setlength(H,3);
  480. H[1]:='$';
  481. Result:=Length(S) div 2;
  482. If Result>0 then
  483. begin
  484. GetMem(PR,Result);
  485. Try
  486. PC:=PChar(S);
  487. PB:=PR;
  488. For I:=1 to Result do
  489. begin
  490. H[2]:=PC[0];
  491. H[3]:=PC[1];
  492. Val(H,PB^,code);
  493. Inc(PC,2);
  494. Inc(PB);
  495. end;
  496. Value.WriteBuffer(PR^,Result);
  497. finally
  498. FreeMem(PR);
  499. end;
  500. end;
  501. end;
  502. procedure TCustomInifile.WriteBinaryStream(const Section, Name: string; Value: TStream);
  503. Var
  504. M : TMemoryStream;
  505. S : String;
  506. PB : PByte;
  507. PC : PChar;
  508. H : String[2];
  509. i : Integer;
  510. begin
  511. M:=TMemoryStream.Create;
  512. Try
  513. M.CopyFrom(Value,0);
  514. SetLength(S,M.Size*2);
  515. If (length(S)>0) then
  516. begin
  517. PB:=M.Memory;
  518. PC:=PChar(S);
  519. For I:=1 to Length(S) div 2 do
  520. begin
  521. H:=HexStr(PB^,2);
  522. PC[0]:=H[1];
  523. PC[1]:=H[2];
  524. Inc(PC,2);
  525. Inc(PB);
  526. end;
  527. end;
  528. WriteString(Section,Name,S);
  529. Finally
  530. M.Free;
  531. end;
  532. end;
  533. { TIniFile }
  534. constructor TIniFile.Create(const AFileName: string; AEscapeLineFeeds : Boolean = False);
  535. var
  536. slLines: TStringList;
  537. begin
  538. If Not (self is TMemIniFile) then
  539. StripQuotes:=True;
  540. inherited Create(AFileName,AEscapeLineFeeds);
  541. FStream := nil;
  542. slLines := TStringList.Create;
  543. try
  544. if FileExists(FFileName) then
  545. begin
  546. // read the ini file values
  547. slLines.LoadFromFile(FFileName);
  548. FillSectionList(slLines);
  549. end
  550. finally
  551. slLines.Free;
  552. end;
  553. end;
  554. constructor TIniFile.Create(AStream: TStream; AEscapeLineFeeds : Boolean = False);
  555. var
  556. slLines: TStringList;
  557. begin
  558. inherited Create('',AEscapeLineFeeds);
  559. FStream := AStream;
  560. slLines := TStringList.Create;
  561. try
  562. // read the ini file values
  563. slLines.LoadFromStream(FStream);
  564. FillSectionList(slLines);
  565. finally
  566. slLines.Free;
  567. end;
  568. end;
  569. destructor TIniFile.destroy;
  570. begin
  571. If FDirty and FCacheUpdates then
  572. try
  573. UpdateFile;
  574. except
  575. // Eat exception. Compatible to D7 behaviour, see comments to bug 19046
  576. end;
  577. inherited destroy;
  578. end;
  579. procedure TIniFile.FillSectionList(AStrings: TStrings);
  580. var
  581. i,j: integer;
  582. sLine, sIdent, sValue: string;
  583. oSection: TIniFileSection;
  584. procedure RemoveBackslashes;
  585. var
  586. i,l: integer;
  587. s: string;
  588. bAppendNextLine, bAppended: boolean;
  589. begin
  590. AStrings.BeginUpdate;
  591. try
  592. For I:=AStrings.Count-2 downto 0 do
  593. begin
  594. S:=AStrings[i];
  595. L:=Length(S);
  596. If (I<AStrings.Count-1) and (L>0) and (S[L]=LF_Escape) then
  597. begin
  598. S:=Copy(S,1,L-1)+AStrings[I+1];
  599. AStrings.Delete(I+1);
  600. AStrings[i]:=S;
  601. end;
  602. end;
  603. finally
  604. AStrings.EndUpdate;
  605. end;
  606. end;
  607. begin
  608. oSection := nil;
  609. FSectionList.Clear;
  610. if FEscapeLineFeeds then
  611. RemoveBackslashes;
  612. for i := 0 to AStrings.Count-1 do begin
  613. sLine := Trim(AStrings[i]);
  614. if sLine > '' then
  615. begin
  616. if IsComment(sLine) and (oSection = nil) then begin
  617. // comment at the beginning of the ini file
  618. oSection := TIniFileSection.Create(sLine);
  619. FSectionList.Add(oSection);
  620. continue;
  621. end;
  622. if (Copy(sLine, 1, 1) = Brackets[0]) and (Copy(sLine, length(sLine), 1) = Brackets[1]) then begin
  623. // regular section
  624. oSection := TIniFileSection.Create(Copy(sLine, 2, Length(sLine) - 2));
  625. FSectionList.Add(oSection);
  626. end else if oSection <> nil then begin
  627. if IsComment(sLine) then begin
  628. // comment within a section
  629. sIdent := sLine;
  630. sValue := '';
  631. end else begin
  632. // regular key
  633. j:=Pos(Separator, sLine);
  634. if j=0 then
  635. begin
  636. sIdent:='';
  637. sValue:=sLine
  638. end
  639. else
  640. begin
  641. sIdent:=Trim(Copy(sLine, 1, j - 1));
  642. sValue:=Trim(Copy(sLine, j + 1, Length(sLine) - j));
  643. end;
  644. end;
  645. oSection.KeyList.Add(TIniFileKey.Create(sIdent, sValue));
  646. end;
  647. end;
  648. end;
  649. end;
  650. function TIniFile.ReadString(const Section, Ident, Default: string): string;
  651. var
  652. oSection: TIniFileSection;
  653. oKey: TIniFileKey;
  654. J: integer;
  655. begin
  656. Result := Default;
  657. oSection := FSectionList.SectionByName(Section,CaseSensitive);
  658. if oSection <> nil then begin
  659. oKey := oSection.KeyList.KeyByName(Ident,CaseSensitive);
  660. if oKey <> nil then
  661. If StripQuotes then
  662. begin
  663. J:=Length(oKey.Value);
  664. // Joost, 2-jan-2007: The check (J>1) is there for the case that
  665. // the value consist of a single double-quote character. (see
  666. // mantis bug 6555)
  667. If (J>1) and ((oKey.Value[1] in ['"','''']) and (oKey.Value[J]=oKey.Value[1])) then
  668. Result:=Copy(oKey.Value,2,J-2)
  669. else
  670. Result:=oKey.Value;
  671. end
  672. else Result:=oKey.Value;
  673. end;
  674. end;
  675. procedure TIniFile.SetCacheUpdates(const AValue: Boolean);
  676. begin
  677. if FCacheUpdates and not AValue and FDirty then
  678. UpdateFile;
  679. FCacheUpdates := AValue;
  680. end;
  681. procedure TIniFile.WriteString(const Section, Ident, Value: String);
  682. var
  683. oSection: TIniFileSection;
  684. oKey: TIniFileKey;
  685. begin
  686. if (Section > '') and (Ident > '') then begin
  687. // update or add key
  688. oSection := FSectionList.SectionByName(Section,CaseSensitive);
  689. if (Value > '') then begin
  690. if oSection = nil then begin
  691. oSection := TIniFileSection.Create(Section);
  692. FSectionList.Add(oSection);
  693. end;
  694. with oSection.KeyList do begin
  695. oKey := KeyByName(Ident,CaseSensitive);
  696. if oKey <> nil then
  697. oKey.Value := Value
  698. else
  699. oSection.KeyList.Add(TIniFileKey.Create(Ident, Value));
  700. end;
  701. end else if oSection <> nil then begin
  702. // remove key
  703. oKey := oSection.KeyList.KeyByName(Ident,CaseSensitive);
  704. if oKey <> nil then begin
  705. oSection.KeyList.Remove(oKey);
  706. end;
  707. end;
  708. end;
  709. MaybeUpdateFile;
  710. end;
  711. procedure TIniFile.ReadSection(const Section: string; Strings: TStrings);
  712. var
  713. oSection: TIniFileSection;
  714. i: integer;
  715. begin
  716. Strings.BeginUpdate;
  717. try
  718. Strings.Clear;
  719. oSection := FSectionList.SectionByName(Section,CaseSensitive);
  720. if oSection <> nil then with oSection.KeyList do
  721. for i := 0 to Count-1 do
  722. if not IsComment(Items[i].Ident) then
  723. Strings.Add(Items[i].Ident);
  724. finally
  725. Strings.EndUpdate;
  726. end;
  727. end;
  728. procedure TIniFile.ReadSectionRaw(const Section: string; Strings: TStrings);
  729. var
  730. oSection: TIniFileSection;
  731. i: integer;
  732. begin
  733. Strings.BeginUpdate;
  734. try
  735. Strings.Clear;
  736. oSection := FSectionList.SectionByName(Section,CaseSensitive);
  737. if oSection <> nil then with oSection.KeyList do
  738. for i := 0 to Count-1 do
  739. if not IsComment(Items[i].Ident) then
  740. begin
  741. if Items[i].Ident<>'' then
  742. Strings.Add(Items[i].Ident + Separator +Items[i].Value)
  743. else
  744. Strings.Add(Items[i].Value);
  745. end;
  746. finally
  747. Strings.EndUpdate;
  748. end;
  749. end;
  750. procedure TIniFile.ReadSections(Strings: TStrings);
  751. var
  752. i: integer;
  753. begin
  754. Strings.BeginUpdate;
  755. try
  756. Strings.Clear;
  757. for i := 0 to FSectionList.Count-1 do
  758. if not IsComment(FSectionList[i].Name) then
  759. Strings.Add(FSectionList[i].Name);
  760. finally
  761. Strings.EndUpdate;
  762. end;
  763. end;
  764. procedure TIniFile.ReadSectionValues(const Section: string; Strings: TStrings);
  765. var
  766. oSection: TIniFileSection;
  767. s: string;
  768. i,J: integer;
  769. begin
  770. Strings.BeginUpdate;
  771. try
  772. Strings.Clear;
  773. oSection := FSectionList.SectionByName(Section,CaseSensitive);
  774. if oSection <> nil then with oSection.KeyList do
  775. for i := 0 to Count-1 do begin
  776. s := Items[i].Value;
  777. If StripQuotes then
  778. begin
  779. J:=Length(s);
  780. // Joost, 2-jan-2007: The check (J>1) is there for the case that
  781. // the value consist of a single double-quote character. (see
  782. // mantis bug 6555)
  783. If (J>1) and ((s[1] in ['"','''']) and (s[J]=s[1])) then
  784. s:=Copy(s,2,J-2);
  785. end;
  786. if Items[i].Ident<>'' then
  787. s:=Items[i].Ident+Separator+s;
  788. Strings.Add(s);
  789. end;
  790. finally
  791. Strings.EndUpdate;
  792. end;
  793. end;
  794. procedure TIniFile.DeleteSection(ASection : TIniFileSection);
  795. begin
  796. FSectionList.Delete(FSectionList.IndexOf(ASection));
  797. ASection.Free;
  798. end;
  799. Procedure TIniFile.MaybeDeleteSection(ASection : TIniFileSection);
  800. begin
  801. If Asection.Empty then
  802. DeleteSection(ASection);
  803. end;
  804. procedure TIniFile.EraseSection(const Section: string);
  805. var
  806. oSection: TIniFileSection;
  807. begin
  808. oSection := FSectionList.SectionByName(Section,CaseSensitive);
  809. if oSection <> nil then begin
  810. { It is needed so UpdateFile doesn't find a defunct section }
  811. { and cause the program to crash }
  812. DeleteSection(OSection);
  813. MaybeUpdateFile;
  814. end;
  815. end;
  816. procedure TIniFile.DeleteKey(const Section, Ident: String);
  817. var
  818. oSection: TIniFileSection;
  819. oKey: TIniFileKey;
  820. begin
  821. oSection := FSectionList.SectionByName(Section,CaseSensitive);
  822. if oSection <> nil then
  823. begin
  824. oKey := oSection.KeyList.KeyByName(Ident,CaseSensitive);
  825. if oKey <> nil then
  826. begin
  827. oSection.KeyList.Delete(oSection.KeyList.IndexOf(oKey));
  828. oKey.Free;
  829. MaybeDeleteSection(oSection);
  830. MaybeUpdateFile;
  831. end;
  832. end;
  833. end;
  834. procedure TIniFile.UpdateFile;
  835. var
  836. slLines: TStringList;
  837. i, j: integer;
  838. begin
  839. slLines := TStringList.Create;
  840. try
  841. for i := 0 to FSectionList.Count-1 do
  842. with FSectionList[i] do begin
  843. if IsComment(Name) then
  844. // comment
  845. slLines.Add(Name)
  846. else
  847. // regular section
  848. slLines.Add(Brackets[0] + Name + Brackets[1]);
  849. for j := 0 to KeyList.Count-1 do
  850. if IsComment(KeyList[j].Ident) then
  851. // comment
  852. slLines.Add(KeyList[j].Ident)
  853. else
  854. // regular key
  855. slLines.Add(KeyList[j].Ident + Separator + KeyList[j].Value);
  856. if (i < FSectionList.Count-1) and not IsComment(Name) then
  857. slLines.Add('');
  858. end;
  859. if FFileName > '' then
  860. slLines.SaveToFile(FFileName)
  861. else if FStream <> nil then
  862. slLines.SaveToStream(FStream);
  863. FillSectionList(slLines);
  864. FDirty := false;
  865. finally
  866. slLines.Free;
  867. end;
  868. end;
  869. procedure TIniFile.MaybeUpdateFile;
  870. begin
  871. If FCacheUpdates then
  872. FDirty:=True
  873. else
  874. UpdateFile;
  875. end;
  876. { TMemIniFile }
  877. constructor TMemIniFile.Create(const AFileName: string; AEscapeLineFeeds : Boolean = False);
  878. begin
  879. Inherited;
  880. FCacheUpdates:=True;
  881. end;
  882. procedure TMemIniFile.Clear;
  883. begin
  884. FSectionList.Clear;
  885. end;
  886. procedure TMemIniFile.GetStrings(List: TStrings);
  887. var
  888. i, j: integer;
  889. oSection: TIniFileSection;
  890. begin
  891. List.BeginUpdate;
  892. try
  893. for i := 0 to FSectionList.Count-1 do begin
  894. oSection := FSectionList[i];
  895. with oSection do begin
  896. if IsComment(Name) then
  897. List.Add(Name)
  898. else
  899. List.Add(Brackets[0] + Name + Brackets[1]);
  900. for j := 0 to KeyList.Count-1 do begin
  901. if IsComment(KeyList[j].Ident) then
  902. List.Add(KeyList[j].Ident)
  903. else
  904. List.Add(KeyList[j].Ident + Separator + KeyList[j].Value);
  905. end;
  906. end;
  907. if i < FSectionList.Count-1 then
  908. List.Add('');
  909. end;
  910. finally
  911. List.EndUpdate;
  912. end;
  913. end;
  914. procedure TMemIniFile.Rename(const AFileName: string; Reload: Boolean);
  915. var
  916. slLines: TStringList;
  917. begin
  918. FFileName := AFileName;
  919. FStream := nil;
  920. if Reload then begin
  921. slLines := TStringList.Create;
  922. try
  923. slLines.LoadFromFile(FFileName);
  924. FillSectionList(slLines);
  925. finally
  926. slLines.Free;
  927. end;
  928. end;
  929. end;
  930. procedure TMemIniFile.SetStrings(List: TStrings);
  931. begin
  932. FillSectionList(List);
  933. end;
  934. end.