{ This file is part of the Free Component Library XML writing routines Copyright (c) 1999-2000 by Sebastian Guenther, sg@freepascal.org Modified in 2006 by Sergei Gorelkin, sergei_gorelkin@mail.ru See the file COPYING.FPC, included in this distribution, for details about the copyright. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. **********************************************************************} unit XMLWrite; {$ifdef fpc}{$MODE objfpc}{$endif} {$H+} interface uses Classes, DOM, xmlutils; Type TXMLWriter = Class; TSpecialCharCallback = procedure(Sender: TXMLWriter; const s: DOMString; var idx: Integer); TNodeInfo = record Name: XMLString; end; TNodeInfoArray = array of TNodeInfo; { TXMLWriter } TXMLWriter = class(TObject) private FIndentSize: Integer; FStream: TStream; FInsideTextNode: Boolean; FCanonical: Boolean; FIndent: XMLString; FNesting: Integer; FBuffer: PChar; FBufPos: PChar; FCapacity: Integer; FLineBreak: XMLString; FNSHelper: TNSSupport; FAttrFixups: TFPList; FScratch: TFPList; FNSDefs: TFPList; FNodes: TNodeInfoArray; FUseTab: Boolean; procedure SetCanonical(AValue: Boolean); procedure SetIndentSize(AValue: Integer); procedure SetLineBreak(AValue: XMLString); procedure SetUseTab(AValue: Boolean); procedure wrtChars(Src: PWideChar; Length: Integer); procedure IncNesting; procedure DecNesting; {$IFDEF HAS_INLINE} inline; {$ENDIF} procedure wrtStr(const ws: XMLString); {$IFDEF HAS_INLINE} inline; {$ENDIF} procedure wrtChr(c: WideChar); {$IFDEF HAS_INLINE} inline; {$ENDIF} procedure wrtIndent(EndElement: Boolean = False); procedure wrtQuotedLiteral(const ws: XMLString); procedure ConvWrite(const s: XMLString; const SpecialChars: TSetOfChar; const SpecialCharCallback: TSpecialCharCallback); procedure WriteNSDef(B: TBinding); protected Procedure InitIndentLineBreak; // Canonical does not yet quite work Property Canonical : Boolean Read FCanonical Write SetCanonical; public constructor Create(AStream: TStream; ANameTable: THashTable); destructor Destroy; override; procedure WriteXMLDecl(const aVersion, aEncoding: XMLString; aStandalone: Integer); virtual; procedure WriteStartElement(const Name: XMLString); virtual; procedure WriteEndElement(shortForm: Boolean); virtual; procedure WriteProcessingInstruction(const Target, Data: XMLString); virtual; procedure WriteEntityRef(const Name: XMLString); virtual; procedure WriteAttributeString(const Name, Value: XMLString); virtual; procedure WriteDocType(const Name, PubId, SysId, Subset: XMLString); virtual; procedure WriteString(const Text: XMLString); virtual; procedure WriteCDATA(const Text: XMLString); virtual; procedure WriteComment(const Text: XMLString); virtual; // Only set these before writing ! // Use tab character instead of space. Property UseTab : Boolean Read FUseTab Write SetUseTab; // Indent size in number of characters Property IndentSize : Integer Read FIndentSize Write SetIndentSize; // Default is system setting. Ignored when Canonical = True. Property LineBreak : XMLString Read FLineBreak Write SetLineBreak; end; { TDOMWriter } TDOMWriter = class(TXMLWriter) Protected procedure NamespaceFixup(Element: TDOMElement); procedure VisitDocument(Node: TDOMNode); procedure VisitDocument_Canonical(Node: TDOMNode); procedure VisitElement(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(AStream: TStream; aNode : TDOMNode); procedure WriteNode(Node: TDOMNode); end; procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String); overload; procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text); overload; procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream); overload; procedure WriteXML(Element: TDOMNode; const AFileName: String); overload; procedure WriteXML(Element: TDOMNode; var AFile: Text); overload; procedure WriteXML(Element: TDOMNode; AStream: TStream); overload; // =================================================================== implementation uses SysUtils; type PAttrFixup = ^TAttrFixup; TAttrFixup = record Attr: TDOMNode; Prefix: PHashItem; end; TTextStream = class(TStream) Private F : ^Text; Public constructor Create(var AFile: Text); function Write(Const Buffer; Count: Longint): Longint; override; end; { --------------------------------------------------------------------- TTextStream ---------------------------------------------------------------------} constructor TTextStream.Create(var AFile: Text); begin inherited Create; f := @AFile; end; function TTextStream.Write(const Buffer; Count: Longint): Longint; var s: string; begin if Count>0 then begin SetString(s, PChar(@Buffer), Count); system.Write(f^, s); end; Result := Count; end; { --------------------------------------------------------------------- Auxiliary routines ---------------------------------------------------------------------} const AttrSpecialChars = ['<', '>', '"', '&', #0..#$1F]; TextSpecialChars = ['<', '>', '&', #0..#8, #10..#$1F]; CDSectSpecialChars = [#0..#8, #11, #12, #14..#$1F, ']']; LineEndingChars = [#13, #10]; QuotStr = '"'; AmpStr = '&'; ltStr = '<'; gtStr = '>'; IndentChars : Array[Boolean] of char = (' ',#9); procedure AttrSpecialCharCallback(Sender: TXMLWriter; const s: DOMString; var idx: Integer); begin case s[idx] of '"': Sender.wrtStr(QuotStr); '&': Sender.wrtStr(AmpStr); '<': Sender.wrtStr(ltStr); // This is *only* to interoperate with broken parsers out there, // Delphi ClientDataset parser being one of them. '>': if not Sender.FCanonical then Sender.wrtStr(gtStr) else Sender.wrtChr('>'); // Escape whitespace using CharRefs to be consistent with W3 spec § 3.3.3 #9: Sender.wrtStr(' '); #10: Sender.wrtStr(' '); #13: Sender.wrtStr(' '); else raise EConvertError.Create('Illegal character'); 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); else raise EConvertError.Create('Illegal character'); 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); #13: Sender.wrtStr(' '); #10: Sender.wrtChr(#10); else raise EConvertError.Create('Illegal character'); end; end; procedure CDSectSpecialCharCallback(Sender: TXMLWriter; const s: DOMString; var idx: Integer); begin if s[idx]=']' then 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(']'); end else raise EConvertError.Create('Illegal character'); end; // clone of system.FPC_WIDESTR_COMPARE which cannot be called directly 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; begin Result := Compare(p1^.Attr.namespaceURI, p2^.Attr.namespaceURI); if Result = 0 then Result := Compare(p1^.Attr.localName, p2^.Attr.localName); end; const TextnodeCallbacks: array[boolean] of TSpecialCharCallback = ( @TextnodeNormalCallback, @TextnodeCanonicalCallback ); { --------------------------------------------------------------------- TXMLWriter ---------------------------------------------------------------------} constructor TXMLWriter.Create(AStream: TStream; ANameTable: THashTable); begin inherited Create; FStream := AStream; // some overhead - always be able to write at least one extra UCS4 FBuffer := AllocMem(512+32); FBufPos := FBuffer; FCapacity := 512; FCanonical:=False; FIndentSize:=2; FUseTab:=False; FLineBreak := sLineBreak; InitIndentLineBreak; FNesting := 0; SetLength(FNodes, 16); FNSHelper := TNSSupport.Create(ANameTable); 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 FStream.write(FBuffer^, FBufPos-FBuffer); FreeMem(FBuffer); inherited Destroy; end; procedure TXMLWriter.wrtChars(Src: PWideChar; Length: Integer); var pb: PChar; wc: Cardinal; SrcEnd: PWideChar; begin pb := FBufPos; SrcEnd := Src + Length; while Src < SrcEnd do begin if pb >= @FBuffer[FCapacity] then begin FStream.write(FBuffer^, FCapacity); Dec(pb, FCapacity); if pb > FBuffer then Move(FBuffer[FCapacity], FBuffer^, pb - FBuffer); end; wc := Cardinal(Src^); Inc(Src); 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; end; FBufPos := pb; end; procedure TXMLWriter.wrtStr(const ws: XMLString); { inline } begin wrtChars(PWideChar(ws), Length(ws)); end; { No checks here - buffer always has 32 extra bytes } procedure TXMLWriter.wrtChr(c: WideChar); { inline } begin FBufPos^ := char(ord(c)); Inc(FBufPos); end; procedure TXMLWriter.wrtIndent(EndElement: Boolean); Var L : integer; begin L:=(FNesting-ord(EndElement))*IndentSize+Length(FLineBreak); if (L>0) then wrtChars(PWideChar(FIndent), L); end; procedure TXMLWriter.IncNesting; var I, NewLen, OldLen: Integer; begin Inc(FNesting); if FNesting >= Length(FNodes) then SetLength(FNodes, FNesting+8); if (Length(FIndent)-Length(FLineBreak)) < IndentSize * FNesting then begin OldLen := Length(FIndent); NewLen := (IndentSize*2) * FNesting; SetLength(FIndent, NewLen); for I := OldLen to NewLen do FIndent[I] := IndentChars[UseTab]; end; end; procedure TXMLWriter.DecNesting; { inline } begin if FNesting>0 then dec(FNesting); end; procedure TXMLWriter.ConvWrite(const s: XMLString; 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 TXMLWriter.wrtQuotedLiteral(const ws: XMLString); var Quote: WideChar; 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.WriteNSDef(B: TBinding); begin wrtChars(' xmlns', 6); if B.Prefix^.Key <> '' then begin wrtChr(':'); wrtStr(B.Prefix^.Key); end; wrtChars('="', 2); if Assigned(B.uri) then ConvWrite(B.uri^.Key, AttrSpecialChars, @AttrSpecialCharCallback); wrtChr('"'); end; procedure TXMLWriter.InitIndentLineBreak; Var I : Integer; begin if FCanonical then FLineBreak := #10; // Initialize Indent string SetLength(FIndent, 100); I:=1; While I<=Length(FLineBreak) do begin FIndent[I] := FLineBreak[I]; Inc(I); end; While I<=Length(Findent) do begin FIndent[I]:=IndentChars[UseTab]; Inc(I); end; end; procedure TXMLWriter.WriteStartElement(const Name: XMLString); begin if not FInsideTextNode then wrtIndent; FNSHelper.PushScope; IncNesting; wrtChr('<'); wrtStr(Name); FNodes[FNesting].Name := Name; end; procedure TXMLWriter.WriteEndElement(shortForm: Boolean); begin if shortForm then wrtChars('/>', 2) else begin wrtChars(''); end; DecNesting; FNSHelper.PopScope; end; procedure TXMLWriter.WriteString(const Text: XMLString); begin ConvWrite(Text, TextSpecialChars, TextnodeCallbacks[FCanonical]); end; procedure TXMLWriter.WriteCDATA(const Text: XMLString); begin if not FInsideTextNode then wrtIndent; if FCanonical then ConvWrite(Text, TextSpecialChars, @TextnodeCanonicalCallback) else begin wrtChars('', 3); end; end; procedure TXMLWriter.WriteEntityRef(const Name: XMLString); begin wrtChr('&'); wrtStr(Name); wrtChr(';'); end; procedure TXMLWriter.WriteProcessingInstruction(const Target, Data: XMLString); begin if not FInsideTextNode then wrtIndent; wrtStr(' '' then begin wrtChr(' '); // TODO: How does this comply with c14n?? ConvWrite(Data, LineEndingChars, @TextnodeNormalCallback); end; wrtStr('?>'); end; procedure TXMLWriter.WriteComment(const Text: XMLString); begin if not FInsideTextNode then wrtIndent; wrtChars('', 3); end; procedure TXMLWriter.WriteXMLDecl(const aVersion, aEncoding: XMLString; aStandalone: Integer); begin wrtStr(' '' then wrtStr(aVersion) else wrtStr('1.0'); wrtChr('"'); wrtStr(' encoding="'); wrtStr(aEncoding); wrtChr('"'); if aStandalone >= 0 then begin wrtStr(' standalone="'); if aStandalone > 0 then wrtStr('yes') else wrtStr('no'); wrtChr('"'); end; wrtStr('?>'); end; procedure TXMLWriter.SetCanonical(AValue: Boolean); begin if FCanonical=AValue then Exit; FCanonical:=AValue; InitIndentLineBreak; end; procedure TXMLWriter.SetIndentSize(AValue: Integer); begin if FIndentSize=AValue then Exit; FIndentSize:=AValue; InitIndentLineBreak; end; procedure TXMLWriter.SetLineBreak(AValue: XMLString); begin if FLineBreak=AValue then Exit; FLineBreak:=AValue; InitIndentLineBreak; end; procedure TXMLWriter.SetUseTab(AValue: Boolean); begin if FUseTab=AValue then Exit; FUseTab:=AValue; InitIndentLineBreak; end; { --------------------------------------------------------------------- TDOMWriter ---------------------------------------------------------------------} procedure TDOMWriter.WriteNode(node: TDOMNode); begin case node.NodeType of ELEMENT_NODE: VisitElement(node); ATTRIBUTE_NODE: VisitAttribute(node); TEXT_NODE: WriteString(TDOMCharacterData(node).Data); CDATA_SECTION_NODE: WriteCDATA(TDOMCharacterData(node).Data); ENTITY_REFERENCE_NODE: VisitEntityRef(node); PROCESSING_INSTRUCTION_NODE: VisitPI(node); COMMENT_NODE: WriteComment(TDOMCharacterData(node).Data); 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 TDOMWriter.VisitElement(node: TDOMNode); var i: Integer; child: TDOMNode; SavedInsideTextNode: Boolean; begin WriteStartElement(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 WriteEndElement(True) else begin // TODO: presence of zero-length textnodes triggers the indenting logic, // while they should be ignored altogeter. SavedInsideTextNode := FInsideTextNode; wrtChr('>'); FInsideTextNode := FCanonical or (Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]); repeat WriteNode(Child); Child := Child.NextSibling; until Child = nil; if not (node.LastChild.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]) then wrtIndent(True); FInsideTextNode := SavedInsideTextNode; writeEndElement(False); end; end; procedure TDOMWriter.VisitEntityRef(node: TDOMNode); begin WriteEntityRef(node.NodeName); end; procedure TDOMWriter.VisitPI(node: TDOMNode); begin WriteProcessingInstruction(TDOMProcessingInstruction(node).Target, TDOMProcessingInstruction(node).Data); end; constructor TDOMWriter.Create(AStream: TStream; aNode: TDOMNode); var doc: TDOMDocument; begin if aNode.NodeType = DOCUMENT_NODE then doc := TDOMDocument(aNode) else doc := aNode.OwnerDocument; Inherited Create(aStream,Doc.Names); end; procedure TDOMWriter.VisitDocument(node: TDOMNode); var child: TDOMNode; begin // Here we ignore doc.xmlEncoding and write a fixed utf-8 label, // because it is the only output encoding currently supported. WriteXMLDecl(TXMLDocument(node).XMLVersion, 'utf-8', (ord(TXMLDocument(node).XMLStandalone)-1) or 1); // TODO: now handled as a regular PI, remove this? if node is TXMLDocument then begin if Length(TXMLDocument(node).StylesheetType) > 0 then begin wrtStr(FLineBreak); wrtStr(''); end; end; child := node.FirstChild; while Assigned(Child) do begin WriteNode(Child); Child := Child.NextSibling; end; wrtStr(FLineBreak); end; procedure TDOMWriter.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.WriteAttributeString(const Name, Value: XMLString); begin wrtChr(' '); wrtStr(Name); wrtChars('="', 2); ConvWrite(Value, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback); wrtChr('"'); end; procedure TXMLWriter.WriteDocType(const Name, PubId, SysId, Subset: XMLString); begin wrtStr(FLineBreak); wrtStr(' '' then begin wrtStr('PUBLIC '); wrtQuotedLiteral(PubId); wrtChr(' '); wrtQuotedLiteral(SysId); end else if SysId <> '' then begin wrtStr('SYSTEM '); wrtQuotedLiteral(SysId); end; if Subset <> '' then begin wrtChr('['); ConvWrite(Subset, LineEndingChars, @TextnodeNormalCallback); wrtChr(']'); end; wrtChr('>'); end; procedure TDOMWriter.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; procedure TDOMWriter.VisitAttribute(Node: TDOMNode); var Child: TDOMNode; begin wrtChr(' '); wrtStr(TDOMAttr(Node).Name); wrtChars('="', 2); Child := Node.FirstChild; while Assigned(Child) do begin case Child.NodeType of ENTITY_REFERENCE_NODE: VisitEntityRef(Child); TEXT_NODE: ConvWrite(TDOMCharacterData(Child).Data, AttrSpecialChars, @AttrSpecialCharCallback); end; Child := Child.NextSibling; end; wrtChr('"'); end; procedure TDOMWriter.VisitDocumentType(Node: TDOMNode); begin WriteDocType(Node.NodeName, TDOMDocumentType(Node).PublicID, TDOMDocumentType(Node).SystemID, TDOMDocumentType(Node).InternalSubset); end; procedure TDOMWriter.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, @AttrSpecialCharCallback); wrtChr('"'); end; end; end; // ------------------------------------------------------------------- // Interface implementation // ------------------------------------------------------------------- procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String); begin WriteXML(doc, AFileName); end; procedure WriteXMLFile(doc: TXMLDocument; var AFile: Text); begin WriteXML(doc, AFile); end; procedure WriteXMLFile(doc: TXMLDocument; AStream: TStream); begin WriteXML(doc, AStream); end; procedure WriteXML(Element: TDOMNode; const AFileName: String); var fs: TFileStream; begin fs := TFileStream.Create(AFileName, fmCreate); try WriteXML(Element, fs); finally fs.Free; end; end; procedure WriteXML(Element: TDOMNode; var AFile: Text); var S : TStream; begin s := TTextStream.Create(AFile); try WriteXML(Element,S); finally s.Free; end; end; procedure WriteXML(Element: TDOMNode; AStream: TStream); begin with TDOMWriter.Create(AStream, Element) do try WriteNode(Element); finally Free; end; end; end.