wini.pas 13 KB

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