選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

668 行
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. procedure Clear;
  65. procedure Assign(Source: TutlMCFSection);
  66. property ValueCount: integer read GetValueCount;
  67. property ValueNameAt[Index: integer]: string read GetValueName;
  68. property SectionCount: integer read GetSectionCount;
  69. property SectionNameAt[Index: integer]: string read GetSectionName;
  70. property Sections[aPath: String]: TutlMCFSection read GetSection; default;
  71. property SectionByIndex[aIndex: Integer]: TutlMCFSection read GetSectionByIndex;
  72. function SectionExists(Path: string): boolean;
  73. function Section(Path: string): TutlMCFSection;
  74. procedure DeleteSection(Name: string);
  75. function ValueExists(Name: string): boolean;
  76. function GetInt(Name: string; Default: Int64 = 0): Int64; overload;
  77. function GetFloat(Name: string; Default: Double = 0): Double; overload;
  78. function GetString(Name: string; Default: AnsiString = ''): AnsiString; overload;
  79. function GetStringW(Name: string; Default: UnicodeString = ''): UnicodeString; overload;
  80. function GetBool(Name: string; Default: Boolean = false): Boolean; overload;
  81. procedure SetInt(Name: string; Value: Int64); overload;
  82. procedure SetFloat(Name: string; Value: Double); overload;
  83. procedure SetString(Name: string; Value: WideString); overload;
  84. procedure SetString(Name: string; Value: AnsiString); overload;
  85. procedure SetBool(Name: string; Value: Boolean); overload;
  86. procedure UnsetValue(Name: string);
  87. end;
  88. { TutlMCFFile }
  89. TutlMCFFile = class(TutlMCFSection)
  90. private
  91. fLineEndMode: TutlMCFLineEndMarkerMode;
  92. public
  93. constructor Create(Data: TStream; LineEndMode: TutlMCFLineEndMarkerMode = leAcceptNoWrite);
  94. procedure LoadFromStream(Stream: TStream);
  95. procedure SaveToStream(Stream: TStream);
  96. end;
  97. implementation
  98. uses Variants, StrUtils;
  99. const
  100. sComment = '#';
  101. sSectionEnd = 'end';
  102. sSectionMarker = ':';
  103. sSectionPathDelim = '.';
  104. sLineEndMarker = ';';
  105. sValueDelim = '=';
  106. sValueQuote = '''';
  107. sValueDecimal = '.';
  108. sIndentOnSave = ' ';
  109. sNameValidChars = [' ' .. #$7F] - [sValueDelim];
  110. sWhitespaceChars = [#0 .. ' '];
  111. type
  112. StoredValue = Variant;
  113. { TutlMCFValue }
  114. TutlMCFValue = class
  115. private
  116. Format: TFormatSettings;
  117. FValue: StoredValue;
  118. procedure SetValue(const Value: StoredValue);
  119. protected
  120. function CheckSpecialChars(Data: WideString): boolean;
  121. procedure LoadData(Data: string);
  122. function SaveData: string;
  123. class function Escape(Value: WideString): AnsiString;
  124. class function Unescape(Value: AnsiString): WideString;
  125. public
  126. constructor Create(Val: StoredValue);
  127. property Value: StoredValue read FValue write SetValue;
  128. end;
  129. { TkcfValue }
  130. constructor TutlMCFValue.Create(Val: StoredValue);
  131. begin
  132. inherited Create;
  133. SetValue(Val);
  134. Format.DecimalSeparator:= sValueDecimal;
  135. end;
  136. procedure TutlMCFValue.SetValue(const Value: StoredValue);
  137. begin
  138. FValue:= Value;
  139. end;
  140. function TutlMCFValue.CheckSpecialChars(Data: WideString): boolean;
  141. var
  142. i: Integer;
  143. begin
  144. result := false;
  145. for i:= 1 to Length(Data) do
  146. if Data[i] in [sSectionMarker, sValueQuote, sValueDelim, sLineEndMarker, ' '] then
  147. exit;
  148. result := true;
  149. end;
  150. procedure TutlMCFValue.LoadData(Data: string);
  151. var
  152. b: boolean;
  153. i: int64;
  154. d: double;
  155. p: PChar;
  156. begin
  157. if TryStrToInt64(Data, i) then
  158. Value:= i
  159. else if TryStrToFloat(Data, d, Format) then
  160. Value:= d
  161. else if TryStrToBool(Data, b) then
  162. Value:= b
  163. else begin
  164. p:= PChar(Data);
  165. if p^ = sValueQuote then
  166. Data := AnsiExtractQuotedStr(p, sValueQuote);
  167. Value:= Unescape(Trim(Data));
  168. end;
  169. end;
  170. function TutlMCFValue.SaveData: string;
  171. begin
  172. if VarIsType(FValue, varBoolean) then
  173. Result:= BoolToStr(FValue, false)
  174. else if VarIsType(FValue, varInt64) then
  175. Result:= IntToStr(FValue)
  176. else if VarIsType(FValue, varDouble) then
  177. Result:= FloatToStr(Double(FValue), Format)
  178. else begin
  179. Result:= AnsiQuotedStr(Escape(FValue), 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. procedure TutlMCFSection.Clear;
  257. begin
  258. ClearSections;
  259. ClearValues;
  260. end;
  261. procedure TutlMCFSection.Assign(Source: TutlMCFSection);
  262. var
  263. ms: TMemoryStream;
  264. begin
  265. Clear;
  266. ms:= TMemoryStream.Create;
  267. try
  268. Source.SaveData(ms, '', leNone);
  269. ms.Position:= 0;
  270. LoadData(ms, leNone, 0);
  271. finally
  272. FreeAndNil(ms);
  273. end;
  274. end;
  275. function TutlMCFSection.GetSectionCount: integer;
  276. begin
  277. Result:= FSections.Count;
  278. end;
  279. function TutlMCFSection.GetSection(aPath: String): TutlMCFSection;
  280. begin
  281. result := Section(aPath);
  282. end;
  283. function TutlMCFSection.GetSectionByIndex(aIndex: Integer): TutlMCFSection;
  284. begin
  285. result := (FSections.Objects[aIndex] as TutlMCFSection);
  286. end;
  287. function TutlMCFSection.GetSectionName(Index: integer): string;
  288. begin
  289. Result:= FSections[Index];
  290. end;
  291. function TutlMCFSection.GetValueCount: integer;
  292. begin
  293. Result:= FValues.Count;
  294. end;
  295. function TutlMCFSection.GetValueName(Index: integer): string;
  296. begin
  297. Result:= FValues[Index];
  298. end;
  299. procedure TutlMCFSection.ClearSections;
  300. var
  301. i: integer;
  302. begin
  303. for i:= FSections.Count - 1 downto 0 do
  304. FSections.Objects[i].Free;
  305. FSections.Clear;
  306. end;
  307. procedure TutlMCFSection.ClearValues;
  308. var
  309. i: integer;
  310. begin
  311. for i:= FValues.Count - 1 downto 0 do
  312. FValues.Objects[i].Free;
  313. FValues.Clear;
  314. end;
  315. procedure TutlMCFSection.SplitPath(const Path: String; out First, Rest: String);
  316. begin
  317. First:= Copy(Path, 1, Pos(sSectionPathDelim, Path)-1);
  318. if First='' then begin
  319. First:= Path;
  320. Rest:= '';
  321. end else begin
  322. Rest:= Copy(Path, Length(First)+2, MaxInt);
  323. end;
  324. end;
  325. function TutlMCFSection.SectionExists(Path: string): boolean;
  326. var
  327. f,r: String;
  328. i: integer;
  329. begin
  330. SplitPath(Path, f, r);
  331. i:= FSections.IndexOf(f);
  332. Result:= (i >= 0) and ((r='') or (TutlMCFSection(FSections.Objects[i]).SectionExists(r)));
  333. end;
  334. function TutlMCFSection.Section(Path: string): TutlMCFSection;
  335. var
  336. f,r: String;
  337. i: integer;
  338. begin
  339. SplitPath(Path, f, r);
  340. i:= FSections.IndexOf(f);
  341. if r <> '' then begin
  342. if (i >= 0) then
  343. Result:= TutlMCFSection(FSections.Objects[i]).Section(r)
  344. else begin
  345. result := TutlMCFSection.Create;
  346. fSections.AddObject(f, result);
  347. result := result.Section(r);
  348. end;
  349. end else begin
  350. if i >= 0 then
  351. Result:= TutlMCFSection(FSections.Objects[i])
  352. else begin
  353. Result:= TutlMCFSection.Create;
  354. FSections.AddObject(f, Result);
  355. end;
  356. end;
  357. end;
  358. procedure TutlMCFSection.DeleteSection(Name: string);
  359. var
  360. i: integer;
  361. begin
  362. i:= FSections.IndexOf(Name);
  363. if i >= 0 then begin
  364. FSections.Objects[i].Free;
  365. FSections.Delete(i);
  366. end;
  367. end;
  368. function TutlMCFSection.ValueExists(Name: string): boolean;
  369. begin
  370. Result:= FValues.IndexOf(Name) >= 0;
  371. end;
  372. function TutlMCFSection.GetInt(Name: string; Default: Int64): Int64;
  373. var
  374. i: integer;
  375. begin
  376. i:= FValues.IndexOf(Name);
  377. if i < 0 then
  378. Result:= Default
  379. else
  380. Result:= TutlMCFValue(FValues.Objects[i]).Value;
  381. end;
  382. function TutlMCFSection.GetFloat(Name: string; Default: Double): Double;
  383. var
  384. i: integer;
  385. begin
  386. i:= FValues.IndexOf(Name);
  387. if i < 0 then
  388. Result:= Default
  389. else
  390. Result:= TutlMCFValue(FValues.Objects[i]).Value;
  391. end;
  392. function TutlMCFSection.GetStringW(Name: string; Default: UnicodeString): UnicodeString;
  393. var
  394. i: integer;
  395. begin
  396. i:= FValues.IndexOf(Name);
  397. if i < 0 then
  398. Result:= Default
  399. else
  400. Result:= TutlMCFValue(FValues.Objects[i]).Value;
  401. end;
  402. function TutlMCFSection.GetString(Name: string; Default: AnsiString): AnsiString;
  403. begin
  404. Result := AnsiString(GetStringW(Name, UnicodeString(Default)));
  405. end;
  406. function TutlMCFSection.GetBool(Name: string; Default: Boolean): Boolean;
  407. var
  408. i: integer;
  409. begin
  410. i:= FValues.IndexOf(Name);
  411. if i < 0 then
  412. Result:= Default
  413. else
  414. Result:= TutlMCFValue(FValues.Objects[i]).Value;
  415. end;
  416. procedure TutlMCFSection.AddValueChecked(Name: String; Val: TObject);
  417. var
  418. i: integer;
  419. begin
  420. if (Length(Name) < 1) or
  421. (Name[1] in sWhitespaceChars) or
  422. (Name[Length(Name)] in sWhitespaceChars) then
  423. raise EConfigException.CreateFmt('Invalid Value Name: "%s"',[Name]);
  424. for i:= 1 to Length(Name) do
  425. if not (Name[i] in sNameValidChars) then
  426. raise EConfigException.CreateFmt('Invalid Value Name: "%s"',[Name]);
  427. FValues.AddObject(Name, Val);
  428. end;
  429. procedure TutlMCFSection.SetInt(Name: string; Value: Int64);
  430. var
  431. i: integer;
  432. begin
  433. i:= FValues.IndexOf(Name);
  434. if i < 0 then
  435. AddValueChecked(Name, TutlMCFValue.Create(Value))
  436. else
  437. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  438. end;
  439. procedure TutlMCFSection.SetFloat(Name: string; Value: Double);
  440. var
  441. i: integer;
  442. begin
  443. i:= FValues.IndexOf(Name);
  444. if i < 0 then
  445. AddValueChecked(Name, TutlMCFValue.Create(Value))
  446. else
  447. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  448. end;
  449. procedure TutlMCFSection.SetString(Name: string; Value: WideString);
  450. var
  451. i: integer;
  452. begin
  453. i:= FValues.IndexOf(Name);
  454. if i < 0 then
  455. AddValueChecked(Name, TutlMCFValue.Create(Value))
  456. else
  457. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  458. end;
  459. procedure TutlMCFSection.SetString(Name: string; Value: AnsiString);
  460. begin
  461. SetString(Name, WideString(Value));
  462. end;
  463. procedure TutlMCFSection.SetBool(Name: string; Value: Boolean);
  464. var
  465. i: integer;
  466. begin
  467. i:= FValues.IndexOf(Name);
  468. if i < 0 then
  469. AddValueChecked(Name, TutlMCFValue.Create(Value))
  470. else
  471. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  472. end;
  473. procedure TutlMCFSection.UnsetValue(Name: string);
  474. var
  475. i: integer;
  476. begin
  477. i:= FValues.IndexOf(Name);
  478. if i >= 0 then begin
  479. FValues.Objects[i].Free;
  480. FValues.Delete(i);
  481. end;
  482. end;
  483. procedure TutlMCFSection.LoadData(Data: TStream; LineEnds: TutlMCFLineEndMarkerMode; Depth: Integer);
  484. var
  485. reader: TutlStreamReader;
  486. l, sn, vn, vs: string;
  487. se: TutlMCFSection;
  488. va: TutlMCFValue;
  489. begin
  490. reader:= TutlStreamReader.Create(Data);
  491. try
  492. repeat
  493. l:= reader.ReadLine;
  494. l:= trim(l);
  495. if (l = '') or AnsiStartsStr(sComment, l) then
  496. continue;
  497. if ((LineEnds in [leNone, leAcceptNoWrite]) and (l = sSectionEnd)) or
  498. ((LineEnds in [leAcceptNoWrite, leAlways]) and (l = sSectionEnd+sLineEndMarker)) then begin
  499. if Depth > 0 then
  500. exit
  501. else
  502. raise EConfigException.Create('Encountered Section End where none was expected.');
  503. end;
  504. if AnsiEndsStr(sSectionMarker, l) then begin
  505. sn:= trim(Copy(l, 1, length(l) - length(sSectionMarker)));
  506. if SectionExists(sn) then
  507. raise EConfigException.Create('Redeclared Section: '+sn);
  508. if Pos(sSectionPathDelim,sn) > 0 then
  509. raise EConfigException.Create('Invalid Section Name: '+sn);
  510. se:= TutlMCFSection.Create;
  511. try
  512. se.LoadData(Data, LineEnds, Depth + 1);
  513. FSections.AddObject(sn, se);
  514. except
  515. FreeAndNil(se);
  516. end;
  517. end else if (Pos(sValueDelim, l) > 0) then begin
  518. if (LineEnds in [leAcceptNoWrite, leAlways]) and AnsiEndsStr(sLineEndMarker, l) then
  519. Delete(l, length(l), 1);
  520. vn:= trim(Copy(l, 1, Pos(sValueDelim, l) - 1));
  521. vs:= trim(Copy(l, Pos(sValueDelim, l) + 1, Maxint));
  522. if ValueExists(vn) then
  523. raise EConfigException.Create('Redeclared Value: '+vn);
  524. va:= TutlMCFValue.Create('');
  525. try
  526. va.LoadData(vs);
  527. AddValueChecked(vn, va);
  528. except
  529. FreeAndNil(va);
  530. end;
  531. end else
  532. raise EConfigException.Create('Cannot Parse Line: '+l);
  533. until reader.IsEOF;
  534. if Depth > 0 then
  535. raise EConfigException.Create('Expected Section End, but reached stream end.');
  536. Depth:= Depth - 1;
  537. finally
  538. FreeAndNil(reader);
  539. end;
  540. end;
  541. procedure TutlMCFSection.SaveData(Stream: TStream; Indent: string;
  542. LineEnds: TutlMCFLineEndMarkerMode);
  543. var
  544. writer: TutlStreamWriter;
  545. i: integer;
  546. ele, s: AnsiString;
  547. begin
  548. if LineEnds in [leAlways] then
  549. ele:= sLineEndMarker
  550. else
  551. ele:= '';
  552. writer:= TutlStreamWriter.Create(Stream);
  553. try
  554. for i:= 0 to FValues.Count - 1 do begin
  555. s:= Indent + FValues[i] + ' ' + sValueDelim + ' ' + TutlMCFValue(FValues.Objects[i]).SaveData + ele;
  556. writer.WriteLine(s);
  557. end;
  558. for i:= 0 to FSections.Count - 1 do begin
  559. s:= Indent + FSections[i] + sSectionMarker;
  560. writer.WriteLine(s);
  561. TutlMCFSection(FSections.Objects[i]).SaveData(Stream, Indent + sIndentOnSave, LineEnds);
  562. s:= Indent + sSectionEnd + ele;
  563. writer.WriteLine(s);
  564. end;
  565. finally
  566. FreeAndNil(writer);
  567. end;
  568. end;
  569. { TutlMCFFile }
  570. constructor TutlMCFFile.Create(Data: TStream; LineEndMode: TutlMCFLineEndMarkerMode);
  571. begin
  572. inherited Create;
  573. fLineEndMode:= LineEndMode;
  574. if Assigned(Data) then
  575. LoadFromStream(Data);
  576. end;
  577. procedure TutlMCFFile.LoadFromStream(Stream: TStream);
  578. begin
  579. ClearSections;
  580. ClearValues;
  581. LoadData(Stream, fLineEndMode, 0);
  582. end;
  583. procedure TutlMCFFile.SaveToStream(Stream: TStream);
  584. begin
  585. SaveData(Stream, '', fLineEndMode);
  586. end;
  587. end.