FastReport_FMX_2.8.12/LibD28x64/FMX.frxXML.pas
2024-07-06 22:41:12 +02:00

984 lines
22 KiB
ObjectPascal

{******************************************}
{ }
{ FastReport v4.0 }
{ XML document }
{ }
{ Copyright (c) 1998-2008 }
{ by Alexander Tzyganenko, }
{ Fast Reports Inc. }
{ }
{******************************************}
unit FMX.frxXML;
interface
{$I fmx.inc}
{$I frx.inc}
uses
System.Classes, System.Variants, System.Types, System.SysUtils;
type
TfrxInvalidXMLException = class(Exception);
TfrxXMLItem = class(TObject)
private
FData: Pointer; { optional item data }
FHiOffset: Byte; { hi-part of the offset }
FItems: TList; { subitems }
FLoaded: Boolean; { item is loaded, no need to call LoadItem }
FLoOffset: Integer; { lo-part of the offset }
FModified: Boolean; { item is modified (used by preview designer) }
FName: String; { item name }
FParent: TfrxXMLItem; { item parent }
FText: String; { item attributes }
FUnloadable: Boolean;
FValue: String; { item value <item>Value</item> }
function GetCount: Integer;
function GetItems(Index: Integer): TfrxXMLItem;
function GetOffset: Int64;
procedure SetOffset(const Value: Int64);
function GetProp(Index: String): String;
procedure SetProp(Index: String; const Value: String);
public
constructor Create;
destructor Destroy; override;
procedure AddItem(Item: TfrxXMLItem);
procedure Clear;
procedure InsertItem(Index: Integer; Item: TfrxXMLItem);
function Add: TfrxXMLItem; overload;
function Add(Name: string): TfrxXMLItem; overload;
function Find(const Name: String): Integer;
function FindItem(const Name: String): TfrxXMLItem;
function IndexOf(Item: TfrxXMLItem): Integer;
function PropExists(const Index: String): Boolean;
function Root: TfrxXMLItem;
procedure DeleteProp(const Index: String);
property Count: Integer read GetCount;
property Data: Pointer read FData write FData;
property Items[Index: Integer]: TfrxXMLItem read GetItems; default;
property Loaded: Boolean read FLoaded;
property Modified: Boolean read FModified write FModified;
property Name: String read FName write FName;
{ offset is the position of the item in the tempstream. This parameter is needed
for dynamically loading large files. Items that can be loaded on-demand must
have Unloadable = True (in run-time) or have 'ld="0"' parameter (in the file) }
property Offset: Int64 read GetOffset write SetOffset;
property Parent: TfrxXMLItem read FParent;
property Prop[Index: String]: String read GetProp write SetProp;
property Text: String read FText write FText;
property Unloadable: Boolean read FUnloadable write FUnloadable;
property Value: String read FValue write FValue;
end;
TfrxXMLDocument = class(TObject)
private
FAutoIndent: Boolean; { use indents when writing document to a file }
FRoot: TfrxXMLItem; { root item }
FTempDir: String; { folder for temporary files }
FTempFile: String; { tempfile name }
FTempStream: TStream; { temp stream associated with tempfile }
FTempFileCreated: Boolean; { tempfile has been created - need to delete it }
FOldVersion: Boolean;
procedure CreateTempFile;
procedure DeleteTempFile;
public
constructor Create;
destructor Destroy; override;
procedure Clear;
procedure LoadItem(Item: TfrxXMLItem);
procedure UnloadItem(Item: TfrxXMLItem);
procedure SaveToStream(Stream: TStream);
procedure LoadFromStream(Stream: TStream; AllowPartialLoading: Boolean = False);
procedure SaveToFile(const FileName: String);
procedure LoadFromFile(const FileName: String);
property AutoIndent: Boolean read FAutoIndent write FAutoIndent;
property Root: TfrxXMLItem read FRoot;
property TempDir: String read FTempDir write FTempDir;
property OldVersion: Boolean read FOldVersion;
end;
{ TfrxXMLReader and TfrxXMLWriter are doing actual read/write to the XML file.
Read/write process is buffered. }
TfrxXMLReader = class(TObject)
private
FBuffer: PAnsiChar;
FBufPos: Integer;
FBufEnd: Integer;
FPosition: Int64;
FSize: Int64;
FStream: TStream;
FOldFormat: Boolean;
procedure SetPosition(const Value: Int64);
procedure ReadBuffer;
procedure ReadItem(var NameS, Text: String);
public
constructor Create(Stream: TStream);
destructor Destroy; override;
procedure RaiseException;
procedure ReadHeader;
procedure ReadRootItem(Item: TfrxXMLItem; ReadChildren: Boolean = True);
property Position: Int64 read FPosition write SetPosition;
property Size: Int64 read FSize;
end;
TfrxXMLWriter = class(TObject)
private
FAutoIndent: Boolean;
FBuffer: AnsiString;
FStream: TStream;
FTempStream: TStream;
procedure FlushBuffer;
procedure WriteLn(const s: AnsiString);
procedure WriteItem(Item: TfrxXMLItem; Level: Integer = 0);
public
constructor Create(Stream: TStream);
procedure WriteHeader;
procedure WriteRootItem(RootItem: TfrxXMLItem);
property TempStream: TStream read FTempStream write FTempStream;
property AutoIndent: Boolean read FAutoIndent write FAutoIndent;
end;
{ StrToXML changes '<', '>', '"', cr, lf symbols to its ascii codes }
function frxStrToXML(const s: String): String;
{ ValueToXML convert a value to the valid XML string }
function frxValueToXML(const Value: Variant): String;
{ XMLToStr is opposite to StrToXML function }
function frxXMLToStr(const s: String): String;
implementation
uses
System.IOUtils
{$IFDEF MSWINDOWS}
, Winapi.Windows
{$ENDIF MSWINDOWS}
{$IFDEF POSIX}
, Posix.Stdio
, Posix.Unistd
{$ENDIF POSIX};
function frxUppercase(s: String): String;
begin
//{$IFDEF }
// AnsiUppercase
Result := AnsiUppercase(s);
end;
function frxStrToXML(const s: String): String;
const
SpecChars = ['<', '>', '"', #10, #13, '&'];
var
i, lenRes, resI, ch: Integer;
pRes: PChar;
procedure ReplaceChars(var s: String; i: Integer);
begin
Insert('#' + IntToStr(Ord(s[i])) + ';', s, i + 1);
s[i] := '&';
end;
begin
lenRes := Length(s);
if lenRes < 32 then
begin
Result := s;
for i := lenRes downto 1 do
if CharInSet(s[i], SpecChars) then
if s[i] <> '&' then
ReplaceChars(Result, i)
else
begin
if Copy(s, i + 1, 5) = 'quot;' then
begin
Delete(Result, i, 6);
Insert('&#34;', Result, i);
end;
end;
Exit;
end;
{ speed optimized code }
SetLength(Result, lenRes);
pRes := PChar(Result) - 1;
resI := 1;
i := 1;
while i <= Length(s) do
begin
if resI + 5 > lenRes then
begin
Inc(lenRes, 256);
SetLength(Result, lenRes);
pRes := PChar(Result) - 1;
end;
if CharInSet(s[i], SpecChars) then
begin
if (s[i] = '&') and (i <= Length(s) - 5) and (s[i + 1] = 'q') and
(s[i + 2] = 'u') and (s[i + 3] = 'o') and (s[i + 4] = 't') and (s[i + 5] = ';') then
begin
pRes[resI] := '&';
pRes[resI + 1] := '#';
pRes[resI + 2] := '3';
pRes[resI + 3] := '4';
pRes[resI + 4] := ';';
Inc(resI, 4);
Inc(i, 5);
end
else
begin
pRes[resI] := '&';
pRes[resI + 1] := '#';
ch := Ord(s[i]);
if ch < 10 then
begin
pRes[resI + 2] := Char(Chr(ch + $30));
Inc(resI, 3);
end
else if ch < 100 then
begin
pRes[resI + 2] := Char(Chr(ch div 10 + $30));
pRes[resI + 3] := Char(Chr(ch mod 10 + $30));
Inc(resI, 4);
end
else
begin
pRes[resI + 2] := Char(Chr(ch div 100 + $30));
pRes[resI + 3] := Char(Chr(ch mod 100 div 10 + $30));
pRes[resI + 4] := Char(Chr(ch mod 10 + $30));
Inc(resI, 5);
end;
pRes[resI] := ';';
end;
end
else
pRes[resI] := s[i];
Inc(resI);
Inc(i);
end;
SetLength(Result, resI - 1);
end;
function frxXMLToStr(const s: String): String;
var
i, j, h, n: Integer;
begin
Result := s;
i := 1;
n := Length(s);
while i < n do
begin
if Result[i] = '&' then
if (i + 3 <= n) and (Result[i + 1] = '#') then
begin
j := i + 3;
while Result[j] <> ';' do
Inc(j);
h := StrToInt(String(Copy(Result, i + 2, j - i - 2)));
Delete(Result, i, j - i);
Result[i] := Char(Chr(h));
Dec(n, j - i);
end
else if Copy(Result, i + 1, 5) = 'quot;' then
begin
Delete(Result, i, 5);
Result[i] := '"';
Dec(n, 5);
end
else if Copy(Result, i + 1, 4) = 'amp;' then
begin
Delete(Result, i, 4);
Result[i] := '&';
Dec(n, 4);
end
else if Copy(Result, i + 1, 3) = 'lt;' then
begin
Delete(Result, i, 3);
Result[i] := '<';
Dec(n, 3);
end
else if Copy(Result, i + 1, 3) = 'gt;' then
begin
Delete(Result, i, 3);
Result[i] := '>';
Dec(n, 3);
end;
Inc(i);
end;
end;
function frxValueToXML(const Value: Variant): String;
begin
case TVarData(Value).VType of
varSmallint, varInteger, varByte:
Result := IntToStr(Value);
varSingle, varDouble, varCurrency:
Result := FloatToStr(Value);
varDate:
Result := DateToStr(Value);
varOleStr, varString, varVariant, varUString:
Result := frxStrToXML(Value);
varBoolean:
if Value = True then Result := '1' else Result := '0';
else
Result := '';
end;
end;
{ TfrxXMLItem }
constructor TfrxXMLItem.Create;
begin
FLoaded := True;
end;
destructor TfrxXMLItem.Destroy;
begin
Clear;
if FParent <> nil then
FParent.FItems.Remove(Self);
inherited;
end;
procedure TfrxXMLItem.Clear;
begin
if FItems <> nil then
begin
while FItems.Count > 0 do
TfrxXMLItem(FItems[0]).Free;
FItems.Free;
FItems := nil;
end;
if FUnloadable then
FLoaded := False;
end;
function TfrxXMLItem.GetItems(Index: Integer): TfrxXMLItem;
begin
Result := TfrxXMLItem(FItems[Index]);
end;
function TfrxXMLItem.GetCount: Integer;
begin
if FItems = nil then
Result := 0 else
Result := FItems.Count;
end;
function TfrxXMLItem.Add: TfrxXMLItem;
begin
Result := TfrxXMLItem.Create;
AddItem(Result);
end;
function TfrxXMLItem.Add(Name: string): TfrxXMLItem;
begin
Result := Add;
Result.Name := Name;
end;
procedure TfrxXMLItem.AddItem(Item: TfrxXMLItem);
begin
if FItems = nil then
FItems := TList.Create;
FItems.Add(Item);
if Item.FParent <> nil then
Item.FParent.FItems.Remove(Item);
Item.FParent := Self;
end;
procedure TfrxXMLItem.InsertItem(Index: Integer; Item: TfrxXMLItem);
begin
AddItem(Item);
FItems.Delete(FItems.Count - 1);
FItems.Insert(Index, Item);
end;
function TfrxXMLItem.Find(const Name: String): Integer;
var
i: Integer;
begin
Result := -1;
for i := 0 to Count - 1 do
if CompareText(Items[i].Name, Name) = 0 then
begin
Result := i;
break;
end;
end;
function TfrxXMLItem.FindItem(const Name: String): TfrxXMLItem;
var
i: Integer;
begin
i := Find(Name);
if i = -1 then
begin
Result := Add;
Result.Name := Name;
end
else
Result := Items[i];
end;
function TfrxXMLItem.GetOffset: Int64;
begin
Result := Int64(FHiOffset) * $100000000 + Int64(FLoOffset);
end;
procedure TfrxXMLItem.SetOffset(const Value: Int64);
begin
FHiOffset := Value div $100000000;
FLoOffset := Value mod $100000000;
end;
function TfrxXMLItem.Root: TfrxXMLItem;
begin
Result := Self;
while Result.Parent <> nil do
Result := Result.Parent;
end;
function TfrxXMLItem.GetProp(Index: String): String;
var
i: Integer;
begin
i := Pos(' ' + frxUppercase(Index) + '="', frxUppercase(' ' + FText));
if i <> 0 then
begin
Result := Copy(FText, i + Length(Index + '="'), MaxInt);
Result := frxXMLToStr(Copy(Result, 1, Pos('"', Result) - 1));
end
else
Result := '';
end;
procedure TfrxXMLItem.SetProp(Index: String; const Value: String);
var
i, j: Integer;
s: String;
begin
i := Pos(' ' + frxUppercase(Index) + '="', frxUppercase(' ' + FText));
if i <> 0 then
begin
j := i + Length(Index + '="');
while (j <= Length(FText)) and (FText[j] <> '"') do
Inc(j);
Delete(FText, i, j - i + 1);
end
else
i := Length(FText) + 1;
s := Index + '="' + frxStrToXML(Value) + '"';
if (i > 1) and (FText[i - 1] <> ' ') then
s := ' ' + s;
Insert(s, FText, i);
end;
function TfrxXMLItem.PropExists(const Index: String): Boolean;
begin
Result := Pos(' ' + frxUppercase(Index) + '="', ' ' + frxUppercase(FText)) > 0;
end;
procedure TfrxXMLItem.DeleteProp(const Index: String);
var
i: Integer;
begin
i := Pos(' ' + frxUppercase(Index) + '="', ' ' + frxUppercase(FText));
if i > 0 then
begin
SetProp(Index, '');
Delete(FText, i, Length(Index) + 4);
end;
end;
function TfrxXMLItem.IndexOf(Item: TfrxXMLItem): Integer;
begin
Result := FItems.IndexOf(Item);
end;
{ TfrxXMLDocument }
constructor TfrxXMLDocument.Create;
begin
FRoot := TfrxXMLItem.Create;
end;
destructor TfrxXMLDocument.Destroy;
begin
DeleteTempFile;
FRoot.Free;
inherited;
end;
procedure TfrxXMLDocument.Clear;
begin
FRoot.Clear;
DeleteTempFile;
end;
procedure TfrxXMLDocument.CreateTempFile;
begin
if FTempFileCreated then Exit;
FTempFile := TPath.GetTempFileName;
FTempStream := TFileStream.Create(FTempFile, fmOpenReadWrite);
FTempFileCreated := True;
end;
procedure TfrxXMLDocument.DeleteTempFile;
begin
if FTempFileCreated then
begin
FTempStream.Free;
FTempStream := nil;
System.SysUtils.DeleteFile(FTempFile);
FTempFileCreated := False;
end;
if FTempStream <> nil then
FTempStream.Free;
FTempStream := nil;
end;
procedure TfrxXMLDocument.LoadItem(Item: TfrxXMLItem);
var
rd: TfrxXMLReader;
Text: String;
begin
if (FTempStream = nil) or Item.FLoaded or not Item.FUnloadable then Exit;
rd := TfrxXMLReader.Create(FTempStream);
try
rd.Position := Item.Offset;
Text := Item.Text;
rd.ReadRootItem(Item);
Item.Text := Text;
Item.FLoaded := True;
finally
rd.Free;
end;
end;
procedure TfrxXMLDocument.UnloadItem(Item: TfrxXMLItem);
var
wr: TfrxXMLWriter;
begin
if not Item.FLoaded or not Item.FUnloadable then Exit;
CreateTempFile;
FTempStream.Position := FTempStream.Size;
wr := TfrxXMLWriter.Create(FTempStream);
try
Item.Offset := FTempStream.Size;
wr.WriteRootItem(Item);
Item.Clear;
finally
wr.Free;
end;
end;
procedure TfrxXMLDocument.LoadFromStream(Stream: TStream;
AllowPartialLoading: Boolean = False);
var
rd: TfrxXMLReader;
begin
DeleteTempFile;
rd := TfrxXMLReader.Create(Stream);
try
FRoot.Clear;
FRoot.Offset := 0;
rd.ReadHeader;
FOldVersion := rd.FOldFormat;
rd.ReadRootItem(FRoot, not AllowPartialLoading);
finally
rd.Free;
end;
if AllowPartialLoading then
FTempStream := Stream else
FTempStream := nil;
end;
procedure TfrxXMLDocument.SaveToStream(Stream: TStream);
var
wr: TfrxXMLWriter;
begin
wr := TfrxXMLWriter.Create(Stream);
wr.TempStream := FTempStream;
wr.FAutoIndent := FAutoIndent;
try
wr.WriteHeader;
wr.WriteRootItem(FRoot);
finally
wr.Free;
end;
end;
procedure TfrxXMLDocument.LoadFromFile(const FileName: String);
var
s: TFileStream;
begin
s := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
LoadFromStream(s, True);
end;
procedure TfrxXMLDocument.SaveToFile(const FileName: String);
var
s: TFileStream;
begin
s := TFileStream.Create(FileName + '.tmp', fmCreate);
try
SaveToStream(s);
finally
s.Free;
end;
DeleteTempFile;
System.SysUtils.DeleteFile(FileName);
RenameFile(FileName + '.tmp', FileName);
LoadFromFile(FileName);
end;
{ TfrxXMLReader }
constructor TfrxXMLReader.Create(Stream: TStream);
begin
FStream := Stream;
FSize := Stream.Size;
FPosition := Stream.Position;
GetMem(FBuffer, 4096);
end;
destructor TfrxXMLReader.Destroy;
begin
FreeMem(FBuffer, 4096);
FStream.Position := FPosition;
inherited;
end;
procedure TfrxXMLReader.ReadBuffer;
begin
FBufEnd := FStream.Read(FBuffer^, 4096);
FBufPos := 0;
end;
procedure TfrxXMLReader.SetPosition(const Value: Int64);
begin
FPosition := Value;
FStream.Position := Value;
FBufPos := 0;
FBufEnd := 0;
end;
procedure TfrxXMLReader.RaiseException;
begin
raise TfrxInvalidXMLException.Create('Invalid file format');
end;
procedure TfrxXMLReader.ReadHeader;
var
s1, s2: String;
i: Integer;
Ver: String;
begin
ReadItem(s1, s2);
if Pos('?xml', s1) <> 1 then
RaiseException;
i := Pos('version=', s2);
if i <> 0 then
Ver := Copy(s2, i + 9, 3);
i := Pos('standalone=', s2);
if (Ver = '1.0') and (i = 0) then
FOldFormat := True;
end;
procedure TfrxXMLReader.ReadItem(var NameS, Text: String);
var
c: Integer;
curpos, len: Integer;
state: (FindLeft, FindRight, FindComment, Done);
i, comment: Integer;
ps: PAnsiChar;
Name: AnsiString;
begin
Text := '';
comment := 0;
state := FindLeft;
curpos := 0;
len := 4096;
SetLength(Name, len);
ps := @Name[1];
while FPosition < FSize do
begin
if FBufPos = FBufEnd then
ReadBuffer;
c := Ord(FBuffer[FBufPos]);
Inc(FBufPos);
Inc(FPosition);
if state = FindLeft then
begin
if c = Ord('<') then
state := FindRight
end
else if state = FindRight then
begin
if c = Ord('>') then
begin
state := Done;
break;
end
else if c = Ord('<') then
RaiseException
else
begin
ps[curpos] := AnsiChar(Chr(c));
Inc(curpos);
if (curpos = 3) and (Pos(AnsiString('!--'), Name) = 1) then
begin
state := FindComment;
comment := 0;
curpos := 0;
end;
if curpos >= len - 1 then
begin
Inc(len, 4096);
SetLength(Name, len);
ps := @Name[1];
end;
end;
end
else if State = FindComment then
begin
if comment = 2 then
begin
if c = Ord('>') then
state := FindLeft
else
comment := 0;
end
else begin
if c = Ord('-') then
Inc(comment)
else
comment := 0;
end;
end;
end;
len := curpos;
SetLength(Name, len);
if state = FindRight then
RaiseException;
if (Name <> '') and (Name[len] = ' ') then
SetLength(Name, len - 1);
i := Pos(AnsiString(' '), Name);
if i <> 0 then
begin
if FOldFormat then
Text := String(Copy(Name, i + 1, len - i)) else
Text := UTF8Decode(Copy(Name, i + 1, len - i));
Delete(Name, i, len - i + 1);
end;
NameS := String(Name);
end;
procedure TfrxXMLReader.ReadRootItem(Item: TfrxXMLItem; ReadChildren: Boolean = True);
var
LastName: String;
function DoRead(RootItem: TfrxXMLItem): Boolean;
var
n: Integer;
ChildItem: TfrxXMLItem;
Done: Boolean;
CurPos: Int64;
begin
Result := False;
CurPos := Position;
ReadItem(RootItem.FName, RootItem.FText);
LastName := RootItem.FName;
if (RootItem.Name = '') or (RootItem.Name[1] = '/') then
begin
Result := True;
Exit;
end;
n := Length(RootItem.Name);
if RootItem.Name[n] = '/' then
begin
SetLength(RootItem.FName, n - 1);
Exit;
end;
n := Length(RootItem.Text);
if (n > 0) and (RootItem.Text[n] = '/') then
begin
SetLength(RootItem.FText, n - 1);
Exit;
end;
repeat
ChildItem := TfrxXMLItem.Create;
Done := DoRead(ChildItem);
if not Done then
RootItem.AddItem(ChildItem) else
ChildItem.Free;
until Done;
if (LastName <> '') and (CompareText(LastName, '/' + RootItem.Name) <> 0) then
RaiseException;
n := Pos(' ld="0"', LowerCase(RootItem.Text));
if n <> 0 then
Delete(RootItem.FText, n, 7);
if not ReadChildren and (n <> 0) then
begin
RootItem.Clear;
RootItem.Offset := CurPos;
RootItem.FUnloadable := True;
RootItem.FLoaded := False;
end;
end;
begin
DoRead(Item);
end;
{ TfrxXMLWriter }
constructor TfrxXMLWriter.Create(Stream: TStream);
begin
FStream := Stream;
end;
procedure TfrxXMLWriter.FlushBuffer;
begin
if FBuffer <> '' then
FStream.Write(FBuffer[1], Length(FBuffer));
FBuffer := '';
end;
procedure TfrxXMLWriter.WriteLn(const s: AnsiString);
begin
if not FAutoIndent then
Insert(s, FBuffer, MaxInt) else
Insert(s + #13#10, FBuffer, MaxInt);
if Length(FBuffer) > 4096 then
FlushBuffer;
end;
procedure TfrxXMLWriter.WriteHeader;
begin
WriteLn('<?xml version="1.0" encoding="utf-8" standalone="no"?>');
end;
function Dup(n: Integer): AnsiString;
begin
SetLength(Result, n);
FillChar(Result[1], n, ' ');
end;
procedure TfrxXMLWriter.WriteItem(Item: TfrxXMLItem; Level: Integer = 0);
var
s: AnsiString;
begin
if (Item.FText <> '') or Item.FUnloadable then
begin
s := UTF8Encode(Item.FText);
if (s = '') or (s[1] <> ' ') then
s := ' ' + s;
if Item.FUnloadable then
s := s + 'ld="0"';
end
else
s := '';
if Item.Count = 0 then
begin
if Item.Value = '' then
s := s + '/>'
else
s := s + '>' + UTF8Encode(Item.Value) + '</' + AnsiString(Item.Name) + '>'
end
else
s := s + '>';
if not FAutoIndent then
s := '<' + AnsiString(Item.Name) + s else
s := Dup(Level) + '<' + AnsiString(Item.Name) + s;
WriteLn(s);
end;
procedure TfrxXMLWriter.WriteRootItem(RootItem: TfrxXMLItem);
procedure DoWrite(RootItem: TfrxXMLItem; Level: Integer = 0);
var
i: Integer;
rd: TfrxXMLReader;
NeedClear: Boolean;
begin
NeedClear := False;
if not FAutoIndent then
Level := 0;
if (FTempStream <> nil) and RootItem.FUnloadable and not RootItem.FLoaded then
begin
rd := TfrxXMLReader.Create(FTempStream);
try
rd.Position := RootItem.Offset;
rd.ReadRootItem(RootItem);
NeedClear := True;
finally
rd.Free;
end;
end;
WriteItem(RootItem, Level);
for i := 0 to RootItem.Count - 1 do
DoWrite(RootItem[i], Level + 2);
if RootItem.Count > 0 then
if not FAutoIndent then
WriteLn('</' + AnsiString(RootItem.Name) + '>') else
WriteLn(Dup(Level) + '</' + AnsiString(RootItem.Name) + '>');
if NeedClear then
RootItem.Clear;
end;
begin
DoWrite(RootItem);
FlushBuffer;
end;
end.