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.

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