You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

646 lines
17 KiB

  1. unit uutlMCF;
  2. { Package: Utils
  3. Prefix: utl - UTiLs
  4. Beschreibung: diese Unit enthält Klassen zum Lesen und Schreiben eines MuoConfgiFiles (kurz MCF)
  5. Lesen/Schreiben in/von Stream über TutlMCFFile
  6. LineEndMode zur Kompatibilität mit MCF-alt und KCF:
  7. leNone - Kein Semikolon erlaubt (KCF)
  8. leAcceptNoWrite - Semikolon wird beim Lesen ignoriert, beim Schreiben weggelassen
  9. leAlways - Beim Lesen erforderlich, immer geschrieben (MCF-alt)
  10. Jeder SectionName und jeder ValueName ist Unique, es kann aber ein Value und eine
  11. Section mit dem gleichen Namen existieren
  12. Zugriff auf Subsections über .Section(), mehrere Stufen auf einmal mit . getrennt:
  13. mcf.Section('foo.bar.baz') === mcf.Section('foo').Section('bar').Section('baz')
  14. Zugriff erstellt automatisch eine Section, falls sie nicht existiert. Prüfung mit
  15. SectionExists (nur direkt, keine Pfade!).
  16. Zugriff auf Werte von der Section aus:
  17. Get/Set[Int,Float,String,Bool](Key, Default)
  18. ValueExists()
  19. UnsetValue()
  20. Strings sind Widestrings, Un/Escaping passiert beim Dateizugriff automatisch
  21. Enumeration: ValueCount/ValueNameAt, SectionCount/SectionNameAt }
  22. interface
  23. uses
  24. SysUtils, Classes, uutlStreamHelper;
  25. type
  26. EConfigException = class(Exception)
  27. end;
  28. TutlMCFSection = class;
  29. TutlMCFFile = class;
  30. TutlMCFLineEndMarkerMode = (leNone, leAcceptNoWrite, leAlways);
  31. { TutlMCFSection }
  32. TutlMCFSection = class
  33. private type
  34. TSectionEnumerator = class(TObject)
  35. private
  36. fList: TStringList;
  37. fPosition: Integer;
  38. function GetCurrent: TutlMCFSection;
  39. public
  40. property Current: TutlMCFSection read GetCurrent;
  41. function MoveNext: Boolean;
  42. constructor Create(const aList: TStringList);
  43. end;
  44. private
  45. FSections,
  46. FValues: TStringList;
  47. function GetSection(aPath: String): TutlMCFSection;
  48. function GetSectionCount: integer;
  49. function GetSectionName(Index: integer): string;
  50. function GetSectionByIndex(aIndex: Integer): TutlMCFSection;
  51. function GetValueCount: integer;
  52. function GetValueName(Index: integer): string;
  53. protected
  54. procedure ClearSections;
  55. procedure ClearValues;
  56. procedure SaveData(Stream: TStream; Indent: string; LineEnds: TutlMCFLineEndMarkerMode);
  57. procedure LoadData(Data: TStream; LineEnds: TutlMCFLineEndMarkerMode; Depth: Integer);
  58. procedure AddValueChecked(Name: String; Val: TObject);
  59. procedure SplitPath(const Path: String; out First, Rest: String);
  60. public
  61. constructor Create;
  62. destructor Destroy; override;
  63. function GetEnumerator: TSectionEnumerator;
  64. property ValueCount: integer read GetValueCount;
  65. property ValueNameAt[Index: integer]: string read GetValueName;
  66. property SectionCount: integer read GetSectionCount;
  67. property SectionNameAt[Index: integer]: string read GetSectionName;
  68. property Sections[aPath: String]: TutlMCFSection read GetSection; default;
  69. property SectionByIndex[aIndex: Integer]: TutlMCFSection read GetSectionByIndex;
  70. function SectionExists(Path: string): boolean;
  71. function Section(Path: string): TutlMCFSection;
  72. procedure DeleteSection(Name: string);
  73. function ValueExists(Name: string): boolean;
  74. function GetInt(Name: string; Default: Int64 = 0): Int64; overload;
  75. function GetFloat(Name: string; Default: Double = 0): Double; overload;
  76. function GetString(Name: string; Default: AnsiString = ''): AnsiString; overload;
  77. function GetStringW(Name: string; Default: UnicodeString = ''): UnicodeString; overload;
  78. function GetBool(Name: string; Default: Boolean = false): Boolean; overload;
  79. procedure SetInt(Name: string; Value: Int64); overload;
  80. procedure SetFloat(Name: string; Value: Double); overload;
  81. procedure SetString(Name: string; Value: WideString); overload;
  82. procedure SetString(Name: string; Value: AnsiString); overload;
  83. procedure SetBool(Name: string; Value: Boolean); overload;
  84. procedure UnsetValue(Name: string);
  85. end;
  86. { TutlMCFFile }
  87. TutlMCFFile = class(TutlMCFSection)
  88. private
  89. fLineEndMode: TutlMCFLineEndMarkerMode;
  90. public
  91. constructor Create(Data: TStream; LineEndMode: TutlMCFLineEndMarkerMode = leAcceptNoWrite);
  92. procedure LoadFromStream(Stream: TStream);
  93. procedure SaveToStream(Stream: TStream);
  94. end;
  95. implementation
  96. uses Variants, StrUtils;
  97. const
  98. sComment = '#';
  99. sSectionEnd = 'end';
  100. sSectionMarker = ':';
  101. sSectionPathDelim = '.';
  102. sLineEndMarker = ';';
  103. sValueDelim = '=';
  104. sValueQuote = '''';
  105. sValueDecimal = '.';
  106. sIndentOnSave = ' ';
  107. sNameValidChars = [' ' .. #$7F] - [sValueDelim];
  108. sWhitespaceChars = [#0 .. ' '];
  109. type
  110. StoredValue = Variant;
  111. { TutlMCFValue }
  112. TutlMCFValue = class
  113. private
  114. Format: TFormatSettings;
  115. FValue: StoredValue;
  116. procedure SetValue(const Value: StoredValue);
  117. protected
  118. function CheckSpecialChars(Data: WideString): boolean;
  119. procedure LoadData(Data: string);
  120. function SaveData: string;
  121. class function Escape(Value: WideString): AnsiString;
  122. class function Unescape(Value: AnsiString): WideString;
  123. public
  124. constructor Create(Val: StoredValue);
  125. property Value: StoredValue read FValue write SetValue;
  126. end;
  127. { TkcfValue }
  128. constructor TutlMCFValue.Create(Val: StoredValue);
  129. begin
  130. inherited Create;
  131. SetValue(Val);
  132. Format.DecimalSeparator:= sValueDecimal;
  133. end;
  134. procedure TutlMCFValue.SetValue(const Value: StoredValue);
  135. begin
  136. FValue:= Value;
  137. end;
  138. function TutlMCFValue.CheckSpecialChars(Data: WideString): boolean;
  139. var
  140. i: Integer;
  141. begin
  142. result := false;
  143. for i:= 1 to Length(Data) do
  144. if Data[i] in [sSectionMarker, sValueQuote, sValueDelim, sLineEndMarker, ' '] then
  145. exit;
  146. result := true;
  147. end;
  148. procedure TutlMCFValue.LoadData(Data: string);
  149. var
  150. b: boolean;
  151. i: int64;
  152. d: double;
  153. p: PChar;
  154. begin
  155. if TryStrToInt64(Data, i) then
  156. Value:= i
  157. else if TryStrToFloat(Data, d, Format) then
  158. Value:= d
  159. else if TryStrToBool(Data, b) then
  160. Value:= b
  161. else begin
  162. p:= PChar(Data);
  163. if p^ = sValueQuote then
  164. Data := AnsiExtractQuotedStr(p, sValueQuote);
  165. Value:= Unescape(Trim(Data));
  166. end;
  167. end;
  168. function TutlMCFValue.SaveData: string;
  169. begin
  170. if VarIsType(FValue, varBoolean) then
  171. Result:= BoolToStr(FValue, false)
  172. else if VarIsType(FValue, varInt64) then
  173. Result:= IntToStr(FValue)
  174. else if VarIsType(FValue, varDouble) then
  175. Result:= FloatToStr(Double(FValue), Format)
  176. else begin
  177. Result:= Escape(FValue);
  178. if not CheckSpecialChars(WideString(Result)) then
  179. Result:= AnsiQuotedStr(Result, sValueQuote);
  180. end;
  181. end;
  182. class function TutlMCFValue.Escape(Value: WideString): AnsiString;
  183. var
  184. i: integer;
  185. wc: WideChar;
  186. begin
  187. Result:= '';
  188. for i:= 1 to length(Value) do begin
  189. wc:= Value[i];
  190. case Ord(wc) of
  191. Ord('\'),
  192. $007F..$FFFF: Result:= Result + '\'+IntToHex(ord(wc),4);
  193. else
  194. Result:= Result + AnsiChar(wc);
  195. end;
  196. end;
  197. end;
  198. class function TutlMCFValue.Unescape(Value: AnsiString): WideString;
  199. var
  200. i: integer;
  201. c: Char;
  202. begin
  203. Result:= '';
  204. i:= 1;
  205. while i <= length(value) do begin
  206. c:= Value[i];
  207. if c='\' then begin
  208. Result:= Result + WideChar(StrToInt('$'+Copy(Value,i+1,4)));
  209. inc(i, 4);
  210. end else
  211. Result:= Result + WideChar(c);
  212. inc(i);
  213. end;
  214. end;
  215. { TutlMCFSection.TSectionEnumerator }
  216. function TutlMCFSection.TSectionEnumerator.GetCurrent: TutlMCFSection;
  217. begin
  218. result := TutlMCFSection(fList.Objects[fPosition]);
  219. end;
  220. function TutlMCFSection.TSectionEnumerator.MoveNext: Boolean;
  221. begin
  222. inc(fPosition);
  223. result := (fPosition < fList.Count);
  224. end;
  225. constructor TutlMCFSection.TSectionEnumerator.Create(const aList: TStringList);
  226. begin
  227. inherited Create;
  228. fList := aList;
  229. fPosition := -1;
  230. end;
  231. { TkcfCompound }
  232. constructor TutlMCFSection.Create;
  233. begin
  234. inherited;
  235. FSections:= TStringList.Create;
  236. FSections.CaseSensitive:= false;
  237. FSections.Sorted:= true;
  238. FSections.Duplicates:= dupError;
  239. FValues:= TStringList.Create;
  240. FValues.CaseSensitive:= false;
  241. FValues.Sorted:= true;
  242. FValues.Duplicates:= dupError;
  243. end;
  244. destructor TutlMCFSection.Destroy;
  245. begin
  246. ClearSections;
  247. ClearValues;
  248. FreeAndNil(FSections);
  249. FreeAndNil(FValues);
  250. inherited;
  251. end;
  252. function TutlMCFSection.GetEnumerator: TSectionEnumerator;
  253. begin
  254. result := TSectionEnumerator.Create(FSections);
  255. end;
  256. function TutlMCFSection.GetSectionCount: integer;
  257. begin
  258. Result:= FSections.Count;
  259. end;
  260. function TutlMCFSection.GetSection(aPath: String): TutlMCFSection;
  261. begin
  262. result := Section(aPath);
  263. end;
  264. function TutlMCFSection.GetSectionByIndex(aIndex: Integer): TutlMCFSection;
  265. begin
  266. result := (FSections.Objects[aIndex] as TutlMCFSection);
  267. end;
  268. function TutlMCFSection.GetSectionName(Index: integer): string;
  269. begin
  270. Result:= FSections[Index];
  271. end;
  272. function TutlMCFSection.GetValueCount: integer;
  273. begin
  274. Result:= FValues.Count;
  275. end;
  276. function TutlMCFSection.GetValueName(Index: integer): string;
  277. begin
  278. Result:= FValues[Index];
  279. end;
  280. procedure TutlMCFSection.ClearSections;
  281. var
  282. i: integer;
  283. begin
  284. for i:= FSections.Count - 1 downto 0 do
  285. FSections.Objects[i].Free;
  286. FSections.Clear;
  287. end;
  288. procedure TutlMCFSection.ClearValues;
  289. var
  290. i: integer;
  291. begin
  292. for i:= FValues.Count - 1 downto 0 do
  293. FValues.Objects[i].Free;
  294. FValues.Clear;
  295. end;
  296. procedure TutlMCFSection.SplitPath(const Path: String; out First, Rest: String);
  297. begin
  298. First:= Copy(Path, 1, Pos(sSectionPathDelim, Path)-1);
  299. if First='' then begin
  300. First:= Path;
  301. Rest:= '';
  302. end else begin
  303. Rest:= Copy(Path, Length(First)+2, MaxInt);
  304. end;
  305. end;
  306. function TutlMCFSection.SectionExists(Path: string): boolean;
  307. var
  308. f,r: String;
  309. i: integer;
  310. begin
  311. SplitPath(Path, f, r);
  312. i:= FSections.IndexOf(f);
  313. Result:= (i >= 0) and ((r='') or (TutlMCFSection(FSections.Objects[i]).SectionExists(r)));
  314. end;
  315. function TutlMCFSection.Section(Path: string): TutlMCFSection;
  316. var
  317. f,r: String;
  318. i: integer;
  319. begin
  320. SplitPath(Path, f, r);
  321. i:= FSections.IndexOf(f);
  322. if r <> '' then begin
  323. if (i >= 0) then
  324. Result:= TutlMCFSection(FSections.Objects[i]).Section(r)
  325. else begin
  326. result := TutlMCFSection.Create;
  327. fSections.AddObject(f, result);
  328. result := result.Section(r);
  329. end;
  330. end else begin
  331. if i >= 0 then
  332. Result:= TutlMCFSection(FSections.Objects[i])
  333. else begin
  334. Result:= TutlMCFSection.Create;
  335. FSections.AddObject(f, Result);
  336. end;
  337. end;
  338. end;
  339. procedure TutlMCFSection.DeleteSection(Name: string);
  340. var
  341. i: integer;
  342. begin
  343. i:= FSections.IndexOf(Name);
  344. if i >= 0 then begin
  345. FSections.Objects[i].Free;
  346. FSections.Delete(i);
  347. end;
  348. end;
  349. function TutlMCFSection.ValueExists(Name: string): boolean;
  350. begin
  351. Result:= FValues.IndexOf(Name) >= 0;
  352. end;
  353. function TutlMCFSection.GetInt(Name: string; Default: Int64): Int64;
  354. var
  355. i: integer;
  356. begin
  357. i:= FValues.IndexOf(Name);
  358. if i < 0 then
  359. Result:= Default
  360. else
  361. Result:= TutlMCFValue(FValues.Objects[i]).Value;
  362. end;
  363. function TutlMCFSection.GetFloat(Name: string; Default: Double): Double;
  364. var
  365. i: integer;
  366. begin
  367. i:= FValues.IndexOf(Name);
  368. if i < 0 then
  369. Result:= Default
  370. else
  371. Result:= TutlMCFValue(FValues.Objects[i]).Value;
  372. end;
  373. function TutlMCFSection.GetStringW(Name: string; Default: UnicodeString): UnicodeString;
  374. var
  375. i: integer;
  376. begin
  377. i:= FValues.IndexOf(Name);
  378. if i < 0 then
  379. Result:= Default
  380. else
  381. Result:= TutlMCFValue(FValues.Objects[i]).Value;
  382. end;
  383. function TutlMCFSection.GetString(Name: string; Default: AnsiString): AnsiString;
  384. begin
  385. Result := AnsiString(GetStringW(Name, UnicodeString(Default)));
  386. end;
  387. function TutlMCFSection.GetBool(Name: string; Default: Boolean): Boolean;
  388. var
  389. i: integer;
  390. begin
  391. i:= FValues.IndexOf(Name);
  392. if i < 0 then
  393. Result:= Default
  394. else
  395. Result:= TutlMCFValue(FValues.Objects[i]).Value;
  396. end;
  397. procedure TutlMCFSection.AddValueChecked(Name: String; Val: TObject);
  398. var
  399. i: integer;
  400. begin
  401. if (Length(Name) < 1) or
  402. (Name[1] in sWhitespaceChars) or
  403. (Name[Length(Name)] in sWhitespaceChars) then
  404. raise EConfigException.CreateFmt('Invalid Value Name: "%s"',[Name]);
  405. for i:= 1 to Length(Name) do
  406. if not (Name[i] in sNameValidChars) then
  407. raise EConfigException.CreateFmt('Invalid Value Name: "%s"',[Name]);
  408. FValues.AddObject(Name, Val);
  409. end;
  410. procedure TutlMCFSection.SetInt(Name: string; Value: Int64);
  411. var
  412. i: integer;
  413. begin
  414. i:= FValues.IndexOf(Name);
  415. if i < 0 then
  416. AddValueChecked(Name, TutlMCFValue.Create(Value))
  417. else
  418. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  419. end;
  420. procedure TutlMCFSection.SetFloat(Name: string; Value: Double);
  421. var
  422. i: integer;
  423. begin
  424. i:= FValues.IndexOf(Name);
  425. if i < 0 then
  426. AddValueChecked(Name, TutlMCFValue.Create(Value))
  427. else
  428. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  429. end;
  430. procedure TutlMCFSection.SetString(Name: string; Value: WideString);
  431. var
  432. i: integer;
  433. begin
  434. i:= FValues.IndexOf(Name);
  435. if i < 0 then
  436. AddValueChecked(Name, TutlMCFValue.Create(Value))
  437. else
  438. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  439. end;
  440. procedure TutlMCFSection.SetString(Name: string; Value: AnsiString);
  441. begin
  442. SetString(Name, WideString(Value));
  443. end;
  444. procedure TutlMCFSection.SetBool(Name: string; Value: Boolean);
  445. var
  446. i: integer;
  447. begin
  448. i:= FValues.IndexOf(Name);
  449. if i < 0 then
  450. AddValueChecked(Name, TutlMCFValue.Create(Value))
  451. else
  452. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  453. end;
  454. procedure TutlMCFSection.UnsetValue(Name: string);
  455. var
  456. i: integer;
  457. begin
  458. i:= FValues.IndexOf(Name);
  459. if i >= 0 then begin
  460. FValues.Objects[i].Free;
  461. FValues.Delete(i);
  462. end;
  463. end;
  464. procedure TutlMCFSection.LoadData(Data: TStream; LineEnds: TutlMCFLineEndMarkerMode; Depth: Integer);
  465. var
  466. reader: TutlStreamReader;
  467. l, sn, vn, vs: string;
  468. se: TutlMCFSection;
  469. va: TutlMCFValue;
  470. begin
  471. reader:= TutlStreamReader.Create(Data);
  472. try
  473. repeat
  474. l:= reader.ReadLine;
  475. l:= trim(l);
  476. if (l = '') or AnsiStartsStr(sComment, l) then
  477. continue;
  478. if ((LineEnds in [leNone, leAcceptNoWrite]) and (l = sSectionEnd)) or
  479. ((LineEnds in [leAcceptNoWrite, leAlways]) and (l = sSectionEnd+sLineEndMarker)) then begin
  480. if Depth > 0 then
  481. exit
  482. else
  483. raise EConfigException.Create('Encountered Section End where none was expected.');
  484. end;
  485. if AnsiEndsStr(sSectionMarker, l) then begin
  486. sn:= trim(Copy(l, 1, length(l) - length(sSectionMarker)));
  487. if SectionExists(sn) then
  488. raise EConfigException.Create('Redeclared Section: '+sn);
  489. if Pos(sSectionPathDelim,sn) > 0 then
  490. raise EConfigException.Create('Invalid Section Name: '+sn);
  491. se:= TutlMCFSection.Create;
  492. try
  493. se.LoadData(Data, LineEnds, Depth + 1);
  494. FSections.AddObject(sn, se);
  495. except
  496. FreeAndNil(se);
  497. end;
  498. end else if (Pos(sValueDelim, l) > 0) then begin
  499. if (LineEnds in [leAcceptNoWrite, leAlways]) and AnsiEndsStr(sLineEndMarker, l) then
  500. Delete(l, length(l), 1);
  501. vn:= trim(Copy(l, 1, Pos(sValueDelim, l) - 1));
  502. vs:= trim(Copy(l, Pos(sValueDelim, l) + 1, Maxint));
  503. if ValueExists(vn) then
  504. raise EConfigException.Create('Redeclared Value: '+vn);
  505. va:= TutlMCFValue.Create('');
  506. try
  507. va.LoadData(vs);
  508. AddValueChecked(vn, va);
  509. except
  510. FreeAndNil(va);
  511. end;
  512. end else
  513. raise EConfigException.Create('Cannot Parse Line: '+l);
  514. until reader.IsEOF;
  515. if Depth > 0 then
  516. raise EConfigException.Create('Expected Section End, but reached stream end.');
  517. Depth:= Depth - 1;
  518. finally
  519. FreeAndNil(reader);
  520. end;
  521. end;
  522. procedure TutlMCFSection.SaveData(Stream: TStream; Indent: string;
  523. LineEnds: TutlMCFLineEndMarkerMode);
  524. var
  525. writer: TutlStreamWriter;
  526. i: integer;
  527. ele, s: AnsiString;
  528. begin
  529. if LineEnds in [leAlways] then
  530. ele:= sLineEndMarker
  531. else
  532. ele:= '';
  533. writer:= TutlStreamWriter.Create(Stream);
  534. try
  535. for i:= 0 to FValues.Count - 1 do begin
  536. s:= Indent + FValues[i] + ' ' + sValueDelim + ' ' + TutlMCFValue(FValues.Objects[i]).SaveData + ele;
  537. writer.WriteLine(s);
  538. end;
  539. for i:= 0 to FSections.Count - 1 do begin
  540. s:= Indent + FSections[i] + sSectionMarker;
  541. writer.WriteLine(s);
  542. TutlMCFSection(FSections.Objects[i]).SaveData(Stream, Indent + sIndentOnSave, LineEnds);
  543. s:= Indent + sSectionEnd + ele;
  544. writer.WriteLine(s);
  545. end;
  546. finally
  547. FreeAndNil(writer);
  548. end;
  549. end;
  550. { TutlMCFFile }
  551. constructor TutlMCFFile.Create(Data: TStream; LineEndMode: TutlMCFLineEndMarkerMode);
  552. begin
  553. inherited Create;
  554. fLineEndMode:= LineEndMode;
  555. if Assigned(Data) then
  556. LoadFromStream(Data);
  557. end;
  558. procedure TutlMCFFile.LoadFromStream(Stream: TStream);
  559. begin
  560. ClearSections;
  561. ClearValues;
  562. LoadData(Stream, fLineEndMode, 0);
  563. end;
  564. procedure TutlMCFFile.SaveToStream(Stream: TStream);
  565. begin
  566. SaveData(Stream, '', fLineEndMode);
  567. end;
  568. end.