Browse Source

--- Merging r14145 into '.':
U packages/fcl-xml/src/dom.pp
--- Merging r14147 into '.':
G packages/fcl-xml/src/dom.pp
--- Merging r14186 into '.':
U packages/fcl-xml/src/xmlwrite.pp
--- Merging r14192 into '.':
G packages/fcl-xml/src/xmlwrite.pp
--- Merging r14194 into '.':
G packages/fcl-xml/src/xmlwrite.pp
--- Merging r14202 into '.':
U packages/fcl-xml/src/xmlread.pp
--- Merging r14207 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r14209 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r14232 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r14248 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r14290 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r14293 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r14351 into '.':
U packages/fcl-xml/tests/extras.pp
G packages/fcl-xml/src/dom.pp
--- Merging r14360 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r14361 into '.':
G packages/fcl-xml/src/xmlread.pp
--- Merging r14362 into '.':
G packages/fcl-xml/src/dom.pp

Second batch XML commits up to 2009-12-08. (only a very few recent ones left)

------------------------------------------------------------------------
r14145 | sergei | 2009-11-11 15:19:50 +0100 (Wed, 11 Nov 2009) | 2 lines

- removed dependency on avl_tree, improves speed, thread safety and memory requirements.

------------------------------------------------------------------------
------------------------------------------------------------------------
r14147 | sergei | 2009-11-11 21:33:03 +0100 (Wed, 11 Nov 2009) | 1 line

+ Added TDOMNode_WithChildren.InternalAppend, and used it to build node tree when cloning nodes. This speeds up the scenario when cloneNode() and node lists are used together, because the document is no longer marked as modified at each call to cloneNode.
------------------------------------------------------------------------
------------------------------------------------------------------------
r14186 | sergei | 2009-11-15 17:35:13 +0100 (Sun, 15 Nov 2009) | 1 line

XML writer: split CDATA sections at the ']]>' sequence.
------------------------------------------------------------------------
------------------------------------------------------------------------
r14192 | sergei | 2009-11-15 22:14:39 +0100 (Sun, 15 Nov 2009) | 5 lines

+ More c14n conformance in the writer:
* dedicated procedure for writing the document node;
* no indenting in c14n mode;
* ignore Specified property of the attributes in c14n mode.

------------------------------------------------------------------------
------------------------------------------------------------------------
r14194 | sergei | 2009-11-16 00:04:02 +0100 (Mon, 16 Nov 2009) | 4 lines

XML writer:
* Moved line ending processing from the encoder to a higher level; without this, implementing/using external encoders is very problematic.
+ Implemented line ending processing for c14n mode.

------------------------------------------------------------------------
------------------------------------------------------------------------
r14202 | sergei | 2009-11-17 00:43:01 +0100 (Tue, 17 Nov 2009) | 3 lines

* Replaced all literal parsing routines with a single ParseLiteral(). Due to entity handling issues, this isn't yet enabled for attributes, therefore the current code contains some amount of redundancy.
* Started refactoring of the entity processing.

------------------------------------------------------------------------
------------------------------------------------------------------------
r14207 | sergei | 2009-11-18 01:48:05 +0100 (Wed, 18 Nov 2009) | 3 lines

* Call StoreLocation once in SkipQuote, rather than every time after calling it.
- Removed recognition of 'ISO8859-1', as it was a workaround for incorrect fpdoc encodings.
- Removed with statement in ParseContent, it won't work if we handle entities non-recusively, because FSource will be changing.
------------------------------------------------------------------------
------------------------------------------------------------------------
r14209 | sergei | 2009-11-18 12:42:35 +0100 (Wed, 18 Nov 2009) | 1 line

* Reverted removal of 'ISO8859-1' encoding because it is still used in fcl-registry.
------------------------------------------------------------------------
------------------------------------------------------------------------
r14232 | sergei | 2009-11-21 00:32:08 +0100 (Sat, 21 Nov 2009) | 4 lines

xmlread.pp: More on entity processing:
* General entities are now processed non-recursively;
* They are now re-parsed on each inclusion, enabling proper validation and ensuring SAX-compatible order of events. Also less dependent on DOM-specific calls like CloneNode.

