{ ********************************************************************** This file is part of LazUtils. It is based on the FCL unit xmlwrite svn revision 15251. See the file COPYING.FPC, included in this distribution, for details about the license. ********************************************************************** XML writing routines Copyright (c) 1999-2000 by Sebastian Guenther, sg@freepascal.org Modified in 2006 by Sergei Gorelkin, sergei_gorelkin@mail.ru Converted to use UTF8 instead of widestrings by Mattias Gaertner. } unit Laz2_XMLWrite; {$ifdef fpc}{$MODE objfpc}{$endif} {$H+} {$DEFINE UseUTF8} {off $DEFINE UseWideString} interface uses Classes, SysUtils, LazUTF8, Laz2_DOM, Laz2_XMLUtils; type TXMLWriterFlag = ( xwfAllowNullCharsInAttributeValue, // Do not throw an exception, when #0 is written in an attribute value (always encoded as � xwfSpecialCharsInAttributeValue, // write #01..#31 as it is (just the byte itself) instead of as &xD; xwfPreserveWhiteSpace ); TXMLWriterFlags = set of TXMLWriterFlag; procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String; Flags: TXMLWriterFlags = []); overload; procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text; Flags: TXMLWriterFlags = []); overload; procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream; Flags: TXMLWriterFlags = []); overload; procedure WriteXML(Element: TDOMNode; const AFileName: String; Flags: TXMLWriterFlags = []); overload; procedure WriteXML(Element: TDOMNode; var AFile: Text; Flags: TXMLWriterFlags = []); overload; procedure WriteXML(Element: TDOMNode; AStream: TStream; Flags: TXMLWriterFlags = []); overload; // =================================================================== implementation type TXMLWriter = class; TSpecialCharCallback = procedure(Sender: TXMLWriter; const s: DOMString; var idx: Integer); { EXMLWriteError } EXMLWriteError = class(Exception) private FErrorMessage: string; FNode: TDOMNode; public constructor Create(const msg: string; ANode: TDOMNode); property ErrorMessage: string read FErrorMessage; property Node: TDOMNode read FNode; end; PAttrFixup = ^TAttrFixup; TAttrFixup = record Attr: TDOMNode; Prefix: PHashItem; end; { TXMLWriter } TXMLWriter = class(TObject) private FInsideTextNode: Boolean; FCanonical: Boolean; FIndent: DOMString; FIndentCount: Integer; FBuffer: PChar; FBufPos: PChar; FCapacity: Integer; FLineBreak: DOMString; FNSHelper: TNSSupport; FAttrFixups: TFPList; FScratch: TFPList; FNSDefs: TFPList; FWriteFlags: TXMLWriterFlags; FCurrentNode: TDOMNode; procedure wrtChars(Src: DOMPChar; Length: Integer); procedure IncIndent; procedure DecIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF} procedure wrtStr(const ws: DOMString); {$IFDEF HAS_INLINE} inline; {$ENDIF} procedure wrtChr(c: DOMChar); {$IFDEF HAS_INLINE} inline; {$ENDIF} procedure wrtIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF} procedure wrtQuotedLiteral(const ws: DOMString); procedure ConvWrite(const s: DOMString; const SpecialChars: TSetOfChar; const SpecialCharCallback: TSpecialCharCallback); procedure WriteNSDef(B: TBinding); procedure NamespaceFixup(Element: TDOMElement); protected procedure Write(const Buffer; Count: Longint); virtual; abstract; procedure WriteNode(Node: TDOMNode); procedure VisitDocument(Node: TDOMNode); procedure VisitDocument_Canonical(Node: TDOMNode); procedure VisitElement(Node: TDOMNode); procedure VisitText(Node: TDOMNode); procedure VisitCDATA(Node: TDOMNode); procedure VisitComment(Node: TDOMNode); procedure VisitFragment(Node: TDOMNode); procedure VisitAttribute(Node: TDOMNode); procedure VisitEntityRef(Node: TDOMNode); procedure VisitDocumentType(Node: TDOMNode); procedure VisitPI(Node: TDOMNode); public constructor Create; destructor Destroy; override; property WriteFlags: TXMLWriterFlags read FWriteFlags write FWriteFlags; end; TTextXMLWriter = Class(TXMLWriter) Private F : ^Text; Protected Procedure Write(Const Buffer; Count : Longint);override; Public constructor Create(var AFile: Text); end; TStreamXMLWriter = Class(TXMLWriter) Private F : TStream; Protected Procedure Write(Const Buffer; Count : Longint);override; Public constructor Create(AStream: TStream); end; { EXMLWriteError } constructor EXMLWriteError.Create(const msg: string; ANode: TDOMNode); begin inherited Create(msg); FNode := ANode; end; { --------------------------------------------------------------------- TTextXMLWriter ---------------------------------------------------------------------} constructor TTextXMLWriter.Create(var AFile: Text); begin inherited Create; f := @AFile; end; procedure TTextXMLWriter.Write(const Buffer; Count: Longint); var s: string; begin if Count>0 then begin SetString(s, PChar(@Buffer), Count); system.Write(f^, s); end; end; { --------------------------------------------------------------------- TStreamXMLWriter ---------------------------------------------------------------------} constructor TStreamXMLWriter.Create(AStream: TStream); begin inherited Create; F := AStream; end; procedure TStreamXMLWriter.Write(const Buffer; Count: Longint); begin if Count > 0 then F.Write(Buffer, Count); end; { --------------------------------------------------------------------- TXMLWriter ---------------------------------------------------------------------} const AttrSpecialChars : array[boolean] of TSetOfChar = ( ['<', '"', '''', '&', #0..#31], // false: default ['<', '"', '''', '&', #0] // true: write special characters ); TextSpecialChars = ['<', '>', '&', #0..#31]; CDSectSpecialChars = [']']; LineEndingChars = [#13, #10]; QuotStr = '"'; AposStr = '''; AmpStr = '&'; ltStr = '<'; gtStr = '>'; HexChr: PChar = '0123456789ABCDEF'; constructor TXMLWriter.Create; var I: Integer; begin inherited Create; // some overhead - always be able to write at least one extra UCS4 FBuffer := AllocMem(512+32); FBufPos := FBuffer; FCapacity := 512; // Later on, this may be put under user control // for now, take OS setting if FCanonical then FLineBreak := #10 else FLineBreak := sLineBreak; // Initialize Indent string // TODO: this must be done in setter of FLineBreak SetLength(FIndent, 100); FIndent[1] := FLineBreak[1]; if Length(FLineBreak) > 1 then FIndent[2] := FLineBreak[2] else FIndent[2] := ' '; for I := 3 to 100 do FIndent[I] := ' '; FIndentCount := 0; FNSHelper := TNSSupport.Create; FScratch := TFPList.Create; FNSDefs := TFPList.Create; FAttrFixups := TFPList.Create; end; destructor TXMLWriter.Destroy; var I: Integer; begin for I := FAttrFixups.Count-1 downto 0 do Dispose(PAttrFixup(FAttrFixups.List^[I])); FAttrFixups.Free; FNSDefs.Free; FScratch.Free; FNSHelper.Free; if FBufPos > FBuffer then write(FBuffer^, FBufPos-FBuffer); FreeMem(FBuffer); inherited Destroy; end; procedure TXMLWriter.wrtChars(Src: DOMPChar; Length: Integer); var pb: PChar; wc: Cardinal; SrcEnd: DOMPChar; begin pb := FBufPos; SrcEnd := Src + Length; while Src < SrcEnd do begin if pb >= @FBuffer[FCapacity] then begin write(FBuffer^, FCapacity); Dec(pb, FCapacity); if pb > FBuffer then Move(FBuffer[FCapacity], FBuffer^, pb - FBuffer); end; wc := Cardinal(Src^); Inc(Src); {$IFDEF UseUTF8} pb^ := char(wc); Inc(pb); {$ENDIF} {$IFDEF UseWideString} case wc of 0..$7F: begin pb^ := char(wc); Inc(pb); end; $80..$7FF: begin pb^ := Char($C0 or (wc shr 6)); pb[1] := Char($80 or (wc and $3F)); Inc(pb,2); end; $D800..$DBFF: begin if (Src < SrcEnd) and (Src^ >= #$DC00) and (Src^ <= #$DFFF) then begin wc := ((LongInt(wc) - $D7C0) shl 10) + LongInt(word(Src^) xor $DC00); Inc(Src); pb^ := Char($F0 or (wc shr 18)); pb[1] := Char($80 or ((wc shr 12) and $3F)); pb[2] := Char($80 or ((wc shr 6) and $3F)); pb[3] := Char($80 or (wc and $3F)); Inc(pb,4); end else raise EConvertError.Create('High surrogate without low one'); end; $DC00..$DFFF: raise EConvertError.Create('Low surrogate without high one'); else // $800 >= wc > $FFFF, excluding surrogates begin pb^ := Char($E0 or (wc shr 12)); pb[1] := Char($80 or ((wc shr 6) and $3F)); pb[2] := Char($80 or (wc and $3F)); Inc(pb,3); end; end; {$ENDIF UseWideString} end; FBufPos := pb; end; procedure TXMLWriter.wrtStr(const ws: DOMString); { inline } begin wrtChars(DOMPChar(ws), Length(ws)); end; { No checks here - buffer always has 32 extra bytes } procedure TXMLWriter.wrtChr(c: DOMChar); { inline } begin FBufPos^ := char(ord(c)); Inc(FBufPos); end; procedure TXMLWriter.wrtIndent; { inline } begin wrtChars(DOMPChar(FIndent), FIndentCount*2+Length(FLineBreak)); end; procedure TXMLWriter.IncIndent; var I, NewLen, OldLen: Integer; begin Inc(FIndentCount); if Length(FIndent) < 2 * FIndentCount then begin OldLen := Length(FIndent); NewLen := 4 * FIndentCount; SetLength(FIndent, NewLen); for I := OldLen to NewLen do FIndent[I] := ' '; end; end; procedure TXMLWriter.DecIndent; { inline } begin if FIndentCount>0 then dec(FIndentCount); end; procedure TXMLWriter.ConvWrite(const s: DOMString; const SpecialChars: TSetOfChar; const SpecialCharCallback: TSpecialCharCallback); var StartPos, EndPos: Integer; begin StartPos := 1; EndPos := 1; while EndPos <= Length(s) do begin if (s[EndPos] < #128) and (Char(ord(s[EndPos])) in SpecialChars) then begin wrtChars(@s[StartPos], EndPos - StartPos); SpecialCharCallback(Self, s, EndPos); StartPos := EndPos + 1; end; Inc(EndPos); end; if StartPos <= length(s) then wrtChars(@s[StartPos], EndPos - StartPos); end; procedure AttrSpecialCharCallback(Sender: TXMLWriter; const s: DOMString; var idx: Integer); begin case s[idx] of '"': Sender.wrtStr(QuotStr); '''': Sender.wrtStr(AposStr); '&': Sender.wrtStr(AmpStr); '<': Sender.wrtStr(ltStr); // Escape whitespace using CharRefs to be consistent with W3 spec ยง 3.3.3 #0: if xwfAllowNullCharsInAttributeValue in Sender.FWriteFlags then Sender.wrtStr('&#x'+HexChr[ord(s[idx])]+';') else raise EXMLWriteError.Create('Null not allowed here', Sender.FCurrentNode); #1..#15: Sender.wrtStr('&#x'+HexChr[ord(s[idx])]+';'); #16..#31: Sender.wrtStr(''+HexChr[ord(s[idx])-16]+';'); else Sender.wrtChr(s[idx]); end; end; procedure TextnodeNormalCallback(Sender: TXMLWriter; const s: DOMString; var idx: Integer); begin case s[idx] of '<': Sender.wrtStr(ltStr); '>': Sender.wrtStr(gtStr); // Required only in ']]>' literal, otherwise optional '&': Sender.wrtStr(AmpStr); #13: begin // We normalize #13#10 and #13 to FLineBreak, going somewhat // beyond the specs here, see issue #13879. Sender.wrtStr(Sender.FLineBreak); if (idx < Length(s)) and (s[idx+1] = #10) then Inc(idx); end; #10: Sender.wrtStr(Sender.FLineBreak); #0..#9,#11..#12,#14..#15: Sender.wrtStr('&#x'+HexChr[ord(s[idx])]+';'); #16..#31: Sender.wrtStr(''+HexChr[ord(s[idx])-16]+';'); else Sender.wrtChr(s[idx]); end; end; procedure TextnodeCanonicalCallback(Sender: TXMLWriter; const s: DOMString; var idx: Integer); begin case s[idx] of '<': Sender.wrtStr(ltStr); '>': Sender.wrtStr(gtStr); '&': Sender.wrtStr(AmpStr); #0..#15: Sender.wrtStr('&#x'+HexChr[ord(s[idx])]+';'); #16..#31: Sender.wrtStr(''+HexChr[ord(s[idx])-16]+';'); else Sender.wrtChr(s[idx]); end; end; procedure CDSectSpecialCharCallback(Sender: TXMLWriter; const s: DOMString; var idx: Integer); begin if (idx <= Length(s)-2) and (s[idx+1] = ']') and (s[idx+2] = '>') then begin Sender.wrtStr(']]]]>'); Inc(idx, 2); // TODO: emit warning 'cdata-section-splitted' end else Sender.wrtChr(s[idx]); end; const TextnodeCallbacks: array[boolean] of TSpecialCharCallback = ( @TextnodeNormalCallback, @TextnodeCanonicalCallback ); procedure TXMLWriter.wrtQuotedLiteral(const ws: DOMString); var Quote: DOMChar; begin // TODO: need to check if the string also contains single quote // both quotes present is a error if Pos('"', ws) > 0 then Quote := '''' else Quote := '"'; wrtChr(Quote); ConvWrite(ws, LineEndingChars, @TextnodeNormalCallback); wrtChr(Quote); end; procedure TXMLWriter.WriteNode(node: TDOMNode); begin FCurrentNode := Node; case node.NodeType of ELEMENT_NODE: VisitElement(node); ATTRIBUTE_NODE: VisitAttribute(node); TEXT_NODE: VisitText(node); CDATA_SECTION_NODE: VisitCDATA(node); ENTITY_REFERENCE_NODE: VisitEntityRef(node); PROCESSING_INSTRUCTION_NODE: VisitPI(node); COMMENT_NODE: VisitComment(node); DOCUMENT_NODE: if FCanonical then VisitDocument_Canonical(node) else VisitDocument(node); DOCUMENT_TYPE_NODE: VisitDocumentType(node); ENTITY_NODE, DOCUMENT_FRAGMENT_NODE: VisitFragment(node); end; end; procedure TXMLWriter.WriteNSDef(B: TBinding); begin wrtChars(' xmlns', 6); if B.Prefix^.Key <> '' then begin wrtChr(':'); wrtStr(B.Prefix^.Key); end; wrtChars('="', 2); ConvWrite(B.uri, AttrSpecialChars[xwfSpecialCharsInAttributeValue in FWriteFlags], @AttrSpecialCharCallback); wrtChr('"'); end; function Compare(const s1, s2: DOMString): integer; var maxi, temp: integer; begin Result := 0; if pointer(S1) = pointer(S2) then exit; maxi := Length(S1); temp := Length(S2); if maxi > temp then maxi := temp; Result := CompareWord(S1[1], S2[1], maxi); if Result = 0 then Result := Length(S1)-Length(S2); end; function SortNSDefs(Item1, Item2: Pointer): Integer; begin Result := Compare(TBinding(Item1).Prefix^.Key, TBinding(Item2).Prefix^.Key); end; function SortAtts(Item1, Item2: Pointer): Integer; var p1: PAttrFixup absolute Item1; p2: PAttrFixup absolute Item2; s1, s2: DOMString; begin Result := Compare(p1^.Attr.namespaceURI, p2^.Attr.namespaceURI); if Result = 0 then begin // TODO: Must fix the parser so it doesn't produce Level 1 attributes if nfLevel2 in p1^.Attr.Flags then s1 := p1^.Attr.localName else s1 := p1^.Attr.nodeName; if nfLevel2 in p2^.Attr.Flags then s2 := p2^.Attr.localName else s2 := p2^.Attr.nodeName; Result := Compare(s1, s2); end; end; procedure TXMLWriter.NamespaceFixup(Element: TDOMElement); var B: TBinding; i, j: Integer; node: TDOMNode; s: DOMString; action: TAttributeAction; p: PAttrFixup; begin FScratch.Count := 0; FNSDefs.Count := 0; if Element.hasAttributes then begin j := 0; for i := 0 to Element.Attributes.Length-1 do begin node := Element.Attributes[i]; if TDOMNode_NS(node).NSI.NSIndex = 2 then begin if TDOMNode_NS(node).NSI.PrefixLen = 0 then s := '' else s := node.localName; FNSHelper.DefineBinding(s, node.nodeValue, B); if Assigned(B) then // drop redundant namespace declarations FNSDefs.Add(B); end else if FCanonical or TDOMAttr(node).Specified then begin // obtain a TAttrFixup record (allocate if needed) if j >= FAttrFixups.Count then begin New(p); FAttrFixups.Add(p); end else p := PAttrFixup(FAttrFixups.List^[j]); // add it to the working list p^.Attr := node; p^.Prefix := nil; FScratch.Add(p); Inc(j); end; end; end; FNSHelper.DefineBinding(Element.Prefix, Element.namespaceURI, B); if Assigned(B) then FNSDefs.Add(B); for i := 0 to FScratch.Count-1 do begin node := PAttrFixup(FScratch.List^[i])^.Attr; action := FNSHelper.CheckAttribute(node.Prefix, node.namespaceURI, B); if action = aaBoth then FNSDefs.Add(B); if action in [aaPrefix, aaBoth] then PAttrFixup(FScratch.List^[i])^.Prefix := B.Prefix; end; if FCanonical then begin FNSDefs.Sort(@SortNSDefs); FScratch.Sort(@SortAtts); end; // now, at last, dump all this stuff. for i := 0 to FNSDefs.Count-1 do WriteNSDef(TBinding(FNSDefs.List^[I])); for i := 0 to FScratch.Count-1 do begin wrtChr(' '); with PAttrFixup(FScratch.List^[I])^ do begin if Assigned(Prefix) then begin wrtStr(Prefix^.Key); wrtChr(':'); wrtStr(Attr.localName); end else wrtStr(Attr.nodeName); wrtChars('="', 2); // TODO: not correct w.r.t. entities ConvWrite(attr.nodeValue, AttrSpecialChars[xwfSpecialCharsInAttributeValue in FWriteFlags], @AttrSpecialCharCallback); wrtChr('"'); end; end; end; procedure TXMLWriter.VisitElement(node: TDOMNode); var i: Integer; child: TDOMNode; SavedInsideTextNode: Boolean; begin //writeln(Space(FIndentCount*2),'TXMLWriter.VisitElement ',TDOMElement(node).TagName,' FInsideTextNode=',FInsideTextNode); if not FInsideTextNode then wrtIndent; FNSHelper.StartElement; wrtChr('<'); wrtStr(TDOMElement(node).TagName); if nfLevel2 in node.Flags then NamespaceFixup(TDOMElement(node)) else if node.HasAttributes then for i := 0 to node.Attributes.Length - 1 do begin child := node.Attributes.Item[i]; if FCanonical or TDOMAttr(child).Specified then VisitAttribute(child); end; Child := node.FirstChild; if Child = nil then wrtChars('/>', 2) else begin SavedInsideTextNode := FInsideTextNode; wrtChr('>'); FInsideTextNode := FCanonical or (Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]) or (xwfPreserveWhiteSpace in WriteFlags); //writeln(Space(FIndentCount*2),'TXMLWriter.VisitElement START FirstChild=',Child.ClassName,':',Child.LocalName,' FInsideTextNode=',FInsideTextNode); IncIndent; repeat //writeln(Space(FIndentCount*2),'TXMLWriter.VisitElement CHILD=',Child.ClassName,':',Child.LocalName,' FInsideTextNode=',FInsideTextNode); WriteNode(Child); FInsideTextNode := FCanonical or (Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]) or (xwfPreserveWhiteSpace in WriteFlags); Child := Child.NextSibling; until Child = nil; DecIndent; if not (node.LastChild.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]) then wrtIndent; FInsideTextNode := SavedInsideTextNode; //writeln(Space(FIndentCount*2),'TXMLWriter.VisitElement END Node=',Node.ClassName,':',Node.LocalName,' FInsideTextNode=',FInsideTextNode); wrtChars(''); end; FNSHelper.EndElement; end; procedure TXMLWriter.VisitText(node: TDOMNode); begin ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, TextnodeCallbacks[FCanonical]); end; procedure TXMLWriter.VisitCDATA(node: TDOMNode); begin if not FInsideTextNode then wrtIndent; if FCanonical then ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeCanonicalCallback) else begin wrtChars('', 3); end; end; procedure TXMLWriter.VisitEntityRef(node: TDOMNode); begin wrtChr('&'); wrtStr(node.NodeName); wrtChr(';'); end; procedure TXMLWriter.VisitPI(node: TDOMNode); begin if not FInsideTextNode then wrtIndent; wrtStr(' '' then begin wrtChr(' '); // TODO: How does this comply with c14n?? ConvWrite(TDOMProcessingInstruction(node).Data, LineEndingChars, @TextnodeNormalCallback); end; wrtStr('?>'); end; procedure TXMLWriter.VisitComment(node: TDOMNode); begin if not FInsideTextNode then wrtIndent; wrtChars('', 3); end; procedure TXMLWriter.VisitDocument(node: TDOMNode); var child: TDOMNode; begin wrtStr(' 0 then wrtStr(TXMLDocument(node).XMLVersion) else wrtStr('1.0'); wrtChr('"'); // DISABLED - we are only able write in UTF-8 which does not require labeling // writing incorrect encoding will render xml unreadable... (* if Length(TXMLDocument(node).Encoding) > 0 then begin wrtStr(' encoding="'); wrtStr(TXMLDocument(node).Encoding); wrtChr('"'); end; *) wrtStr(' encoding="UTF-8"'); wrtStr('?>'); // TODO: now handled as a regular PI, remove this? if Length(TXMLDocument(node).StylesheetType) > 0 then begin wrtStr(FLineBreak); wrtStr(''); end; child := node.FirstChild; while Assigned(Child) do begin WriteNode(Child); Child := Child.NextSibling; end; wrtStr(FLineBreak); end; procedure TXMLWriter.VisitDocument_Canonical(Node: TDOMNode); var child, root: TDOMNode; begin root := TDOMDocument(Node).DocumentElement; child := node.FirstChild; while Assigned(child) and (child <> root) do begin if child.nodeType in [COMMENT_NODE, PROCESSING_INSTRUCTION_NODE] then begin WriteNode(child); wrtChr(#10); end; child := child.nextSibling; end; if root = nil then Exit; VisitElement(TDOMElement(root)); child := root.nextSibling; while Assigned(child) do begin if child.nodeType in [COMMENT_NODE, PROCESSING_INSTRUCTION_NODE] then begin wrtChr(#10); WriteNode(child); end; child := child.nextSibling; end; end; procedure TXMLWriter.VisitAttribute(Node: TDOMNode); var Child: TDOMNode; begin wrtChr(' '); wrtStr(TDOMAttr(Node).Name); wrtChars('="', 2); Child := Node.FirstChild; while Assigned(Child) do begin //writeln('TXMLWriter.VisitAttribute ',Child.NodeType); case Child.NodeType of ENTITY_REFERENCE_NODE: VisitEntityRef(Child); TEXT_NODE: ConvWrite(TDOMCharacterData(Child).Data, AttrSpecialChars[xwfSpecialCharsInAttributeValue in FWriteFlags], @AttrSpecialCharCallback); end; Child := Child.NextSibling; end; wrtChr('"'); end; procedure TXMLWriter.VisitDocumentType(Node: TDOMNode); begin wrtStr(FLineBreak); wrtStr(' '' then begin wrtStr('PUBLIC '); wrtQuotedLiteral(PublicID); wrtChr(' '); wrtQuotedLiteral(SystemID); end else if SystemID <> '' then begin wrtStr('SYSTEM '); wrtQuotedLiteral(SystemID); end; if InternalSubset <> '' then begin wrtChr('['); ConvWrite(InternalSubset, LineEndingChars, @TextnodeNormalCallback); wrtChr(']'); end; end; wrtChr('>'); end; procedure TXMLWriter.VisitFragment(Node: TDOMNode); var Child: TDOMNode; begin // TODO: TextDecl is probably needed // Fragment itself should not be written, only its children should... Child := Node.FirstChild; while Assigned(Child) do begin WriteNode(Child); Child := Child.NextSibling; end; end; // ------------------------------------------------------------------- // Interface implementation // ------------------------------------------------------------------- procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String; Flags: TXMLWriterFlags = []); var fs: TFileStream; begin fs := TFileStream.Create(AFileName, fmCreate); try WriteXMLFile(doc, fs, Flags); finally fs.Free; end; end; procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text; Flags: TXMLWriterFlags = []); begin with TTextXMLWriter.Create(AFile) do try WriteFlags:=Flags; WriteNode(doc); finally Free; end; end; procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream; Flags: TXMLWriterFlags = []); begin with TStreamXMLWriter.Create(AStream) do try WriteFlags:=Flags; WriteNode(doc); finally Free; end; end; procedure WriteXML(Element: TDOMNode; const AFileName: String; Flags: TXMLWriterFlags = []); begin WriteXMLFile(TXMLDocument(Element), AFileName, Flags); end; procedure WriteXML(Element: TDOMNode; var AFile: Text; Flags: TXMLWriterFlags = []); begin WriteXMLFile(TXMLDocument(Element), AFile, Flags); end; procedure WriteXML(Element: TDOMNode; AStream: TStream; Flags: TXMLWriterFlags = []); begin WriteXMLFile(TXMLDocument(Element), AStream, Flags); end; end.