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

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

@@ -38,7 +38,7 @@ unit DOM;
 interface
 interface
 
 
 uses
 uses
-  SysUtils, Classes, AVL_Tree, xmlutils;
+  SysUtils, Classes, xmlutils;
 
 
 // -------------------------------------------------------
 // -------------------------------------------------------
 //   DOMException
 //   DOMException
@@ -276,13 +276,10 @@ type
   TDOMNode_WithChildren = class(TDOMNode)
   TDOMNode_WithChildren = class(TDOMNode)
   protected
   protected
     FFirstChild, FLastChild: TDOMNode;
     FFirstChild, FLastChild: TDOMNode;
-    FChildNodeTree: TAVLTree;
     FChildNodes: TDOMNodeList;
     FChildNodes: TDOMNodeList;
     function GetFirstChild: TDOMNode; override;
     function GetFirstChild: TDOMNode; override;
     function GetLastChild: TDOMNode; override;
     function GetLastChild: TDOMNode; override;
     procedure CloneChildren(ACopy: TDOMNode; ACloneOwner: TDOMDocument);
     procedure CloneChildren(ACopy: TDOMNode; ACloneOwner: TDOMDocument);
-    procedure AddToChildNodeTree(NewNode: TDOMNode);
-    procedure RemoveFromChildNodeTree(OldNode: TDOMNode);
     procedure FreeChildren;
     procedure FreeChildren;
     function GetTextContent: DOMString; override;
     function GetTextContent: DOMString; override;
     procedure SetTextContent(const AValue: DOMString); override;
     procedure SetTextContent(const AValue: DOMString); override;
@@ -293,6 +290,7 @@ type
     function DetachChild(OldChild: TDOMNode): TDOMNode; override;
     function DetachChild(OldChild: TDOMNode): TDOMNode; override;
     function HasChildNodes: Boolean; override;
     function HasChildNodes: Boolean; override;
     function FindNode(const ANodeName: DOMString): TDOMNode; override;
     function FindNode(const ANodeName: DOMString): TDOMNode; override;
+    procedure InternalAppend(NewChild: TDOMNode);
   end;
   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
 type
   TNodeTypeEnum = ELEMENT_NODE..NOTATION_NODE;
   TNodeTypeEnum = ELEMENT_NODE..NOTATION_NODE;
   TNodeTypeSet = set of TNodeTypeEnum;
   TNodeTypeSet = set of TNodeTypeEnum;
@@ -1307,7 +1295,6 @@ end;
 destructor TDOMNode_WithChildren.Destroy;
 destructor TDOMNode_WithChildren.Destroy;
 begin
 begin
   FreeChildren;
   FreeChildren;
-  FreeAndNil(FChildNodeTree);
   FChildNodes.Free; // its destructor will zero the field
   FChildNodes.Free; // its destructor will zero the field
   inherited Destroy;
   inherited Destroy;
 end;
 end;
@@ -1395,13 +1382,11 @@ begin
     RefChild.FPreviousSibling := NewChild;
     RefChild.FPreviousSibling := NewChild;
   end;
   end;
   NewChild.FParentNode := Self;
   NewChild.FParentNode := Self;
-  AddToChildNodeTree(NewChild);
 end;
 end;
 
 
 function TDOMNode_WithChildren.ReplaceChild(NewChild, OldChild: TDOMNode):
 function TDOMNode_WithChildren.ReplaceChild(NewChild, OldChild: TDOMNode):
   TDOMNode;
   TDOMNode;
 begin
 begin
-  RemoveFromChildNodeTree(OldChild);
   InsertBefore(NewChild, OldChild);
   InsertBefore(NewChild, OldChild);
   if Assigned(OldChild) then
   if Assigned(OldChild) then
     RemoveChild(OldChild);
     RemoveChild(OldChild);
@@ -1427,7 +1412,6 @@ begin
   else
   else
     OldChild.FNextSibling.FPreviousSibling := OldChild.FPreviousSibling;
     OldChild.FNextSibling.FPreviousSibling := OldChild.FPreviousSibling;
 
 
-  RemoveFromChildNodeTree(OldChild);
   // Make sure removed child does not contain references to nowhere
   // Make sure removed child does not contain references to nowhere
   OldChild.FPreviousSibling := nil;
   OldChild.FPreviousSibling := nil;
   OldChild.FNextSibling := nil;
   OldChild.FNextSibling := nil;