------------------------------------------------------------------------
------------------------------------------------------------------------
r14248 | sergei | 2009-11-21 22:59:16 +0100 (Sat, 21 Nov 2009) | 2 lines

* Rewrote TXMLReader.ParseContent to eliminate the inner loop;
* Also modified TXMLReader.ParseContent so that it produces normalized text nodes, i.e. merges text nodes on entity boundaries (when Options.ExpandEntities=True, of course) and merges the text coming from CDATA sections when Options.CDSectionsAsText=True.
------------------------------------------------------------------------
------------------------------------------------------------------------
r14290 | sergei | 2009-11-30 17:15:53 +0100 (Mon, 30 Nov 2009) | 1 line

* xmlread.pp: move all entity recursion checks into one place (in ContextPush).
------------------------------------------------------------------------
------------------------------------------------------------------------
r14293 | sergei | 2009-12-01 10:12:28 +0100 (Tue, 01 Dec 2009) | 1 line

* xmlread.pp: In case of reference to an undefined parameter entity, produce a validation error and ignore further DTD declarations unless the document is standalone (compliance).
------------------------------------------------------------------------
------------------------------------------------------------------------
r14351 | sergei | 2009-12-07 17:16:10 +0100 (Mon, 07 Dec 2009) | 2 lines

* TDOMElement.RemoveAttributeNode() was not resetting OwnerElement property of the removed attribute to nil, fixed and added a test case.

------------------------------------------------------------------------
------------------------------------------------------------------------
r14360 | sergei | 2009-12-08 06:20:44 +0100 (Tue, 08 Dec 2009) | 1 line

* Removed null-termination in TXMLStreamInputSource.FetchData(): it isn't necessary and is causing unaligned access errors with ARM CPUs.
------------------------------------------------------------------------
------------------------------------------------------------------------
r14361 | sergei | 2009-12-08 09:10:35 +0100 (Tue, 08 Dec 2009) | 7 lines

* xmlread.pp, added a flag to force input stack unwinding upon reader destruction.
Without this, certain (malformed) documents (e.g. eduni/xml-1.1/005.xml) were causing
InputSource leaks.

Note: these leaks are a side effect from recent changes to entity processing and are not
observed with older versions.

------------------------------------------------------------------------
------------------------------------------------------------------------
r14362 | sergei | 2009-12-08 10:09:23 +0100 (Tue, 08 Dec 2009) | 1 line

* dom.pp: clean up
------------------------------------------------------------------------

git-svn-id: branches/fixes_2_4@14635 -

marco 15 years ago
parent
commit
aedad4aa4a
4 changed files with 565 additions and 392 deletions
  1. 25 55
      packages/fcl-xml/src/dom.pp
  2. 385 282
      packages/fcl-xml/src/xmlread.pp
  3. 136 55
      packages/fcl-xml/src/xmlwrite.pp
  4. 19 0
      packages/fcl-xml/tests/extras.pp

+ 25 - 55
packages/fcl-xml/src/dom.pp

@@ -38,7 +38,7 @@ unit DOM;
 interface
 
 uses
-  SysUtils, Classes, AVL_Tree, xmlutils;
+  SysUtils, Classes, xmlutils;
 
 // -------------------------------------------------------
 //   DOMException
@@ -276,13 +276,10 @@ type
   TDOMNode_WithChildren = class(TDOMNode)
   protected
     FFirstChild, FLastChild: TDOMNode;
-    FChildNodeTree: TAVLTree;
     FChildNodes: TDOMNodeList;
     function GetFirstChild: TDOMNode; override;
     function GetLastChild: TDOMNode; override;
     procedure CloneChildren(ACopy: TDOMNode; ACloneOwner: TDOMDocument);
-    procedure AddToChildNodeTree(NewNode: TDOMNode);
-    procedure RemoveFromChildNodeTree(OldNode: TDOMNode);
     procedure FreeChildren;
     function GetTextContent: DOMString; override;
     procedure SetTextContent(const AValue: DOMString); override;
@@ -293,6 +290,7 @@ type
     function DetachChild(OldChild: TDOMNode): TDOMNode; override;
     function HasChildNodes: Boolean; override;
     function FindNode(const ANodeName: DOMString): TDOMNode; override;
