2
0

wini.pas 15 KB

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