@@ -1435,6 +1419,18 @@ begin
   Result := OldChild;
   Result := OldChild;
 end;
 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;
 function TDOMNode_WithChildren.HasChildNodes: Boolean;
 begin
 begin
   Result := Assigned(FFirstChild);
   Result := Assigned(FFirstChild);
@@ -1442,14 +1438,13 @@ end;
 
 
 
 
 function TDOMNode_WithChildren.FindNode(const ANodeName: DOMString): TDOMNode;
 function TDOMNode_WithChildren.FindNode(const ANodeName: DOMString): TDOMNode;
-var AVLNode: TAVLTreeNode;
 begin
 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;
 end;
 end;
 
 
@@ -1462,7 +1457,7 @@ begin
   node := FirstChild;
   node := FirstChild;
   while Assigned(node) do
   while Assigned(node) do
   begin
   begin
-    ACopy.AppendChild(node.CloneNode(True, ACloneOwner));
+    TDOMNode_WithChildren(ACopy).InternalAppend(node.CloneNode(True, ACloneOwner));
     node := node.NextSibling;
     node := node.NextSibling;
   end;
   end;
 end;
 end;
@@ -1471,8 +1466,6 @@ procedure TDOMNode_WithChildren.FreeChildren;
 var
 var
   child, next: TDOMNode;
   child, next: TDOMNode;
 begin
 begin
-  if Assigned(FChildNodeTree) then
-    FChildNodeTree.Clear;
   child := FFirstChild;
   child := FFirstChild;
   while Assigned(child) do
   while Assigned(child) do
   begin
   begin
@@ -1514,21 +1507,6 @@ begin
     AppendChild(FOwnerDocument.CreateTextNode(AValue));
     AppendChild(FOwnerDocument.CreateTextNode(AValue));
 end;
 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
 //   NodeList
 // -------------------------------------------------------
 // -------------------------------------------------------
@@ -2188,13 +2166,9 @@ begin
 
 
   ID := Attr.Value;
   ID := Attr.Value;
   p := FIDList.FindOrAdd(DOMPChar(ID), Length(ID), Exists);
   p := FIDList.FindOrAdd(DOMPChar(ID), Length(ID), Exists);
-  if not Exists then
-  begin
+  Result := not Exists;
+  if Result then
     p^.Data := Attr.OwnerElement;
     p^.Data := Attr.OwnerElement;
-    Result := True;
-  end
-  else
-    Result := False;
 end;
 end;
 
 
 // This shouldn't be called if document has no IDs,
 // This shouldn't be called if document has no IDs,
@@ -2973,16 +2947,12 @@ end;
 function TDOMElement.RemoveAttributeNode(OldAttr: TDOMAttr): TDOMAttr;
 function TDOMElement.RemoveAttributeNode(OldAttr: TDOMAttr): TDOMAttr;
 begin
 begin
   Changing;
   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
   if Assigned(FAttributes) and (FAttributes.FList.Remove(OldAttr) > -1) then
   begin
   begin
-    Result := OldAttr;
     if Assigned(OldAttr.FNSI.QName) then  // safeguard
     if Assigned(OldAttr.FNSI.QName) then  // safeguard
       FAttributes.RestoreDefault(OldAttr.FNSI.QName^.Key);
       FAttributes.RestoreDefault(OldAttr.FNSI.QName^.Key);
+    Result.FOwnerElement := nil;
   end
   end
   else
   else
     raise EDOMNotFound.Create('Element.RemoveAttributeNode');
     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;
     FBuffer: PChar;
     FBufPos: PChar;
     FBufPos: PChar;
     FCapacity: Integer;
     FCapacity: Integer;
-    FLineBreak: string;
+    FLineBreak: WideString;
     FNSHelper: TNSSupport;
     FNSHelper: TNSSupport;
     FAttrFixups: TFPList;
     FAttrFixups: TFPList;
     FScratch: TFPList;
     FScratch: TFPList;
@@ -79,6 +79,7 @@ type
     procedure Write(const Buffer; Count: Longint); virtual; abstract;
     procedure Write(const Buffer; Count: Longint); virtual; abstract;
     procedure WriteNode(Node: TDOMNode);
     procedure WriteNode(Node: TDOMNode);
     procedure VisitDocument(Node: TDOMNode);
     procedure VisitDocument(Node: TDOMNode);