+    procedure InternalAppend(NewChild: TDOMNode);
   end;
 
 
@@ -1261,16 +1259,6 @@ end;
 
 //------------------------------------------------------------------------------
 
-function CompareDOMNodeWithDOMNode(Node1, Node2: Pointer): integer;
-begin
-  Result := TDOMNode(Node2).CompareName(TDOMNode(Node1).NodeName);
-end;
-
-function CompareDOMStringWithDOMNode(AKey, ANode: Pointer): integer;
-begin
-  Result := TDOMNode(ANode).CompareName(PDOMString(AKey)^);
-end;
-
 type
   TNodeTypeEnum = ELEMENT_NODE..NOTATION_NODE;
   TNodeTypeSet = set of TNodeTypeEnum;
@@ -1307,7 +1295,6 @@ end;
 destructor TDOMNode_WithChildren.Destroy;
 begin
   FreeChildren;
-  FreeAndNil(FChildNodeTree);
   FChildNodes.Free; // its destructor will zero the field
   inherited Destroy;
 end;
@@ -1395,13 +1382,11 @@ begin
     RefChild.FPreviousSibling := NewChild;
   end;
   NewChild.FParentNode := Self;
-  AddToChildNodeTree(NewChild);
 end;
 
 function TDOMNode_WithChildren.ReplaceChild(NewChild, OldChild: TDOMNode):
   TDOMNode;
 begin
-  RemoveFromChildNodeTree(OldChild);
   InsertBefore(NewChild, OldChild);
   if Assigned(OldChild) then
     RemoveChild(OldChild);
@@ -1427,7 +1412,6 @@ begin
   else
     OldChild.FNextSibling.FPreviousSibling := OldChild.FPreviousSibling;
 
-  RemoveFromChildNodeTree(OldChild);
   // Make sure removed child does not contain references to nowhere
   OldChild.FPreviousSibling := nil;
   OldChild.FNextSibling := nil;
@@ -1435,6 +1419,18 @@ begin
   Result := OldChild;
 end;
 
+procedure TDOMNode_WithChildren.InternalAppend(NewChild: TDOMNode);
+begin
+  if Assigned(FFirstChild) then
+  begin
+    FLastChild.FNextSibling := NewChild;
+    NewChild.FPreviousSibling := FLastChild;
+  end else
+    FFirstChild := NewChild;
+  FLastChild := NewChild;
+  NewChild.FParentNode := Self;
+end;
+
 function TDOMNode_WithChildren.HasChildNodes: Boolean;
 begin
   Result := Assigned(FFirstChild);
@@ -1442,14 +1438,13 @@ end;
 
 
 function TDOMNode_WithChildren.FindNode(const ANodeName: DOMString): TDOMNode;
-var AVLNode: TAVLTreeNode;
 begin
-  Result:=nil;
-  if FChildNodeTree<>nil then begin
-    AVLNode:=FChildNodeTree.FindKey(Pointer(@ANodeName),
-                                    @CompareDOMStringWithDOMNode);
-    if AVLNode<>nil then
-      Result:=TDOMNode(AVLNode.Data);
+  Result := FFirstChild;
+  while Assigned(Result) do
+  begin
+    if Result.CompareName(ANodeName)=0 then
+      Exit;
+    Result := Result.NextSibling;
   end;
 end;
 
@@ -1462,7 +1457,7 @@ begin
   node := FirstChild;
   while Assigned(node) do
   begin
-    ACopy.AppendChild(node.CloneNode(True, ACloneOwner));
+    TDOMNode_WithChildren(ACopy).InternalAppend(node.CloneNode(True, ACloneOwner));
     node := node.NextSibling;
   end;
 end;
@@ -1471,8 +1466,6 @@ procedure TDOMNode_WithChildren.FreeChildren;
 var
   child, next: TDOMNode;
 begin
-  if Assigned(FChildNodeTree) then
-    FChildNodeTree.Clear;
   child := FFirstChild;
   while Assigned(child) do
   begin
@@ -1514,21 +1507,6 @@ begin
     AppendChild(FOwnerDocument.CreateTextNode(AValue));
 end;
 
