wini.pas 14 KB

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