Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

717 linhas
18 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. procedure UnsetValue(Name: string);
  77. function GetValue (Name: String; Default: Variant): Variant;
  78. function GetInt (Name: string; Default: Int64 = 0): Int64; overload;
  79. function GetFloat (Name: string; Default: Double = 0): Double; overload;
  80. function GetString (Name: string; Default: AnsiString = ''): AnsiString; overload;
  81. function GetStringW (Name: string; Default: UnicodeString = ''): UnicodeString; overload;
  82. function GetBool (Name: string; Default: Boolean = false): Boolean; overload;
  83. function TryGetValue (aName: String; out aValue: Variant): Boolean;
  84. function TryGetInt (aName: String; out aValue: Int64): Boolean;
  85. function TryGetFloat (aName: String; out aValue: Double): Boolean;
  86. function TryGetString (aName: String; out aValue: AnsiString): Boolean;
  87. function TryGetStringW(aName: String; out aValue: UnicodeString): Boolean;
  88. function TryGetBool (aName: String; out aValue: Boolean): Boolean;
  89. procedure SetInt (Name: string; Value: Int64); overload;
  90. procedure SetFloat (Name: string; Value: Double); overload;
  91. procedure SetString (Name: string; Value: WideString); overload;
  92. procedure SetString (Name: string; Value: AnsiString); overload;
  93. procedure SetBool (Name: string; Value: Boolean); overload;
  94. end;
  95. { TutlMCFFile }
  96. TutlMCFFile = class(TutlMCFSection)
  97. private
  98. fLineEndMode: TutlMCFLineEndMarkerMode;
  99. public
  100. constructor Create(Data: TStream; LineEndMode: TutlMCFLineEndMarkerMode = leAcceptNoWrite);
  101. procedure LoadFromStream(Stream: TStream);
  102. procedure SaveToStream(Stream: TStream);
  103. end;
  104. implementation
  105. uses Variants, StrUtils;
  106. const
  107. sComment = '#';
  108. sSectionEnd = 'end';
  109. sSectionMarker = ':';
  110. sSectionPathDelim = '.';
  111. sLineEndMarker = ';';
  112. sValueDelim = '=';
  113. sValueQuote = '''';
  114. sValueDecimal = '.';
  115. sIndentOnSave = ' ';
  116. sNameValidChars = [' ' .. #$7F] - [sValueDelim];
  117. sWhitespaceChars = [#0 .. ' '];
  118. type
  119. StoredValue = Variant;
  120. { TutlMCFValue }
  121. TutlMCFValue = class
  122. private
  123. Format: TFormatSettings;
  124. FValue: StoredValue;
  125. procedure SetValue(const Value: StoredValue);
  126. protected
  127. function CheckSpecialChars(Data: WideString): boolean;
  128. procedure LoadData(Data: string);
  129. function SaveData: string;
  130. class function Escape(Value: WideString): AnsiString;
  131. class function Unescape(Value: AnsiString): WideString;
  132. public
  133. constructor Create(Val: StoredValue);
  134. property Value: StoredValue read FValue write SetValue;
  135. end;
  136. { TkcfValue }
  137. constructor TutlMCFValue.Create(Val: StoredValue);
  138. begin
  139. inherited Create;
  140. SetValue(Val);
  141. Format.DecimalSeparator:= sValueDecimal;
  142. end;
  143. procedure TutlMCFValue.SetValue(const Value: StoredValue);
  144. begin
  145. FValue:= Value;
  146. end;
  147. function TutlMCFValue.CheckSpecialChars(Data: WideString): boolean;
  148. var
  149. i: Integer;
  150. begin
  151. result := false;
  152. for i:= 1 to Length(Data) do
  153. if Data[i] in [sSectionMarker, sValueQuote, sValueDelim, sLineEndMarker, ' '] then
  154. exit;
  155. result := true;
  156. end;
  157. procedure TutlMCFValue.LoadData(Data: string);
  158. var
  159. b: boolean;
  160. i: int64;
  161. d: double;
  162. p: PChar;
  163. begin
  164. if TryStrToInt64(Data, i) then
  165. Value:= i
  166. else if TryStrToFloat(Data, d, Format) then
  167. Value:= d
  168. else if TryStrToBool(Data, b) then
  169. Value:= b
  170. else begin
  171. p:= PChar(Data);
  172. if p^ = sValueQuote then
  173. Data := AnsiExtractQuotedStr(p, sValueQuote);
  174. Value:= Unescape(Trim(Data));
  175. end;
  176. end;
  177. function TutlMCFValue.SaveData: string;
  178. begin
  179. if VarIsType(FValue, varBoolean) then
  180. Result:= BoolToStr(FValue, false)
  181. else if VarIsType(FValue, varInt64) then
  182. Result:= IntToStr(FValue)
  183. else if VarIsType(FValue, varDouble) then
  184. Result:= FloatToStr(Double(FValue), Format)
  185. else begin
  186. Result:= AnsiQuotedStr(Escape(FValue), sValueQuote);
  187. end;
  188. end;
  189. class function TutlMCFValue.Escape(Value: WideString): AnsiString;
  190. var
  191. i: integer;
  192. wc: WideChar;
  193. begin
  194. Result:= '';
  195. for i:= 1 to length(Value) do begin
  196. wc:= Value[i];
  197. case Ord(wc) of
  198. Ord('\'),
  199. $007F..$FFFF: Result:= Result + '\'+IntToHex(ord(wc),4);
  200. else
  201. Result:= Result + AnsiChar(wc);
  202. end;
  203. end;
  204. end;
  205. class function TutlMCFValue.Unescape(Value: AnsiString): WideString;
  206. var
  207. i: integer;
  208. c: Char;
  209. begin
  210. Result:= '';
  211. i:= 1;
  212. while i <= length(value) do begin
  213. c:= Value[i];
  214. if c='\' then begin
  215. Result:= Result + WideChar(StrToInt('$'+Copy(Value,i+1,4)));
  216. inc(i, 4);
  217. end else
  218. Result:= Result + WideChar(c);
  219. inc(i);
  220. end;
  221. end;
  222. { TutlMCFSection.TSectionEnumerator }
  223. function TutlMCFSection.TSectionEnumerator.GetCurrent: TutlMCFSection;
  224. begin
  225. result := TutlMCFSection(fList.Objects[fPosition]);
  226. end;
  227. function TutlMCFSection.TSectionEnumerator.MoveNext: Boolean;
  228. begin
  229. inc(fPosition);
  230. result := (fPosition < fList.Count);
  231. end;
  232. constructor TutlMCFSection.TSectionEnumerator.Create(const aList: TStringList);
  233. begin
  234. inherited Create;
  235. fList := aList;
  236. fPosition := -1;
  237. end;
  238. { TkcfCompound }
  239. constructor TutlMCFSection.Create;
  240. begin
  241. inherited;
  242. FSections:= TStringList.Create;
  243. FSections.CaseSensitive:= false;
  244. FSections.Sorted:= true;
  245. FSections.Duplicates:= dupError;
  246. FValues:= TStringList.Create;
  247. FValues.CaseSensitive:= false;
  248. FValues.Sorted:= true;
  249. FValues.Duplicates:= dupError;
  250. end;
  251. destructor TutlMCFSection.Destroy;
  252. begin
  253. ClearSections;
  254. ClearValues;
  255. FreeAndNil(FSections);
  256. FreeAndNil(FValues);
  257. inherited;
  258. end;
  259. function TutlMCFSection.GetEnumerator: TSectionEnumerator;
  260. begin
  261. result := TSectionEnumerator.Create(FSections);
  262. end;
  263. procedure TutlMCFSection.Clear;
  264. begin
  265. ClearSections;
  266. ClearValues;
  267. end;
  268. procedure TutlMCFSection.Assign(Source: TutlMCFSection);
  269. var
  270. ms: TMemoryStream;
  271. begin
  272. Clear;
  273. ms:= TMemoryStream.Create;
  274. try
  275. Source.SaveData(ms, '', leNone);
  276. ms.Position:= 0;
  277. LoadData(ms, leNone, 0);
  278. finally
  279. FreeAndNil(ms);
  280. end;
  281. end;
  282. function TutlMCFSection.GetSectionCount: integer;
  283. begin
  284. Result:= FSections.Count;
  285. end;
  286. function TutlMCFSection.GetSection(aPath: String): TutlMCFSection;
  287. begin
  288. result := Section(aPath);
  289. end;
  290. function TutlMCFSection.GetSectionByIndex(aIndex: Integer): TutlMCFSection;
  291. begin
  292. result := (FSections.Objects[aIndex] as TutlMCFSection);
  293. end;
  294. function TutlMCFSection.GetSectionName(Index: integer): string;
  295. begin
  296. Result:= FSections[Index];
  297. end;
  298. function TutlMCFSection.GetValueCount: integer;
  299. begin
  300. Result:= FValues.Count;
  301. end;
  302. function TutlMCFSection.GetValueName(Index: integer): string;
  303. begin
  304. Result:= FValues[Index];
  305. end;
  306. procedure TutlMCFSection.ClearSections;
  307. var
  308. i: integer;
  309. begin
  310. for i:= FSections.Count - 1 downto 0 do
  311. FSections.Objects[i].Free;
  312. FSections.Clear;
  313. end;
  314. procedure TutlMCFSection.ClearValues;
  315. var
  316. i: integer;
  317. begin
  318. for i:= FValues.Count - 1 downto 0 do
  319. FValues.Objects[i].Free;
  320. FValues.Clear;
  321. end;
  322. procedure TutlMCFSection.SplitPath(const Path: String; out First, Rest: String);
  323. begin
  324. First:= Copy(Path, 1, Pos(sSectionPathDelim, Path)-1);
  325. if First='' then begin
  326. First:= Path;
  327. Rest:= '';
  328. end else begin
  329. Rest:= Copy(Path, Length(First)+2, MaxInt);
  330. end;
  331. end;
  332. function TutlMCFSection.SectionExists(Path: string): boolean;
  333. var
  334. f,r: String;
  335. i: integer;
  336. begin
  337. SplitPath(Path, f, r);
  338. i:= FSections.IndexOf(f);
  339. Result:= (i >= 0) and ((r='') or (TutlMCFSection(FSections.Objects[i]).SectionExists(r)));
  340. end;
  341. function TutlMCFSection.Section(Path: string): TutlMCFSection;
  342. var
  343. f,r: String;
  344. i: integer;
  345. begin
  346. SplitPath(Path, f, r);
  347. i:= FSections.IndexOf(f);
  348. if r <> '' then begin
  349. if (i >= 0) then
  350. Result:= TutlMCFSection(FSections.Objects[i]).Section(r)
  351. else begin
  352. result := TutlMCFSection.Create;
  353. fSections.AddObject(f, result);
  354. result := result.Section(r);
  355. end;
  356. end else begin
  357. if i >= 0 then
  358. Result:= TutlMCFSection(FSections.Objects[i])
  359. else begin
  360. Result:= TutlMCFSection.Create;
  361. FSections.AddObject(f, Result);
  362. end;
  363. end;
  364. end;
  365. procedure TutlMCFSection.DeleteSection(Name: string);
  366. var
  367. i: integer;
  368. begin
  369. i:= FSections.IndexOf(Name);
  370. if i >= 0 then begin
  371. FSections.Objects[i].Free;
  372. FSections.Delete(i);
  373. end;
  374. end;
  375. function TutlMCFSection.ValueExists(Name: string): boolean;
  376. begin
  377. Result:= FValues.IndexOf(Name) >= 0;
  378. end;
  379. procedure TutlMCFSection.UnsetValue(Name: string);
  380. var
  381. i: integer;
  382. begin
  383. i:= FValues.IndexOf(Name);
  384. if i >= 0 then begin
  385. FValues.Objects[i].Free;
  386. FValues.Delete(i);
  387. end;
  388. end;
  389. function TutlMCFSection.GetValue(Name: String; Default: Variant): Variant;
  390. begin
  391. if not TryGetValue(Name, result) then
  392. result := Default;
  393. end;
  394. function TutlMCFSection.GetInt(Name: string; Default: Int64): Int64;
  395. begin
  396. if not TryGetInt(Name, result) then
  397. result := Default;
  398. end;
  399. function TutlMCFSection.GetFloat(Name: string; Default: Double): Double;
  400. begin
  401. if not TryGetFloat(Name, result) then
  402. result := Default;
  403. end;
  404. function TutlMCFSection.GetStringW(Name: string; Default: UnicodeString): UnicodeString;
  405. begin
  406. if not TryGetStringW(Name, result) then
  407. result := Default;
  408. end;
  409. function TutlMCFSection.GetString(Name: string; Default: AnsiString): AnsiString;
  410. begin
  411. if not TryGetString(Name, result) then
  412. result := Default;
  413. end;
  414. function TutlMCFSection.GetBool(Name: string; Default: Boolean): Boolean;
  415. begin
  416. if not TryGetBool(Name, result) then
  417. result := Default;
  418. end;
  419. function TutlMCFSection.TryGetValue(aName: String; out aValue: Variant): Boolean;
  420. var
  421. i: Integer;
  422. begin
  423. i := FValues.IndexOf(aName);
  424. result := (i >= 0);
  425. if result then
  426. aValue := TutlMcfValue(FValues.Objects[i]).Value;
  427. end;
  428. function TutlMCFSection.TryGetInt(aName: String; out aValue: Int64): Boolean;
  429. var
  430. v: Variant;
  431. begin
  432. result := TryGetValue(aName, v);
  433. if result then
  434. aValue := v;
  435. end;
  436. function TutlMCFSection.TryGetFloat(aName: String; out aValue: Double): Boolean;
  437. var
  438. v: Variant;
  439. begin
  440. result := TryGetValue(aName, v);
  441. if result then
  442. aValue := v;
  443. end;
  444. function TutlMCFSection.TryGetString(aName: String; out aValue: AnsiString): Boolean;
  445. var
  446. v: Variant;
  447. begin
  448. result := TryGetValue(aName, v);
  449. if result then
  450. aValue := v;
  451. end;
  452. function TutlMCFSection.TryGetStringW(aName: String; out aValue: UnicodeString): Boolean;
  453. var
  454. v: Variant;
  455. begin
  456. result := TryGetValue(aName, v);
  457. if result then
  458. aValue := v;
  459. end;
  460. function TutlMCFSection.TryGetBool(aName: String; out aValue: Boolean): Boolean;
  461. var
  462. v: Variant;
  463. begin
  464. result := TryGetValue(aName, v);
  465. if result then
  466. aValue := v;
  467. end;
  468. procedure TutlMCFSection.AddValueChecked(Name: String; Val: TObject);
  469. var
  470. i: integer;
  471. begin
  472. if (Length(Name) < 1) or
  473. (Name[1] in sWhitespaceChars) or
  474. (Name[Length(Name)] in sWhitespaceChars) then
  475. raise EConfigException.CreateFmt('Invalid Value Name: "%s"',[Name]);
  476. for i:= 1 to Length(Name) do
  477. if not (Name[i] in sNameValidChars) then
  478. raise EConfigException.CreateFmt('Invalid Value Name: "%s"',[Name]);
  479. FValues.AddObject(Name, Val);
  480. end;
  481. procedure TutlMCFSection.SetInt(Name: string; Value: Int64);
  482. var
  483. i: integer;
  484. begin
  485. i:= FValues.IndexOf(Name);
  486. if i < 0 then
  487. AddValueChecked(Name, TutlMCFValue.Create(Value))
  488. else
  489. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  490. end;
  491. procedure TutlMCFSection.SetFloat(Name: string; Value: Double);
  492. var
  493. i: integer;
  494. begin
  495. i:= FValues.IndexOf(Name);
  496. if i < 0 then
  497. AddValueChecked(Name, TutlMCFValue.Create(Value))
  498. else
  499. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  500. end;
  501. procedure TutlMCFSection.SetString(Name: string; Value: WideString);
  502. var
  503. i: integer;
  504. begin
  505. i:= FValues.IndexOf(Name);
  506. if i < 0 then
  507. AddValueChecked(Name, TutlMCFValue.Create(Value))
  508. else
  509. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  510. end;
  511. procedure TutlMCFSection.SetString(Name: string; Value: AnsiString);
  512. begin
  513. SetString(Name, WideString(Value));
  514. end;
  515. procedure TutlMCFSection.SetBool(Name: string; Value: Boolean);
  516. var
  517. i: integer;
  518. begin
  519. i:= FValues.IndexOf(Name);
  520. if i < 0 then
  521. AddValueChecked(Name, TutlMCFValue.Create(Value))
  522. else
  523. TutlMCFValue(FValues.Objects[i]).Value:= Value;
  524. end;
  525. procedure TutlMCFSection.LoadData(Data: TStream; LineEnds: TutlMCFLineEndMarkerMode; Depth: Integer);
  526. var
  527. reader: TutlStreamReader;
  528. l, sn, vn, vs: string;
  529. se: TutlMCFSection;
  530. va: TutlMCFValue;
  531. begin
  532. reader:= TutlStreamReader.Create(Data, false);
  533. try
  534. repeat
  535. l:= reader.ReadLine;
  536. l:= trim(l);
  537. if (l = '') or AnsiStartsStr(sComment, l) then
  538. continue;
  539. if ((LineEnds in [leNone, leAcceptNoWrite]) and (l = sSectionEnd)) or
  540. ((LineEnds in [leAcceptNoWrite, leAlways]) and (l = sSectionEnd+sLineEndMarker)) then begin
  541. if Depth > 0 then
  542. exit
  543. else
  544. raise EConfigException.Create('Encountered Section End where none was expected.');
  545. end;
  546. if AnsiEndsStr(sSectionMarker, l) then begin
  547. sn:= trim(Copy(l, 1, length(l) - length(sSectionMarker)));
  548. if SectionExists(sn) then
  549. raise EConfigException.Create('Redeclared Section: '+sn);
  550. if Pos(sSectionPathDelim,sn) > 0 then
  551. raise EConfigException.Create('Invalid Section Name: '+sn);
  552. se:= TutlMCFSection.Create;
  553. try
  554. se.LoadData(Data, LineEnds, Depth + 1);
  555. FSections.AddObject(sn, se);
  556. except
  557. FreeAndNil(se);
  558. end;
  559. end else if (Pos(sValueDelim, l) > 0) then begin
  560. if (LineEnds in [leAcceptNoWrite, leAlways]) and AnsiEndsStr(sLineEndMarker, l) then
  561. Delete(l, length(l), 1);
  562. vn:= trim(Copy(l, 1, Pos(sValueDelim, l) - 1));
  563. vs:= trim(Copy(l, Pos(sValueDelim, l) + 1, Maxint));
  564. if ValueExists(vn) then
  565. raise EConfigException.Create('Redeclared Value: '+vn);
  566. va:= TutlMCFValue.Create('');
  567. try
  568. va.LoadData(vs);
  569. AddValueChecked(vn, va);
  570. except
  571. FreeAndNil(va);
  572. end;
  573. end else
  574. raise EConfigException.Create('Cannot Parse Line: '+l);
  575. until reader.IsEOF;
  576. if Depth > 0 then
  577. raise EConfigException.Create('Expected Section End, but reached stream end.');
  578. Depth:= Depth - 1;
  579. finally
  580. FreeAndNil(reader);
  581. end;
  582. end;
  583. procedure TutlMCFSection.SaveData(Stream: TStream; Indent: string; LineEnds: TutlMCFLineEndMarkerMode);
  584. var
  585. writer: TutlStreamWriter;
  586. i: integer;
  587. ele, s: AnsiString;
  588. begin
  589. if LineEnds in [leAlways] then
  590. ele:= sLineEndMarker
  591. else
  592. ele:= '';
  593. writer:= TutlStreamWriter.Create(Stream, false);
  594. try
  595. for i:= 0 to FValues.Count - 1 do begin
  596. s:= Indent + FValues[i] + ' ' + sValueDelim + ' ' + TutlMCFValue(FValues.Objects[i]).SaveData + ele;
  597. writer.WriteLine(s);
  598. end;
  599. for i:= 0 to FSections.Count - 1 do begin
  600. s:= Indent + FSections[i] + sSectionMarker;
  601. writer.WriteLine(s);
  602. TutlMCFSection(FSections.Objects[i]).SaveData(Stream, Indent + sIndentOnSave, LineEnds);
  603. s:= Indent + sSectionEnd + ele;
  604. writer.WriteLine(s);
  605. end;
  606. finally
  607. FreeAndNil(writer);
  608. end;
  609. end;
  610. { TutlMCFFile }
  611. constructor TutlMCFFile.Create(Data: TStream; LineEndMode: TutlMCFLineEndMarkerMode);
  612. begin
  613. inherited Create;
  614. fLineEndMode:= LineEndMode;
  615. if Assigned(Data) then
  616. LoadFromStream(Data);
  617. end;
  618. procedure TutlMCFFile.LoadFromStream(Stream: TStream);
  619. begin
  620. ClearSections;
  621. ClearValues;
  622. LoadData(Stream, fLineEndMode, 0);
  623. end;
  624. procedure TutlMCFFile.SaveToStream(Stream: TStream);
  625. begin
  626. SaveData(Stream, '', fLineEndMode);
  627. end;
  628. end.