-procedure TDOMNode_WithChildren.AddToChildNodeTree(NewNode: TDOMNode);
-begin
-  if FChildNodeTree=nil then
-    FChildNodeTree:=TAVLTree.Create(@CompareDOMNodeWithDOMNode);
-  if FChildNodeTree.Find(NewNode)=nil then
-    FChildNodeTree.Add(NewNode);
-end;
-
-procedure TDOMNode_WithChildren.RemoveFromChildNodeTree(OldNode: TDOMNode);
-begin
-  if FChildNodeTree<>nil then
-    FChildNodeTree.Remove(OldNode);
-end;
-
-
 // -------------------------------------------------------
 //   NodeList
 // -------------------------------------------------------
@@ -2188,13 +2166,9 @@ begin
 
   ID := Attr.Value;
   p := FIDList.FindOrAdd(DOMPChar(ID), Length(ID), Exists);
-  if not Exists then
-  begin
+  Result := not Exists;
+  if Result then
     p^.Data := Attr.OwnerElement;
-    Result := True;
-  end
-  else
-    Result := False;
 end;
 
 // This shouldn't be called if document has no IDs,
@@ -2973,16 +2947,12 @@ end;
 function TDOMElement.RemoveAttributeNode(OldAttr: TDOMAttr): TDOMAttr;
 begin
   Changing;
-  Result:=nil;
-  // TODO: DOM 2: must raise NOT_FOUND_ERR if OldAttr is not ours.
-  //       -- but what is the purpose of return value then?
-  // TODO: delegate to TNamedNodeMap?  Nope, it does not have such method
-  // (note) one way around is to remove by name
+  Result:=OldAttr;
   if Assigned(FAttributes) and (FAttributes.FList.Remove(OldAttr) > -1) then
   begin
-    Result := OldAttr;
     if Assigned(OldAttr.FNSI.QName) then  // safeguard
       FAttributes.RestoreDefault(OldAttr.FNSI.QName^.Key);
+    Result.FOwnerElement := nil;
   end
   else
     raise EDOMNotFound.Create('Element.RemoveAttributeNode');

File diff suppressed because it is too large
+ 385 - 282
packages/fcl-xml/src/xmlread.pp


+ 136 - 55
packages/fcl-xml/src/xmlwrite.pp

@@ -59,7 +59,7 @@ type
     FBuffer: PChar;
     FBufPos: PChar;
     FCapacity: Integer;
-    FLineBreak: string;
+    FLineBreak: WideString;
     FNSHelper: TNSSupport;
     FAttrFixups: TFPList;
     FScratch: TFPList;
@@ -79,6 +79,7 @@ type
     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);
@@ -155,6 +156,16 @@ end;
     TXMLWriter
   ---------------------------------------------------------------------}
 