+    procedure VisitDocument_Canonical(Node: TDOMNode);
     procedure VisitElement(Node: TDOMNode);
     procedure VisitElement(Node: TDOMNode);
     procedure VisitText(Node: TDOMNode);
     procedure VisitText(Node: TDOMNode);
     procedure VisitCDATA(Node: TDOMNode);
     procedure VisitCDATA(Node: TDOMNode);
@@ -155,6 +156,16 @@ end;
     TXMLWriter
     TXMLWriter
   ---------------------------------------------------------------------}
   ---------------------------------------------------------------------}
 
 
+const
+  AttrSpecialChars = ['<', '"', '&', #9, #10, #13];
+  TextSpecialChars = ['<', '>', '&', #10, #13];
+  CDSectSpecialChars = [']'];
+  LineEndingChars = [#13, #10];
+  QuotStr = '&quot;';
+  AmpStr = '&amp;';
+  ltStr = '&lt;';
+  gtStr = '&gt;';
+
 constructor TXMLWriter.Create;
 constructor TXMLWriter.Create;
 var
 var
   I: Integer;
   I: Integer;
@@ -164,14 +175,22 @@ begin
   FBuffer := AllocMem(512+32);
   FBuffer := AllocMem(512+32);
   FBufPos := FBuffer;
   FBufPos := FBuffer;
   FCapacity := 512;
   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
   // Initialize Indent string
+  // TODO: this must be done in setter of FLineBreak
   SetLength(FIndent, 100);
   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;
   FIndentCount := 0;
-  // Later on, this may be put under user control
-  // for now, take OS setting
-  FLineBreak := sLineBreak;
   FNSHelper := TNSSupport.Create;
   FNSHelper := TNSSupport.Create;
   FScratch := TFPList.Create;
   FScratch := TFPList.Create;
   FNSDefs := TFPList.Create;
   FNSDefs := TFPList.Create;
@@ -215,14 +234,7 @@ begin
 
 
     wc := Cardinal(Src^);  Inc(Src);
     wc := Cardinal(Src^);  Inc(Src);
     case wc of
     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);
         pb^ := char(wc); Inc(pb);
       end;
       end;
 
 
@@ -275,7 +287,7 @@ end;
 
 
 procedure TXMLWriter.wrtIndent; { inline }
 procedure TXMLWriter.wrtIndent; { inline }
 begin
 begin
-  wrtChars(PWideChar(FIndent), FIndentCount*2+1);
+  wrtChars(PWideChar(FIndent), FIndentCount*2+Length(FLineBreak));
 end;
 end;
 
 
 procedure TXMLWriter.IncIndent;
 procedure TXMLWriter.IncIndent;
@@ -298,25 +310,6 @@ begin
   if FIndentCount>0 then dec(FIndentCount);
   if FIndentCount>0 then dec(FIndentCount);
 end;
 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;
 procedure TXMLWriter.ConvWrite(const s: WideString; const SpecialChars: TSetOfChar;
   const SpecialCharCallback: TSpecialCharCallback);
   const SpecialCharCallback: TSpecialCharCallback);
 var
 var
@@ -326,7 +319,7 @@ begin
   EndPos := 1;
   EndPos := 1;
   while EndPos <= Length(s) do
   while EndPos <= Length(s) do
   begin
   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
     begin
       wrtChars(@s[StartPos], EndPos - StartPos);
       wrtChars(@s[StartPos], EndPos - StartPos);
       SpecialCharCallback(Self, s, EndPos);
       SpecialCharCallback(Self, s, EndPos);
@@ -338,12 +331,6 @@ begin
     wrtChars(@s[StartPos], EndPos - StartPos);
     wrtChars(@s[StartPos], EndPos - StartPos);
 end;
 end;
 
 
-const
-  QuotStr = '&quot;';
-  AmpStr = '&amp;';
-  ltStr = '&lt;';
-  gtStr = '&gt;';
-
 procedure AttrSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
 procedure AttrSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
   var idx: Integer);
   var idx: Integer);
 begin
 begin
@@ -360,18 +347,74 @@ begin
   end;
   end;
 end;
 end;
 
 
