wini.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. {
  2. This file is part of the Free Pascal Integrated Development Environment
  3. Copyright (c) 1998 by B‚rczi G bor
  4. Reading and writing .INI files
  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. unit WINI;
  12. {$ifdef cpullvm}
  13. {$modeswitch nestedprocvars}
  14. {$endif}
  15. {$H-}
  16. interface
  17. uses Objects;
  18. type
  19. PINIEntry = ^TINIEntry;
  20. TINIEntry = object(TObject)
  21. constructor Init(const ALine: string);
  22. constructor Init(const ATag,AValue,AComment: string);
  23. function GetText: string;
  24. function GetTag: string;
  25. function GetComment: string;
  26. function GetValue: string;
  27. procedure SetValue(const S: string);
  28. procedure SetComment(const S: string);
  29. destructor Done; virtual;
  30. private
  31. TagHash : Cardinal;
  32. Tag : PString;
  33. Value : PString;
  34. Comment : PString;
  35. Text : PString;
  36. Modified : boolean;
  37. procedure Split;
  38. end;
  39. PINISection = ^TINISection;
  40. TINISection = object(TObject)
  41. constructor Init(const AName: string);
  42. function GetName: string;
  43. function AddEntry(const S: string): PINIEntry;
  44. function AddEntry(const Tag,Value,Comment: string): PINIEntry;
  45. function SearchEntry(Tag: string): PINIEntry; virtual;
  46. procedure DeleteEntry(Tag: string);
  47. procedure ForEachEntry(EnumProc: TCallbackProcParam); virtual;
  48. destructor Done; virtual;
  49. private
  50. NameHash : Cardinal;
  51. Name : PString;
  52. Entries : PCollection;
  53. end;
  54. PINIFile = ^TINIFile;
  55. TINIFile = object(TObject)
  56. MakeNullEntries: boolean;
  57. constructor Init(const AFileName: string);
  58. function GetFileName: string;
  59. function Read: boolean; virtual;
  60. function Update: boolean; virtual;
  61. function IsModified: boolean; virtual;
  62. function SearchSection(Section: string): PINISection; virtual;
  63. function SearchEntry(const Section, Tag: string): PINIEntry; virtual;
  64. procedure ForEachSection(EnumProc: TCallbackProcParam); virtual;
  65. procedure ForEachEntry(const Section: string; EnumProc: TCallbackProcParam); virtual;
  66. function GetEntry(const Section, Tag, Default: string): string; virtual;
  67. procedure SetEntry(const Section, Tag, Value: string); virtual;
  68. procedure SetEntry(const Section, Tag, Value,Comment: string); virtual;
  69. function GetIntEntry(const Section, Tag: string; Default: longint): longint; virtual;
  70. procedure SetIntEntry(const Section, Tag: string; Value: longint); virtual;
  71. procedure DeleteSection(const Section: string); virtual;
  72. procedure DeleteEntry(const Section, Tag: string);
  73. destructor Done; virtual;
  74. private
  75. { ReadOnly: boolean;}
  76. Sections: PCollection;
  77. FileName: PString;
  78. end;
  79. const MainSectionName : string[40] = 'MainSection';
  80. CommentChar : AnsiChar = ';';
  81. ValidStrDelimiters: set of AnsiChar = ['''','"'];
  82. function EscapeIniText(S : string) : String;
  83. implementation
  84. uses
  85. WUtils;
  86. function EscapeIniText(S : string) : String;
  87. var
  88. delimiter : AnsiChar;
  89. i: integer;
  90. begin
  91. delimiter:=#0;
  92. while delimiter < #255 do
  93. begin
  94. if (delimiter in ValidStrDelimiters) and
  95. (pos(delimiter,S)=0) then
  96. break;
  97. delimiter:=succ(delimiter);
  98. end;
  99. if delimiter=#255 then
  100. begin
  101. { we use " delimiter, but the text also contains double quotes,
  102. which need to be escaped, by doubling it }
  103. delimiter:='"';
  104. for i:=length(s) downto 1 do
  105. if (s[i]=delimiter) then
  106. s:=copy(s,1,i-1)+delimiter+copy(s,i+1,length(s));
  107. end;
  108. EscapeIniText:=delimiter+s+delimiter;
  109. end;
  110. {$IFOPT Q+}
  111. {$Q-}
  112. {$DEFINE REENABLE_Q}
  113. {$ENDIF}
  114. {$IFOPT R+}
  115. {$R-}
  116. {$DEFINE REENABLE_R}
  117. {$ENDIF}
  118. function CalcHash(const s: String): Cardinal;
  119. var
  120. i: integer;
  121. begin
  122. CalcHash := 0;
  123. for i := 1 to Length(s) do
  124. CalcHash := CalcHash shl 9 - CalcHash shl 4 + Ord(S[I]);
  125. end;
  126. {$IFDEF REENABLE_Q}
  127. {$Q+}
  128. {$ENDIF}
  129. {$IFDEF REENABLE_R}
  130. {$R+}
  131. {$ENDIF}
  132. constructor TINIEntry.Init(const ALine: string);
  133. begin
  134. inherited Init;
  135. Text:=NewStr(ALine);
  136. Split;
  137. end;
  138. constructor TINIEntry.Init(const ATag,AValue,AComment: string);
  139. begin
  140. inherited Init;
  141. Tag:=NewStr(ATag);
  142. Value:=NewStr(AValue);
  143. Comment:=NewStr(AComment);
  144. Text:=NewStr(GetText);
  145. end;
  146. function TINIEntry.GetText: string;
  147. var S,CoS: string;
  148. begin
  149. if Text=nil then
  150. begin
  151. CoS:=GetComment;
  152. S:=GetTag+'='+GetValue;
  153. if Trim(S)='=' then S:=CoS else
  154. if CoS<>'' then
  155. begin
  156. { if Value contains CommentChar, we need to add delimiters }
  157. if pos(CommentChar,S)>0 then
  158. S:=EscapeIniText(S);
  159. S:=S+' '+CommentChar+' '+CoS;
  160. end
  161. end
  162. else S:=Text^;
  163. GetText:=S;
  164. end;
  165. function TINIEntry.GetTag: string;
  166. begin
  167. GetTag:=GetStr(Tag);
  168. end;
  169. function TINIEntry.GetComment: string;
  170. begin
  171. GetComment:=GetStr(Comment);
  172. end;
  173. function TINIEntry.GetValue: string;
  174. begin
  175. GetValue:=GetStr(Value);
  176. end;
  177. procedure TINIEntry.SetValue(const S: string);
  178. begin
  179. if GetValue<>S then
  180. begin
  181. if Text<>nil then DisposeStr(Text); Text:=nil;
  182. if Value<>nil then DisposeStr(Value);
  183. Value:=NewStr(S);
  184. Modified:=true;
  185. end;
  186. end;
  187. procedure TINIEntry.SetComment(const S: string);
  188. begin
  189. if (GetComment<>S) then
  190. begin
  191. if Text<>nil then DisposeStr(Text); Text:=nil;
  192. if Comment<>nil then DisposeStr(Comment);
  193. Comment:=NewStr(S);
  194. Modified:=true;
  195. end;
  196. end;
  197. procedure TINIEntry.Split;
  198. var S,ValueS: string;
  199. P,P2,StartP: longint;
  200. { using byte for P2 lead to infinite loops PM }
  201. C: AnsiChar;
  202. InString: boolean;
  203. Delimiter: AnsiChar;
  204. begin
  205. S:=GetText;
  206. Delimiter:=#0;
  207. P:=Pos('=',S); P2:=Pos(CommentChar,S);
  208. if (P2<>0) and (P2<P) then
  209. P:=0;
  210. if P<>0 then
  211. begin
  212. Tag:=NewStr(copy(S,1,P-1));
  213. TagHash:=CalcHash(UpcaseStr(Tag^));
  214. P2:=P+1; InString:=false; ValueS:='';
  215. StartP:=P2;
  216. while (P2<=length(S)) do
  217. begin
  218. C:=S[P2];
  219. if (P2=StartP) and (C in ValidStrDelimiters) then
  220. begin
  221. Delimiter:=C;
  222. InString:=true;
  223. end
  224. else if (C=Delimiter) then
  225. begin
  226. { Delimiter inside escaped Value are simply doubled }
  227. if (P2+1<length(S)) and (S[P2+1]=Delimiter) then
  228. ValueS:=ValueS+Delimiter
  229. else
  230. InString:=not InString;
  231. end
  232. else if (C=CommentChar) and (InString=false) then
  233. Break
  234. else
  235. ValueS:=ValueS+C;
  236. Inc(P2);
  237. end;
  238. Value:=NewStr(Trim(ValueS));
  239. Comment:=NewStr(copy(S,P2+1,High(S)));
  240. { dispose raw text as special treatment is needed for
  241. write }
  242. if assigned(Comment) and assigned(Text) and
  243. (delimiter<>#0) then
  244. begin
  245. DisposeStr(Text);
  246. Text:=nil;
  247. end;
  248. end
  249. else
  250. begin
  251. Tag:=nil;
  252. TagHash:=0;
  253. Value:=nil;
  254. Comment:=NewStr(S);
  255. end;
  256. end;
  257. destructor TINIEntry.Done;
  258. begin
  259. inherited Done;
  260. if Text<>nil then DisposeStr(Text);
  261. if Tag<>nil then DisposeStr(Tag);
  262. if Value<>nil then DisposeStr(Value);
  263. if Comment<>nil then DisposeStr(Comment);
  264. end;
  265. constructor TINISection.Init(const AName: string);
  266. begin
  267. inherited Init;
  268. Name:=NewStr(AName);
  269. NameHash:=CalcHash(UpcaseStr(AName));
  270. New(Entries, Init(50,500));
  271. end;
  272. function TINISection.GetName: string;
  273. begin
  274. GetName:=GetStr(Name);
  275. end;
  276. function TINISection.AddEntry(const S: string): PINIEntry;
  277. var
  278. E: PINIEntry;
  279. Tag : String;
  280. begin
  281. if pos('=',S)>0 then
  282. begin
  283. Tag:=copy(S,1,pos('=',S)-1);
  284. E:=SearchEntry(Tag);
  285. end
  286. else
  287. E:=nil;
  288. if not assigned(E) then
  289. begin
  290. New(E, Init(S));
  291. Entries^.Insert(E);
  292. end
  293. else
  294. begin
  295. if assigned(E^.Text) then
  296. DisposeStr(E^.Text);
  297. E^.Text:=NewStr(S);
  298. E^.Split;
  299. end;
  300. AddEntry:=E;
  301. end;
  302. function TINISection.AddEntry(const Tag,Value,Comment: string): PINIEntry;
  303. var E: PINIEntry;
  304. begin
  305. E:=SearchEntry(Tag);
  306. if not assigned(E) then
  307. begin
  308. New(E, Init(Tag,Value,Comment));
  309. Entries^.Insert(E);
  310. end
  311. else
  312. begin
  313. E^.SetValue(Value);
  314. if Comment<>'' then
  315. E^.SetComment(Comment);
  316. end;
  317. AddEntry:=E;
  318. end;
  319. procedure TINIFile.ForEachSection(EnumProc: TCallbackProcParam);
  320. var I: Sw_integer;
  321. S: PINISection;
  322. begin
  323. for I:=0 to Sections^.Count-1 do
  324. begin
  325. S:=Sections^.At(I);
  326. CallPointerLocal(EnumProc,get_caller_frame(get_frame,get_pc_addr),S);
  327. end;
  328. end;
  329. procedure TINISection.ForEachEntry(EnumProc: TCallbackProcParam);
  330. var I: integer;
  331. E: PINIEntry;
  332. begin
  333. for I:=0 to Entries^.Count-1 do
  334. begin
  335. E:=Entries^.At(I);
  336. CallPointerLocal(EnumProc,get_caller_frame(get_frame,get_pc_addr),E);
  337. end;
  338. end;
  339. function TINISection.SearchEntry(Tag: string): PINIEntry;
  340. var
  341. P : PINIEntry;
  342. I : Sw_integer;
  343. Hash : Cardinal;
  344. begin
  345. SearchEntry:=nil;
  346. Tag:=UpcaseStr(Tag);
  347. Hash:=CalcHash(Tag);
  348. for I:=0 to Entries^.Count-1 do
  349. begin
  350. P:=Entries^.At(I);
  351. if (P^.TagHash=Hash) and (UpcaseStr(P^.GetTag)=Tag) then
  352. begin
  353. SearchEntry:=P;
  354. break;
  355. end;
  356. end;
  357. end;
  358. procedure TINISection.DeleteEntry(Tag: string);
  359. var
  360. P : PIniEntry;
  361. begin
  362. P:=SearchEntry(Tag);
  363. if assigned(P) then
  364. Entries^.Free(P);
  365. end;
  366. destructor TINISection.Done;
  367. begin
  368. inherited Done;
  369. if Name<>nil then DisposeStr(Name);
  370. Dispose(Entries, Done);
  371. end;
  372. constructor TINIFile.Init(const AFileName: string);
  373. begin
  374. inherited Init;
  375. FileName:=NewStr(AFileName);
  376. New(Sections, Init(50,50));
  377. Read;
  378. end;
  379. function TINIFile.GetFileName: string;
  380. begin
  381. GetFileName:=GetStr(FileName);
  382. end;
  383. function TINIFile.Read: boolean;
  384. var f: text;
  385. OK: boolean;
  386. S,TS: string;
  387. P: PINISection;
  388. I: integer;
  389. oFileMode : byte;
  390. begin
  391. New(P, Init(MainSectionName));
  392. Sections^.Insert(P);
  393. oFileMode:=FileMode; {save file open mode}
  394. FileMode:=0; {Reset will open file in read only mode }
  395. Assign(f,FileName^);
  396. {$I-}
  397. Reset(f);
  398. FileMode:=oFileMode; {restore file open mode}
  399. OK:=EatIO=0;
  400. while OK and (Eof(f)=false) do
  401. begin
  402. readln(f,S);
  403. TS:=Trim(S);
  404. OK:=EatIO=0;
  405. if OK then
  406. if TS<>'' then
  407. if copy(TS,1,1)='[' then
  408. begin
  409. I:=Pos(']',TS); if I=0 then I:=length(TS)+1;
  410. New(P, Init(copy(TS,2,I-2)));
  411. Sections^.Insert(P);
  412. end else
  413. begin
  414. P^.AddEntry(S);
  415. end;
  416. end;
  417. Close(f);
  418. EatIO;
  419. {$I+}
  420. Read:=true;
  421. end;
  422. function TINIFile.IsModified: boolean;
  423. function SectionModified(P: PINISection): boolean;
  424. function EntryModified(E: PINIEntry): boolean;
  425. begin
  426. EntryModified:=E^.Modified;
  427. end;
  428. begin
  429. SectionModified:=(P^.Entries^.FirstThat(TCallbackFunBoolParam(@EntryModified))<>nil);
  430. end;
  431. begin
  432. IsModified:=(Sections^.FirstThat(TCallbackFunBoolParam(@SectionModified))<>nil);
  433. end;
  434. function TINIFile.Update: boolean;
  435. var f: text;
  436. OK: boolean;
  437. P: PINISection;
  438. E: PINIEntry;
  439. I,J: integer;
  440. begin
  441. Assign(f,FileName^);
  442. {$I-}
  443. Rewrite(f);
  444. OK:=EatIO=0;
  445. if OK then
  446. for I:=0 to Sections^.Count-1 do
  447. begin
  448. P:=Sections^.At(I);
  449. if I<>0 then writeln(f,'['+P^.GetName+']');
  450. for J:=0 to P^.Entries^.Count-1 do
  451. begin
  452. E:=P^.Entries^.At(J);
  453. writeln(f,E^.GetText);
  454. OK:=EatIO=0;
  455. if OK=false then Break;
  456. end;
  457. if OK and ((I>0) or (P^.Entries^.Count>0)) and (I<Sections^.Count-1) then
  458. writeln(f,'');
  459. OK:=OK and (EatIO=0);
  460. if OK=false then Break;
  461. end;
  462. Close(f);
  463. EatIO;
  464. {$I+}
  465. if OK then
  466. for I:=0 to Sections^.Count-1 do
  467. begin
  468. P:=Sections^.At(I);
  469. for J:=0 to P^.Entries^.Count-1 do
  470. begin
  471. E:=P^.Entries^.At(J);
  472. E^.Modified:=false;
  473. end;
  474. end;
  475. Update:=OK;
  476. end;
  477. function TINIFile.SearchSection(Section: string): PINISection;
  478. var
  479. P : PINISection;
  480. I : Sw_integer;
  481. Hash : Cardinal;
  482. begin
  483. SearchSection:=nil;
  484. Section:=UpcaseStr(Section);
  485. Hash:=CalcHash(Section);
  486. for I:=0 to Sections^.Count-1 do
  487. begin
  488. P:=Sections^.At(I);
  489. if (P^.NameHash=Hash) and (UpcaseStr(P^.GetName)=Section) then
  490. begin
  491. SearchSection:=P;
  492. break;
  493. end;
  494. end;
  495. end;
  496. function TINIFile.SearchEntry(const Section, Tag: string): PINIEntry;
  497. var P: PINISection;
  498. E: PINIEntry;
  499. begin
  500. P:=SearchSection(Section);
  501. if P=nil then E:=nil else
  502. E:=P^.SearchEntry(Tag);
  503. SearchEntry:=E;
  504. end;
  505. procedure TINIFile.ForEachEntry(const Section: string; EnumProc: TCallbackProcParam);
  506. var P: PINISection;
  507. E: PINIEntry;
  508. I: integer;
  509. begin
  510. P:=SearchSection(Section);
  511. if P<>nil then
  512. for I:=0 to P^.Entries^.Count-1 do
  513. begin
  514. E:=P^.Entries^.At(I);
  515. CallPointerMethodLocal(EnumProc,get_frame,@Self,E);
  516. end;
  517. end;
  518. function TINIFile.GetEntry(const Section, Tag, Default: string): string;
  519. var E: PINIEntry;
  520. S: string;
  521. begin
  522. E:=SearchEntry(Section,Tag);
  523. if E=nil then S:=Default else
  524. S:=E^.GetValue;
  525. GetEntry:=S;
  526. end;
  527. procedure TINIFile.SetEntry(const Section, Tag, Value,Comment: string);
  528. var E: PINIEntry;
  529. P: PINISection;
  530. begin
  531. E:=SearchEntry(Section,Tag);
  532. if E=nil then
  533. if (MakeNullEntries=true) or (Value<>'') then
  534. begin
  535. P:=SearchSection(Section);
  536. if P=nil then
  537. begin
  538. New(P, Init(Section));
  539. Sections^.Insert(P);
  540. end;
  541. E:=P^.AddEntry(Tag,Value,Comment);
  542. E^.Modified:=true;
  543. end;
  544. if E<>nil then
  545. E^.SetValue(Value);
  546. end;
  547. procedure TINIFile.SetEntry(const Section, Tag, Value: string);
  548. begin
  549. SetEntry(Section,Tag,Value,'');
  550. end;
  551. function TINIFile.GetIntEntry(const Section, Tag: string; Default: longint): longint;
  552. var L: longint;
  553. begin
  554. L:=StrToInt(GetEntry(Section,Tag,IntToStr(Default)));
  555. if LastStrToIntResult<>0 then L:=Default;
  556. GetIntEntry:=L;
  557. end;
  558. procedure TINIFile.SetIntEntry(const Section, Tag: string; Value: longint);
  559. begin
  560. SetEntry(Section,Tag,IntToStr(Value));
  561. end;
  562. procedure TINIFile.DeleteSection(const Section: string);
  563. var P: PINISection;
  564. begin
  565. P:=SearchSection(Section);
  566. if P<>nil then
  567. Sections^.Free(P);
  568. end;
  569. procedure TINIFile.DeleteEntry(const Section, Tag: string);
  570. var P: PINISection;
  571. begin
  572. P:=SearchSection(Section);
  573. if P<>nil then
  574. P^.DeleteEntry(Tag);
  575. end;
  576. destructor TINIFile.Done;
  577. begin
  578. if IsModified then
  579. Update;
  580. inherited Done;
  581. if FileName<>nil then
  582. DisposeStr(FileName);
  583. Dispose(Sections, Done);
  584. end;
  585. END.