+const
+  AttrSpecialChars = ['<', '"', '&', #9, #10, #13];
+  TextSpecialChars = ['<', '>', '&', #10, #13];
+  CDSectSpecialChars = [']'];
+  LineEndingChars = [#13, #10];
+  QuotStr = '&quot;';
+  AmpStr = '&amp;';
+  ltStr = '&lt;';
+  gtStr = '&gt;';
+
 constructor TXMLWriter.Create;
 var
   I: Integer;
@@ -164,14 +175,22 @@ begin
   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] := #10;
-  for I := 2 to 100 do FIndent[I] := ' ';
+  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;
-  // Later on, this may be put under user control
-  // for now, take OS setting
-  FLineBreak := sLineBreak;
   FNSHelper := TNSSupport.Create;
   FScratch := TFPList.Create;
   FNSDefs := TFPList.Create;
@@ -215,14 +234,7 @@ begin
 
     wc := Cardinal(Src^);  Inc(Src);
     case wc of
-      $0A: pb := StrECopy(pb, PChar(FLineBreak));
-      $0D: begin
-        pb := StrECopy(pb, PChar(FLineBreak));
-        if (Src < SrcEnd) and (Src^ = #$0A) then
-          Inc(Src);
-      end;
-
-      0..$09, $0B, $0C, $0E..$7F:  begin
+      0..$7F:  begin
         pb^ := char(wc); Inc(pb);
       end;
 
@@ -275,7 +287,7 @@ end;
 
 procedure TXMLWriter.wrtIndent; { inline }
 begin
-  wrtChars(PWideChar(FIndent), FIndentCount*2+1);
+  wrtChars(PWideChar(FIndent), FIndentCount*2+Length(FLineBreak));
 end;
 
 procedure TXMLWriter.IncIndent;
@@ -298,25 +310,6 @@ begin
   if FIndentCount>0 then dec(FIndentCount);
 end;
 
-procedure TXMLWriter.wrtQuotedLiteral(const ws: WideString);
-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);
-  wrtStr(ws);
-  wrtChr(Quote);
-end;
-
-const
-  AttrSpecialChars = ['<', '"', '&', #9, #10, #13];
-  TextSpecialChars = ['<', '>', '&'];
-
 procedure TXMLWriter.ConvWrite(const s: WideString; const SpecialChars: TSetOfChar;
   const SpecialCharCallback: TSpecialCharCallback);
 var
@@ -326,7 +319,7 @@ begin
   EndPos := 1;
   while EndPos <= Length(s) do
   begin
-    if (s[EndPos] < #255) and (Char(ord(s[EndPos])) in SpecialChars) then
+    if (s[EndPos] < 'A') and (Char(ord(s[EndPos])) in SpecialChars) then
     begin
       wrtChars(@s[StartPos], EndPos - StartPos);
       SpecialCharCallback(Self, s, EndPos);
@@ -338,12 +331,6 @@ begin
     wrtChars(@s[StartPos], EndPos - StartPos);
 end;
 
-const
-  QuotStr = '&quot;';
-  AmpStr = '&amp;';
-  ltStr = '&lt;';
-  gtStr = '&gt;';
-
 procedure AttrSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
   var idx: Integer);
 begin
@@ -360,18 +347,74 @@ begin
   end;
 end;
 
-procedure TextnodeSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
+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
     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);
+    #13: Sender.wrtStr('&#xD;')
+  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(']]]]><![CDATA[>');
+    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: WideString);
+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.WriteNode(node: TDOMNode);
 begin
   case node.NodeType of
@@ -382,7 +425,11 @@ begin
     ENTITY_REFERENCE_NODE:       VisitEntityRef(node);
     PROCESSING_INSTRUCTION_NODE: VisitPI(node);
     COMMENT_NODE:                VisitComment(node);
-    DOCUMENT_NODE:               VisitDocument(node);
+    DOCUMENT_NODE:
+      if FCanonical then
+        VisitDocument_Canonical(node)
+      else
+        VisitDocument(node);
     DOCUMENT_TYPE_NODE:          VisitDocumentType(node);
     ENTITY_NODE,
     DOCUMENT_FRAGMENT_NODE:      VisitFragment(node);
@@ -473,7 +520,7 @@ begin
         if Assigned(B) then  // drop redundant namespace declarations
           FNSDefs.Add(B);
       end
-      else if TDOMAttr(node).Specified then
+      else if FCanonical or TDOMAttr(node).Specified then
       begin
         // obtain a TAttrFixup record (allocate if needed)
         if j >= FAttrFixups.Count then
@@ -557,7 +604,7 @@ begin
     for i := 0 to node.Attributes.Length - 1 do
     begin
       child := node.Attributes.Item[i];
-      if TDOMAttr(child).Specified then
+      if FCanonical or TDOMAttr(child).Specified then
         VisitAttribute(child);
     end;
   Child := node.FirstChild;
@@ -567,7 +614,7 @@ begin
   begin
     SavedInsideTextNode := FInsideTextNode;
     wrtChr('>');
-    FInsideTextNode := Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE];
+    FInsideTextNode := FCanonical or (Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]);
     IncIndent;
     repeat
       WriteNode(Child);
@@ -586,7 +633,7 @@ end;
 
 procedure TXMLWriter.VisitText(node: TDOMNode);
 begin
-  ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeSpecialCharCallback);
+  ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, TextnodeCallbacks[FCanonical]);
 end;
 
 procedure TXMLWriter.VisitCDATA(node: TDOMNode);