-procedure TextnodeSpecialCharCallback(Sender: TXMLWriter; const s: DOMString;
+procedure TextnodeNormalCallback(Sender: TXMLWriter; const s: DOMString;
   var idx: Integer);
   var idx: Integer);
 begin
 begin
   case s[idx] of
   case s[idx] of
     '<': Sender.wrtStr(ltStr);
     '<': Sender.wrtStr(ltStr);
     '>': Sender.wrtStr(gtStr); // Required only in ']]>' literal, otherwise optional
     '>': Sender.wrtStr(gtStr); // Required only in ']]>' literal, otherwise optional
     '&': Sender.wrtStr(AmpStr);
     '&': 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
   else
     Sender.wrtChr(s[idx]);
     Sender.wrtChr(s[idx]);
   end;
   end;
 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);
 procedure TXMLWriter.WriteNode(node: TDOMNode);
 begin
 begin
   case node.NodeType of
   case node.NodeType of
@@ -382,7 +425,11 @@ begin
     ENTITY_REFERENCE_NODE:       VisitEntityRef(node);
     ENTITY_REFERENCE_NODE:       VisitEntityRef(node);
     PROCESSING_INSTRUCTION_NODE: VisitPI(node);
     PROCESSING_INSTRUCTION_NODE: VisitPI(node);
     COMMENT_NODE:                VisitComment(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);
     DOCUMENT_TYPE_NODE:          VisitDocumentType(node);
     ENTITY_NODE,
     ENTITY_NODE,
     DOCUMENT_FRAGMENT_NODE:      VisitFragment(node);
     DOCUMENT_FRAGMENT_NODE:      VisitFragment(node);
@@ -473,7 +520,7 @@ begin
         if Assigned(B) then  // drop redundant namespace declarations
         if Assigned(B) then  // drop redundant namespace declarations
           FNSDefs.Add(B);
           FNSDefs.Add(B);
       end
       end
-      else if TDOMAttr(node).Specified then
+      else if FCanonical or TDOMAttr(node).Specified then
       begin
       begin
         // obtain a TAttrFixup record (allocate if needed)
         // obtain a TAttrFixup record (allocate if needed)
         if j >= FAttrFixups.Count then
         if j >= FAttrFixups.Count then
@@ -557,7 +604,7 @@ begin
     for i := 0 to node.Attributes.Length - 1 do
     for i := 0 to node.Attributes.Length - 1 do
     begin
     begin
       child := node.Attributes.Item[i];
       child := node.Attributes.Item[i];
-      if TDOMAttr(child).Specified then
+      if FCanonical or TDOMAttr(child).Specified then
         VisitAttribute(child);
         VisitAttribute(child);
     end;
     end;
   Child := node.FirstChild;
   Child := node.FirstChild;
@@ -567,7 +614,7 @@ begin
   begin
   begin
     SavedInsideTextNode := FInsideTextNode;
     SavedInsideTextNode := FInsideTextNode;
     wrtChr('>');
     wrtChr('>');
-    FInsideTextNode := Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE];
+    FInsideTextNode := FCanonical or (Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]);
     IncIndent;
     IncIndent;
     repeat
     repeat
       WriteNode(Child);
       WriteNode(Child);
@@ -586,7 +633,7 @@ end;
 
 
 procedure TXMLWriter.VisitText(node: TDOMNode);
 procedure TXMLWriter.VisitText(node: TDOMNode);
 begin
 begin
-  ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeSpecialCharCallback);
+  ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, TextnodeCallbacks[FCanonical]);
 end;
 end;
 
 
 procedure TXMLWriter.VisitCDATA(node: TDOMNode);
 procedure TXMLWriter.VisitCDATA(node: TDOMNode);
@@ -594,11 +641,11 @@ begin
   if not FInsideTextNode then
   if not FInsideTextNode then
     wrtIndent;
     wrtIndent;
   if FCanonical then
   if FCanonical then
-    ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeSpecialCharCallback)
+    ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, @TextnodeCanonicalCallback)
   else
   else
   begin
   begin
     wrtChars('<![CDATA[', 9);
     wrtChars('<![CDATA[', 9);
-    wrtStr(TDOMCharacterData(node).Data);
+    ConvWrite(TDOMCharacterData(node).Data, CDSectSpecialChars, @CDSectSpecialCharCallback);
     wrtChars(']]>', 3);
     wrtChars(']]>', 3);
   end;
   end;
 end;
 end;
