|
@@ -50,13 +50,19 @@ type
|
|
|
Prefix: PHashItem;
|
|
|
end;
|
|
|
|
|
|
+ TNodeInfo = record
|
|
|
+ Name: XMLString;
|
|
|
+ end;
|
|
|
+
|
|
|
+ TNodeInfoArray = array of TNodeInfo;
|
|
|
+
|
|
|
TXMLWriter = class(TObject)
|
|
|
private
|
|
|
FStream: TStream;
|
|
|
FInsideTextNode: Boolean;
|
|
|
FCanonical: Boolean;
|
|
|
FIndent: XMLString;
|
|
|
- FIndentCount: Integer;
|
|
|
+ FNesting: Integer;
|
|
|
FBuffer: PChar;
|
|
|
FBufPos: PChar;
|
|
|
FCapacity: Integer;
|
|
@@ -65,12 +71,15 @@ type
|
|
|
FAttrFixups: TFPList;
|
|
|
FScratch: TFPList;
|
|
|
FNSDefs: TFPList;
|
|
|
+ FNodes: TNodeInfoArray;
|
|
|
+ procedure WriteXMLDecl(const aVersion, aEncoding: XMLString;
|
|
|
+ aStandalone: Integer);
|
|
|
procedure wrtChars(Src: PWideChar; Length: Integer);
|
|
|
- procedure IncIndent;
|
|
|
- procedure DecIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
|
|
|
+ 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; {$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);
|
|
@@ -90,6 +99,8 @@ type
|
|
|
procedure VisitDocumentType(Node: TDOMNode);
|
|
|
procedure VisitPI(Node: TDOMNode);
|
|
|
|
|
|
+ procedure WriteStartElement(const Name: XMLString);
|
|
|
+ procedure WriteEndElement(shortForm: Boolean);
|
|
|
procedure WriteProcessingInstruction(const Target, Data: XMLString);
|
|
|
procedure WriteEntityRef(const Name: XMLString);
|
|
|
procedure WriteAttributeString(const Name, Value: XMLString);
|
|
@@ -169,7 +180,8 @@ begin
|
|
|
else
|
|
|
FIndent[2] := ' ';
|
|
|
for I := 3 to 100 do FIndent[I] := ' ';
|
|
|
- FIndentCount := 0;
|
|
|
+ FNesting := 0;
|
|
|
+ SetLength(FNodes, 16);
|
|
|
FNSHelper := TNSSupport.Create(ANameTable);
|
|
|
FScratch := TFPList.Create;
|
|
|
FNSDefs := TFPList.Create;
|
|
@@ -264,29 +276,31 @@ begin
|
|
|
Inc(FBufPos);
|
|
|
end;
|
|
|
|
|
|
-procedure TXMLWriter.wrtIndent; { inline }
|
|
|
+procedure TXMLWriter.wrtIndent(EndElement: Boolean);
|
|
|
begin
|
|
|
- wrtChars(PWideChar(FIndent), FIndentCount*2+Length(FLineBreak));
|
|
|
+ wrtChars(PWideChar(FIndent), (FNesting-ord(EndElement))*2+Length(FLineBreak));
|
|
|
end;
|
|
|
|
|
|
-procedure TXMLWriter.IncIndent;
|
|
|
+procedure TXMLWriter.IncNesting;
|
|
|
var
|
|
|
I, NewLen, OldLen: Integer;
|
|
|
begin
|
|
|
- Inc(FIndentCount);
|
|
|
- if Length(FIndent) < 2 * FIndentCount then
|
|
|
+ Inc(FNesting);
|
|
|
+ if FNesting >= Length(FNodes) then
|
|
|
+ SetLength(FNodes, FNesting+8);
|
|
|
+ if Length(FIndent) < 2 * FNesting then
|
|
|
begin
|
|
|
OldLen := Length(FIndent);
|
|
|
- NewLen := 4 * FIndentCount;
|
|
|
+ NewLen := 4 * FNesting;
|
|
|
SetLength(FIndent, NewLen);
|
|
|
for I := OldLen to NewLen do
|
|
|
FIndent[I] := ' ';
|
|
|
end;
|
|
|
end;
|
|
|
|
|
|
-procedure TXMLWriter.DecIndent; { inline }
|
|
|
+procedure TXMLWriter.DecNesting; { inline }
|
|
|
begin
|
|
|
- if FIndentCount>0 then dec(FIndentCount);
|
|
|
+ if FNesting>0 then dec(FNesting);
|
|
|
end;
|
|
|
|
|
|
procedure TXMLWriter.ConvWrite(const s: XMLString; const SpecialChars: TSetOfChar;
|
|
@@ -579,11 +593,7 @@ var
|
|
|
child: TDOMNode;
|
|
|
SavedInsideTextNode: Boolean;
|
|
|
begin
|
|
|
- if not FInsideTextNode then
|
|
|
- wrtIndent;
|
|
|
- FNSHelper.PushScope;
|
|
|
- wrtChr('<');
|
|
|
- wrtStr(TDOMElement(node).TagName);
|
|
|
+ WriteStartElement(TDOMElement(node).TagName);
|
|
|
|
|
|
if nfLevel2 in node.Flags then
|
|
|
NamespaceFixup(TDOMElement(node))
|
|
@@ -596,7 +606,7 @@ begin
|
|
|
end;
|
|
|
Child := node.FirstChild;
|
|
|
if Child = nil then
|
|
|
- wrtChars('/>', 2)
|
|
|
+ WriteEndElement(True)
|
|
|
else
|
|
|
begin
|
|
|
// TODO: presence of zero-length textnodes triggers the indenting logic,
|
|
@@ -604,19 +614,40 @@ begin
|
|
|
SavedInsideTextNode := FInsideTextNode;
|
|
|
wrtChr('>');
|
|
|
FInsideTextNode := FCanonical or (Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]);
|
|
|
- IncIndent;
|
|
|
repeat
|
|
|
WriteNode(Child);
|
|
|
Child := Child.NextSibling;
|
|
|
until Child = nil;
|
|
|
- DecIndent;
|
|
|
if not (node.LastChild.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]) then
|
|
|
- wrtIndent;
|
|
|
+ wrtIndent(True);
|
|
|
FInsideTextNode := SavedInsideTextNode;
|
|
|
+ writeEndElement(False);
|
|
|
+ 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('</', 2);
|
|
|
- wrtStr(TDOMElement(Node).TagName);
|
|
|
+ wrtStr(FNodes[FNesting].Name);
|
|
|
wrtChr('>');
|
|
|
end;
|
|
|
+ DecNesting;
|
|
|
FNSHelper.PopScope;
|
|
|
end;
|
|
|
|
|
@@ -679,26 +710,39 @@ begin
|
|
|
wrtChars('-->', 3);
|
|
|
end;
|
|
|
|
|
|
-procedure TXMLWriter.VisitDocument(node: TDOMNode);
|
|
|
-var
|
|
|
- child: TDOMNode;
|
|
|
+procedure TXMLWriter.WriteXMLDecl(const aVersion, aEncoding: XMLString; aStandalone: Integer);
|
|
|
begin
|
|
|
wrtStr('<?xml version="');
|
|
|
- // Definitely should not escape anything here
|
|
|
- if Length(TXMLDocument(node).XMLVersion) > 0 then
|
|
|
- wrtStr(TXMLDocument(node).XMLVersion)
|
|
|
+ if aVersion <> '' then
|
|
|
+ wrtStr(aVersion)
|
|
|
else
|
|
|
wrtStr('1.0');
|
|
|
wrtChr('"');
|
|
|
|
|
|
- // Here we ignore doc.xmlEncoding and write a fixed utf-8 label,
|
|
|
- // because it is the only output encoding currently supported.
|
|
|
- wrtStr(' encoding="utf-8"');
|
|
|
+ wrtStr(' encoding="');
|
|
|
+ wrtStr(aEncoding);
|
|
|
+ wrtChr('"');
|
|
|
|
|
|
- if TXMLDocument(node).xmlStandalone then
|
|
|
- wrtStr(' standalone="yes"');
|
|
|
+ if aStandalone >= 0 then
|
|
|
+ begin
|
|
|
+ wrtStr(' standalone="');
|
|
|
+ if aStandalone > 0 then
|
|
|
+ wrtStr('yes')
|
|
|
+ else
|
|
|
+ wrtStr('no');
|
|
|
+ wrtChr('"');
|
|
|
+ end;
|
|
|
|
|
|
wrtStr('?>');
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TXMLWriter.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
|