@@ -594,11 +641,11 @@ begin
   if not FInsideTextNode then
     wrtIndent;
   if FCanonical then
-    ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeSpecialCharCallback)
+    ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeCanonicalCallback)
   else
   begin
     wrtChars('<![CDATA[', 9);
-    wrtStr(TDOMCharacterData(node).Data);
+    ConvWrite(TDOMCharacterData(node).Data, CDSectSpecialChars, @CDSectSpecialCharCallback);
     wrtChars(']]>', 3);
   end;
 end;
@@ -618,7 +665,8 @@ begin
   if TDOMProcessingInstruction(node).Data <> '' then
   begin
     wrtChr(' ');
-    wrtStr(TDOMProcessingInstruction(node).Data);
+    // TODO: How does this comply with c14n??
+    ConvWrite(TDOMProcessingInstruction(node).Data, LineEndingChars, @TextnodeNormalCallback);
   end;
   wrtStr('?>');
 end;
@@ -627,7 +675,8 @@ procedure TXMLWriter.VisitComment(node: TDOMNode);
 begin
   if not FInsideTextNode then wrtIndent;
   wrtChars('<!--', 4);
-  wrtStr(TDOMCharacterData(node).Data);
+  // TODO: How does this comply with c14n??
+  ConvWrite(TDOMCharacterData(node).Data, LineEndingChars, @TextnodeNormalCallback);
   wrtChars('-->', 3);
 end;
 
@@ -658,7 +707,8 @@ begin
   // TODO: now handled as a regular PI, remove this?
   if Length(TXMLDocument(node).StylesheetType) > 0 then
   begin
-    wrtStr(#10'<?xml-stylesheet type="');
+    wrtStr(FLineBreak);
+    wrtStr('<?xml-stylesheet type="');
     wrtStr(TXMLDocument(node).StylesheetType);
     wrtStr('" href="');
     wrtStr(TXMLDocument(node).StylesheetHRef);
@@ -671,7 +721,37 @@ begin
     WriteNode(Child);
     Child := Child.NextSibling;
   end;
-  wrtChars(#10, 1);
+  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);
@@ -697,7 +777,8 @@ end;
 
 procedure TXMLWriter.VisitDocumentType(Node: TDOMNode);
 begin
-  wrtStr(#10'<!DOCTYPE ');
+  wrtStr(FLineBreak);
+  wrtStr('<!DOCTYPE ');
   wrtStr(Node.NodeName);
   wrtChr(' ');
   with TDOMDocumentType(Node) do
@@ -717,7 +798,7 @@ begin
     if InternalSubset <> '' then
     begin
       wrtChr('[');
-      wrtStr(InternalSubset);
+      ConvWrite(InternalSubset, LineEndingChars, @TextnodeNormalCallback);
       wrtChr(']');
     end;
   end;

+ 19 - 0
packages/fcl-xml/tests/extras.pp

@@ -29,6 +29,7 @@ type
     procedure attr_ownership02;
     procedure attr_ownership03;
     procedure attr_ownership04;
+    procedure attr_ownership05;
     procedure nsFixup1;
     procedure nsFixup2;
     procedure nsFixup3;
@@ -116,6 +117,24 @@ begin
   AssertEquals('ownerElement2', el, attr2.OwnerElement);
 end;
 
+
+// verify that Element.removeAttributeNode() resets ownerElement
+// of the attribute being removed
+procedure TDOMTestExtra.attr_ownership05;
+var
+  doc: TDOMDocument;
+  el: TDOMElement;
+  attr: TDOMAttr;
+begin
+  LoadStringData(doc, '<doc/>');
+  el := doc.CreateElement('element1');
+  attr := doc.CreateAttributeNS('http://www.freepascal.org', 'fpc:newAttr');
+  el.SetAttributeNodeNS(attr);
+  AssertEquals('ownerElement_before', el, attr.OwnerElement);
+  el.RemoveAttributeNode(attr);
+  AssertNull('ownerElement_after', attr.ownerElement);
+end;
+
 const
   nsURI1 = 'http://www.example.com/ns1';
   nsURI2 = 'http://www.example.com/ns2';

Some files were not shown because too many files changed in this diff