@@ -618,7 +665,8 @@ begin
   if TDOMProcessingInstruction(node).Data <> '' then
   if TDOMProcessingInstruction(node).Data <> '' then
   begin
   begin
     wrtChr(' ');
     wrtChr(' ');
-    wrtStr(TDOMProcessingInstruction(node).Data);
+    // TODO: How does this comply with c14n??
+    ConvWrite(TDOMProcessingInstruction(node).Data, LineEndingChars, @TextnodeNormalCallback);
   end;
   end;
   wrtStr('?>');
   wrtStr('?>');
 end;
 end;
@@ -627,7 +675,8 @@ procedure TXMLWriter.VisitComment(node: TDOMNode);
 begin
 begin
   if not FInsideTextNode then wrtIndent;
   if not FInsideTextNode then wrtIndent;
   wrtChars('<!--', 4);
   wrtChars('<!--', 4);
-  wrtStr(TDOMCharacterData(node).Data);
+  // TODO: How does this comply with c14n??
+  ConvWrite(TDOMCharacterData(node).Data, LineEndingChars, @TextnodeNormalCallback);
   wrtChars('-->', 3);
   wrtChars('-->', 3);
 end;
 end;
 
 
@@ -658,7 +707,8 @@ begin
   // TODO: now handled as a regular PI, remove this?
   // TODO: now handled as a regular PI, remove this?
   if Length(TXMLDocument(node).StylesheetType) > 0 then
   if Length(TXMLDocument(node).StylesheetType) > 0 then
   begin
   begin
-    wrtStr(#10'<?xml-stylesheet type="');
+    wrtStr(FLineBreak);
+    wrtStr('<?xml-stylesheet type="');
     wrtStr(TXMLDocument(node).StylesheetType);
     wrtStr(TXMLDocument(node).StylesheetType);
     wrtStr('" href="');
     wrtStr('" href="');
     wrtStr(TXMLDocument(node).StylesheetHRef);
     wrtStr(TXMLDocument(node).StylesheetHRef);
@@ -671,7 +721,37 @@ begin
     WriteNode(Child);
     WriteNode(Child);
     Child := Child.NextSibling;
     Child := Child.NextSibling;
   end;
   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;
 end;
 
 
 procedure TXMLWriter.VisitAttribute(Node: TDOMNode);
 procedure TXMLWriter.VisitAttribute(Node: TDOMNode);
@@ -697,7 +777,8 @@ end;
 
 
 procedure TXMLWriter.VisitDocumentType(Node: TDOMNode);
 procedure TXMLWriter.VisitDocumentType(Node: TDOMNode);
 begin
 begin
-  wrtStr(#10'<!DOCTYPE ');
+  wrtStr(FLineBreak);
+  wrtStr('<!DOCTYPE ');
   wrtStr(Node.NodeName);
   wrtStr(Node.NodeName);
   wrtChr(' ');
   wrtChr(' ');
   with TDOMDocumentType(Node) do
   with TDOMDocumentType(Node) do
@@ -717,7 +798,7 @@ begin
     if InternalSubset <> '' then
     if InternalSubset <> '' then
     begin
     begin
       wrtChr('[');
       wrtChr('[');
-      wrtStr(InternalSubset);
+      ConvWrite(InternalSubset, LineEndingChars, @TextnodeNormalCallback);
       wrtChr(']');
       wrtChr(']');
     end;
     end;
   end;
   end;

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

@@ -29,6 +29,7 @@ type
     procedure attr_ownership02;
     procedure attr_ownership02;
     procedure attr_ownership03;
     procedure attr_ownership03;
     procedure attr_ownership04;
     procedure attr_ownership04;
+    procedure attr_ownership05;
     procedure nsFixup1;
     procedure nsFixup1;
     procedure nsFixup2;
     procedure nsFixup2;
     procedure nsFixup3;
     procedure nsFixup3;
@@ -116,6 +117,24 @@ begin
   AssertEquals('ownerElement2', el, attr2.OwnerElement);
   AssertEquals('ownerElement2', el, attr2.OwnerElement);
 end;
 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
 const
   nsURI1 = 'http://www.example.com/ns1';
   nsURI1 = 'http://www.example.com/ns1';
   nsURI2 = 'http://www.example.com/ns2';
   nsURI2 = 'http://www.example.com/ns2';

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