wini.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  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. begin
  390. New(P, Init(MainSectionName));
  391. Sections^.Insert(P);
  392. Assign(f,FileName^);
  393. {$I-}
  394. Reset(f);
  395. OK:=EatIO=0;
  396. while OK and (Eof(f)=false) do
  397. begin
  398. readln(f,S);
  399. TS:=Trim(S);
  400. OK:=EatIO=0;
  401. if OK then
  402. if TS<>'' then
  403. if copy(TS,1,1)='[' then
  404. begin
  405. I:=Pos(']',TS); if I=0 then I:=length(TS)+1;
  406. New(P, Init(copy(TS,2,I-2)));
  407. Sections^.Insert(P);
  408. end else
  409. begin
  410. P^.AddEntry(S);
  411. end;
  412. end;
  413. Close(f);
  414. EatIO;
  415. {$I+}
  416. Read:=true;
  417. end;
  418. function TINIFile.IsModified: boolean;
  419. function SectionModified(P: PINISection): boolean;
  420. function EntryModified(E: PINIEntry): boolean;
  421. begin
  422. EntryModified:=E^.Modified;
  423. end;
  424. begin
  425. SectionModified:=(P^.Entries^.FirstThat(TCallbackFunBoolParam(@EntryModified))<>nil);
  426. end;
  427. begin
  428. IsModified:=(Sections^.FirstThat(TCallbackFunBoolParam(@SectionModified))<>nil);
  429. end;
  430. function TINIFile.Update: boolean;
  431. var f: text;
  432. OK: boolean;
  433. P: PINISection;
  434. E: PINIEntry;
  435. I,J: integer;
  436. begin
  437. Assign(f,FileName^);
  438. {$I-}
  439. Rewrite(f);
  440. OK:=EatIO=0;
  441. if OK then
  442. for I:=0 to Sections^.Count-1 do
  443. begin
  444. P:=Sections^.At(I);
  445. if I<>0 then writeln(f,'['+P^.GetName+']');
  446. for J:=0 to P^.Entries^.Count-1 do
  447. begin
  448. E:=P^.Entries^.At(J);
  449. writeln(f,E^.GetText);
  450. OK:=EatIO=0;
  451. if OK=false then Break;
  452. end;
  453. if OK and ((I>0) or (P^.Entries^.Count>0)) and (I<Sections^.Count-1) then
  454. writeln(f,'');
  455. OK:=OK and (EatIO=0);
  456. if OK=false then Break;
  457. end;
  458. Close(f);
  459. EatIO;
  460. {$I+}
  461. if OK then
  462. for I:=0 to Sections^.Count-1 do
  463. begin
  464. P:=Sections^.At(I);
  465. for J:=0 to P^.Entries^.Count-1 do
  466. begin
  467. E:=P^.Entries^.At(J);
  468. E^.Modified:=false;
  469. end;
  470. end;
  471. Update:=OK;
  472. end;
  473. function TINIFile.SearchSection(Section: string): PINISection;
  474. var
  475. P : PINISection;
  476. I : Sw_integer;
  477. Hash : Cardinal;
  478. begin
  479. SearchSection:=nil;
  480. Section:=UpcaseStr(Section);
  481. Hash:=CalcHash(Section);
  482. for I:=0 to Sections^.Count-1 do
  483. begin
  484. P:=Sections^.At(I);
  485. if (P^.NameHash=Hash) and (UpcaseStr(P^.GetName)=Section) then
  486. begin
  487. SearchSection:=P;
  488. break;
  489. end;
  490. end;
  491. end;
  492. function TINIFile.SearchEntry(const Section, Tag: string): PINIEntry;
  493. var P: PINISection;
  494. E: PINIEntry;
  495. begin
  496. P:=SearchSection(Section);
  497. if P=nil then E:=nil else
  498. E:=P^.SearchEntry(Tag);
  499. SearchEntry:=E;
  500. end;
  501. procedure TINIFile.ForEachEntry(const Section: string; EnumProc: TCallbackProcParam);
  502. var P: PINISection;
  503. E: PINIEntry;
  504. I: integer;
  505. begin
  506. P:=SearchSection(Section);
  507. if P<>nil then
  508. for I:=0 to P^.Entries^.Count-1 do
  509. begin
  510. E:=P^.Entries^.At(I);
  511. CallPointerMethodLocal(EnumProc,get_frame,@Self,E);
  512. end;
  513. end;
  514. function TINIFile.GetEntry(const Section, Tag, Default: string): string;
  515. var E: PINIEntry;
  516. S: string;
  517. begin
  518. E:=SearchEntry(Section,Tag);
  519. if E=nil then S:=Default else
  520. S:=E^.GetValue;
  521. GetEntry:=S;
  522. end;
  523. procedure TINIFile.SetEntry(const Section, Tag, Value,Comment: string);
  524. var E: PINIEntry;
  525. P: PINISection;
  526. begin
  527. E:=SearchEntry(Section,Tag);
  528. if E=nil then
  529. if (MakeNullEntries=true) or (Value<>'') then
  530. begin
  531. P:=SearchSection(Section);
  532. if P=nil then
  533. begin
  534. New(P, Init(Section));
  535. Sections^.Insert(P);
  536. end;
  537. E:=P^.AddEntry(Tag,Value,Comment);
  538. E^.Modified:=true;
  539. end;
  540. if E<>nil then
  541. E^.SetValue(Value);
  542. end;
  543. procedure TINIFile.SetEntry(const Section, Tag, Value: string);
  544. begin
  545. SetEntry(Section,Tag,Value,'');
  546. end;
  547. function TINIFile.GetIntEntry(const Section, Tag: string; Default: longint): longint;
  548. var L: longint;
  549. begin
  550. L:=StrToInt(GetEntry(Section,Tag,IntToStr(Default)));
  551. if LastStrToIntResult<>0 then L:=Default;
  552. GetIntEntry:=L;
  553. end;
  554. procedure TINIFile.SetIntEntry(const Section, Tag: string; Value: longint);
  555. begin
  556. SetEntry(Section,Tag,IntToStr(Value));
  557. end;
  558. procedure TINIFile.DeleteSection(const Section: string);
  559. var P: PINISection;
  560. begin
  561. P:=SearchSection(Section);
  562. if P<>nil then
  563. Sections^.Free(P);
  564. end;
  565. procedure TINIFile.DeleteEntry(const Section, Tag: string);
  566. var P: PINISection;
  567. begin
  568. P:=SearchSection(Section);
  569. if P<>nil then
  570. P^.DeleteEntry(Tag);
  571. end;
  572. destructor TINIFile.Done;
  573. begin
  574. if IsModified then
  575. Update;
  576. inherited Done;
  577. if FileName<>nil then
  578. DisposeStr(FileName);
  579. Dispose(Sections, Done);
  580. end;
  581. END.