wini.pas 13 KB

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