Browse Source

* Patch from Sergei Gorelkin:
fcl-xml/src/dom.pp: resolved a number of Level 1 conformance issues:

* Node.Normalize always deletes empty text nodes
* Node.Normalize is recursive into Attributes
* Node.InsertBefore corrected exception code in case when RefChild is
not one of node's children
+ Node.InsertBefore added missing check for possible cycle in tree
+ Node.AppendChild and Node.InsertBefore added checking type of NewChild
+ CloneNode enabled for Fragment and Entity
- CloneNode deleted for DocumentType (w3 specs directly prohibit cloning
it between documents, and cloning within one document is claimed
'implementation specific' - but makes no sense).
+ Node.ImportNode is now working

* Uncommented Level 2 node properties (NamespaceURI, localName and
Prefix), this caused a name clash and a lot of function argument
renames.

fcl-xml/src/xmlutils.pp:

+ overloaded IsXmlName() that accepts PWideChars

fcl-xml/src/xmlconf.pp

* Applied a fix similar to xmlcfg.pp for Mantis #10554

fcl-xml/src/xmlread.pp:

* Major: Got errors reported at correct locations for all 1600+ negative
tests. Easy to say, but it required modifying almost every second
line of code.
* TContentParticle references an existing element definition instead of
storing its own name (this allows content model matching without
string comparisons).
* Resorted to old-style 'object' for TElementValidator and to plain
procedures for decoders (allows to drop almost all related memory
management).
* Moved parameter entity detection from char to token level, this
simplifies things a bit.
+ Added second level of buffering to input source (a step towards
supporting arbitrary encodings).
* The main parsing loop contains no implicit exception frames now.


fcl-xml/src/xmlwrite.pp

* Replaced the stupid indenting algorithm with a simple rule: "Do not
write indents adjacent to text nodes". Now it does not make a mess
out of the documents which were parsed with PreserveWhitespace=True.
* Use specialized node properties instead of generic ones, this
eliminates WideString copies and results in almost 2x performance
boost in Windows.
* Even more performance:
* Write line endings together with indents -> twice less calls.
* Increase slack in buffer and write strings with known length (i.e.
most of markup) without overflow checking.

fcl-xml/tests/xmlts.pp:

* Use parser options instead of dedicated procedure to 'canonicalize'
documents, the parser has become mature enough to do that.
* Fatal error in non-valid category is a test failure, as well as
validation error alone in not-wellformed category.

fcl-xml/src/README

* Brought a bit up to date

fcl-xml/tests/README

+ Added testsuite errata/issues

git-svn-id: trunk@10314 -

michael 17 years ago
parent
commit
77b38b6be5

+ 14 - 13
packages/fcl-xml/src/README

@@ -9,33 +9,34 @@ DOM level 2 extensions.
 
 
 XMLRead
 XMLRead
 -------
 -------
-Provides a simple XML reader, which can read XML data from a file or stream.
-This simple parser will be replaced by a much improved one soon, which will be
-able to handle different character encodings, namespaces and entity references.
-(This parser reads the file directly, i.e. it doesn't support Unicode or
-different charsets yet.)
+Provides an XML reader, which can read XML data from a file or stream.
+The parser can read files encoded in UTF-8, UTF-16 (both endianness),
+and ISO-8859-1. It supports DTD validation.
 Regarding entity references: The pre-defined entities "lt", "gt", "amp", "apos"
 Regarding entity references: The pre-defined entities "lt", "gt", "amp", "apos"
-and "quot" are replaced by their normal value during reading. Other entity
-references are stored as TDOMEntityReference nodes in the DOM tree.
-Regarding whitespace handling: Whitespace directly after the beginning of a
+and "quot", and internal entities declared in DTD, are replaced by their
+defined values during reading. Ability to resolve external entities is
+currently limited to the file system.
+Regarding whitespace handling: By default, whitespace directly after the beginning of a
 tag is discarded, and sections of the XML file which contain only whitespace and
 tag is discarded, and sections of the XML file which contain only whitespace and
-no other text content are discarded as well.
+no other text content are discarded as well. However, whitespace-preserving
+mode can be enabled by setting TDOMParser.Options.PreserveWhitespace property to
+True.
 
 
 
 
 XMLWrite
 XMLWrite
 --------
 --------
 Writes a DOM structure as XML data into a file or stream. It can deal both with
 Writes a DOM structure as XML data into a file or stream. It can deal both with
 XML files and XML fragments.
 XML files and XML fragments.
-At the moment it supports only the node types which can be read by XMLRead.
+At the moment it supports only the UTF-8 output endcoding.
 Please note that the writer replaces some characters by entity references
 Please note that the writer replaces some characters by entity references
 automatically:
 automatically:
 For normal text nodes, the following replacements will be done:
 For normal text nodes, the following replacements will be done:
 '<' => '&lt;'
 '<' => '&lt;'
 '>' => '&gt;'
 '>' => '&gt;'
 '&' => '&amp;'
 '&' => '&amp;'
-For attribute values, additionally '"' gets replaced by '&quot;'. Single
-apostrophes (') don't need to get converted, as values are already written using
-"" quotes.
+For attribute values, additionally '"' gets replaced by '&quot;', and characters
+#9, #10 and #13 are escaped using numerical references. Single apostrophes (')
+don't need to get converted, as values are already written using "" quotes.
 The XML reader (in xmlread.pp) will convert these entity references back to
 The XML reader (in xmlread.pp) will convert these entity references back to
 their original characters.
 their original characters.
 
 

+ 212 - 118
packages/fcl-xml/src/dom.pp

@@ -197,7 +197,7 @@ type
     FPreviousSibling, FNextSibling: TDOMNode;
     FPreviousSibling, FNextSibling: TDOMNode;
     FOwnerDocument: TDOMDocument;
     FOwnerDocument: TDOMDocument;
 
 
-    function  GetNodeName: DOMString; virtual;
+    function  GetNodeName: DOMString; virtual; abstract;
     function  GetNodeValue: DOMString; virtual;
     function  GetNodeValue: DOMString; virtual;
     procedure SetNodeValue(const AValue: DOMString); virtual;
     procedure SetNodeValue(const AValue: DOMString); virtual;
     function  GetFirstChild: TDOMNode; virtual;
     function  GetFirstChild: TDOMNode; virtual;
@@ -209,6 +209,8 @@ type
     procedure SetTextContent(const AValue: DOMString); virtual;
     procedure SetTextContent(const AValue: DOMString); virtual;
     function GetLocalName: DOMString; virtual;
     function GetLocalName: DOMString; virtual;
     function GetNamespaceURI: DOMString; virtual;
     function GetNamespaceURI: DOMString; virtual;
+    function GetPrefix: DOMString; virtual;
+    procedure SetPrefix(const Value: DOMString); virtual;
   public
   public
     constructor Create(AOwner: TDOMDocument);
     constructor Create(AOwner: TDOMDocument);
     destructor Destroy; override;
     destructor Destroy; override;
@@ -242,16 +244,11 @@ type
     function Supports(const Feature, Version: DOMString): Boolean;
     function Supports(const Feature, Version: DOMString): Boolean;
     *)
     *)
     function HasAttributes: Boolean; virtual;
     function HasAttributes: Boolean; virtual;
-    procedure Normalize;
+    procedure Normalize; virtual;
 
 
-    // always '' for nodes other than ELEMENT and ATTRIBUTE
-    // as well as for nodes created with DOM 1 methods
-    //property NamespaceURI: DOMString read GetNamespaceURI;
-    //property LocalName: DOMString read GetLocalName;
-    (*
-    // Prefix may only be changed if it was specified at creation time.
-    property Prefix: DOMString read FPrefix (write SetPrefix?);
-    *)
+    property NamespaceURI: DOMString read GetNamespaceURI;
+    property LocalName: DOMString read GetLocalName;
+    property Prefix: DOMString read GetPrefix write SetPrefix;
     // DOM level 3
     // DOM level 3
     property TextContent: DOMString read GetTextContent write SetTextContent;
     property TextContent: DOMString read GetTextContent write SetTextContent;
     // Extensions to DOM interface:
     // Extensions to DOM interface:
@@ -327,13 +324,14 @@ type
 
 
   TDOMNamedNodeMap = class(TObject)
   TDOMNamedNodeMap = class(TObject)
   protected
   protected
-    FOwnerElement: TDOMNode;
+    FOwner: TDOMNode;
     FNodeType: Integer;
     FNodeType: Integer;
     FList: TList;
     FList: TList;
     function GetItem(index: LongWord): TDOMNode;
     function GetItem(index: LongWord): TDOMNode;
     function GetLength: LongWord;
     function GetLength: LongWord;
     function Find(const name: DOMString; out Index: LongWord): Boolean;
     function Find(const name: DOMString; out Index: LongWord): Boolean;
     function InternalRemove(const name: DOMString): TDOMNode;
     function InternalRemove(const name: DOMString): TDOMNode;
+    function ValidateInsert(arg: TDOMNode): Integer;
   public
   public
     constructor Create(AOwner: TDOMNode; ANodeType: Integer);
     constructor Create(AOwner: TDOMNode; ANodeType: Integer);
     destructor Destroy; override;
     destructor Destroy; override;
@@ -399,6 +397,8 @@ type
   protected
   protected
     function GetNodeType: Integer; override;
     function GetNodeType: Integer; override;
     function GetNodeName: DOMString; override;
     function GetNodeName: DOMString; override;
+  public
+    function CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
   end;
   end;
 
 
 
 
@@ -445,12 +445,11 @@ type
 
 
     // DOM level 2 methods
     // DOM level 2 methods
     function ImportNode(ImportedNode: TDOMNode; Deep: Boolean): TDOMNode;
     function ImportNode(ImportedNode: TDOMNode; Deep: Boolean): TDOMNode;
-    function CreateElementNS(const NamespaceURI, QualifiedName: DOMString): TDOMElement;
-    function CreateAttributeNS(const NamespaceURI, QualifiedName: DOMString): TDOMAttr;
-    function GetElementsByTagNameNS(const namespaceURI, localName: DOMString): TDOMNodeList;
+    function CreateElementNS(const nsURI, QualifiedName: DOMString): TDOMElement;
+    function CreateAttributeNS(const nsURI, QualifiedName: DOMString): TDOMAttr;
+    function GetElementsByTagNameNS(const nsURI, alocalName: DOMString): TDOMNodeList;
     function GetElementById(const ElementID: DOMString): TDOMElement;
     function GetElementById(const ElementID: DOMString): TDOMElement;
     // Extensions to DOM interface:
     // Extensions to DOM interface:
-    // TODO: obsolete now, but must check for usage dependencies
     constructor Create;
     constructor Create;
     destructor Destroy; override;
     destructor Destroy; override;
     function AddID(Attr: TDOMAttr): Boolean;
     function AddID(Attr: TDOMAttr): Boolean;
@@ -492,9 +491,8 @@ type
   protected
   protected
     FName: DOMString;
     FName: DOMString;
     FOwnerElement: TDOMElement;
     FOwnerElement: TDOMElement;
-    // TODO: following 3 - replace with a link to AttDecl ??
-    // ('specified' isn't related...)
     FSpecified: Boolean;
     FSpecified: Boolean;
+    // TODO: following 2 - replace with a link to AttDecl ??    
     FDeclared: Boolean;
     FDeclared: Boolean;
     FDataType: TAttrDataType;
     FDataType: TAttrDataType;
     function  GetNodeValue: DOMString; override;
     function  GetNodeValue: DOMString; override;
@@ -528,6 +526,7 @@ type
     destructor Destroy; override;
     destructor Destroy; override;
     function  CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
     function  CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
     property  TagName: DOMString read FNodeName;
     property  TagName: DOMString read FNodeName;
+    procedure Normalize; override;
     function  GetAttribute(const name: DOMString): DOMString;
     function  GetAttribute(const name: DOMString): DOMString;
     procedure SetAttribute(const name, value: DOMString);
     procedure SetAttribute(const name, value: DOMString);
     procedure RemoveAttribute(const name: DOMString);
     procedure RemoveAttribute(const name: DOMString);
@@ -538,14 +537,14 @@ type
     function  GetElementsByTagName(const name: DOMString): TDOMNodeList;
     function  GetElementsByTagName(const name: DOMString): TDOMNodeList;
 
 
     // Introduced in DOM Level 2:
     // Introduced in DOM Level 2:
-    function GetAttributeNS(const namespaceURI, localName: DOMString): DOMString;
-    procedure SetAttributeNS(const namespaceURI, qualifiedName, value: DOMString);
-    procedure RemoveAttributeNS(const namespaceURI, localName: DOMString);
-    function GetAttributeNodeNS(const namespaceURI, localName: DOMString): TDOMAttr;
+    function GetAttributeNS(const nsURI, aLocalName: DOMString): DOMString;
+    procedure SetAttributeNS(const nsURI, qualifiedName, value: DOMString);
+    procedure RemoveAttributeNS(const nsURI, aLocalName: DOMString);
+    function GetAttributeNodeNS(const nsURI, aLocalName: DOMString): TDOMAttr;
     function SetAttributeNodeNS(newAttr: TDOMAttr): TDOMAttr;
     function SetAttributeNodeNS(newAttr: TDOMAttr): TDOMAttr;
-    function GetElementsByTagNameNS(const namespaceURI, localName: DOMString): TDOMNodeList;
+    function GetElementsByTagNameNS(const nsURI, aLocalName: DOMString): TDOMNodeList;
     function hasAttribute(const name: DOMString): Boolean;
     function hasAttribute(const name: DOMString): Boolean;
-    function hasAttributeNS(const namespaceURI, localName: DOMString): Boolean;
+    function hasAttributeNS(const nsURI, aLocalName: DOMString): Boolean;
     function HasAttributes: Boolean; override;
     function HasAttributes: Boolean; override;
     // extension
     // extension
     function CompareName(const name: DOMString): Integer; override;
     function CompareName(const name: DOMString): Integer; override;
@@ -619,7 +618,6 @@ type
     function GetElementDefs: TDOMNamedNodeMap;
     function GetElementDefs: TDOMNamedNodeMap;
   public
   public
     destructor Destroy; override;
     destructor Destroy; override;
-    function CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode; overload; override;
     property Name: DOMString read FName;
     property Name: DOMString read FName;
     property Entities: TDOMNamedNodeMap read GetEntities;
     property Entities: TDOMNamedNodeMap read GetEntities;
     property Notations: TDOMNamedNodeMap read GetNotations;
     property Notations: TDOMNamedNodeMap read GetNotations;
@@ -660,6 +658,7 @@ type
     function GetNodeType: Integer; override;
     function GetNodeType: Integer; override;
     function GetNodeName: DOMString; override;
     function GetNodeName: DOMString; override;
   public
   public
+    function CloneNode(deep: Boolean; aCloneOwner: TDOMDocument): TDOMNode; override;
     property PublicID: DOMString read FPublicID;
     property PublicID: DOMString read FPublicID;
     property SystemID: DOMString read FSystemID;
     property SystemID: DOMString read FSystemID;
     property NotationName: DOMString read FNotationName;
     property NotationName: DOMString read FNotationName;
@@ -820,11 +819,6 @@ begin
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
-function TDOMNode.GetNodeName: DOMString;
-begin
-  Result := '';
-end;
-
 function TDOMNode.GetNodeValue: DOMString;
 function TDOMNode.GetNodeValue: DOMString;
 begin
 begin
   Result := '';
   Result := '';
@@ -899,7 +893,8 @@ end;
 
 
 function TDOMNode.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 function TDOMNode.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 begin
 begin
-  raise EDOMNotSupported.CreateFmt('CloneNode not implemented for %s', [ClassName]);
+// !! CreateFmt() does not set Code property !!
+  raise EDOMNotSupported.Create(Format('Cloning/importing of %s is not supported', [ClassName]));
   Result:=nil;
   Result:=nil;
 end;
 end;
 
 
@@ -919,7 +914,6 @@ begin
   Result := False;
   Result := False;
 end;
 end;
 
 
-// DONE: moved to TDOMNode and implemented
 procedure TDOMNode.Normalize;
 procedure TDOMNode.Normalize;
 var
 var
   Child, tmp: TDOMNode;
   Child, tmp: TDOMNode;
@@ -932,19 +926,23 @@ begin
   begin
   begin
     if Child.NodeType = TEXT_NODE then
     if Child.NodeType = TEXT_NODE then
     begin
     begin
-      if Assigned(Txt) then
-      begin
-        tmp := Child.NextSibling;
-        Txt.AppendData(TDOMText(Child).Data);
-        Txt.FMayBeIgnorable := Txt.FMayBeIgnorable and TDOMText(Child).FMayBeIgnorable;
-        RemoveChild(Child);
-        Child := tmp;
-      end
-      else
+      tmp := Child.NextSibling;
+      if TDOMText(Child).Data <> '' then
       begin
       begin
-        Txt := TDOMText(Child);
-        Child := Child.NextSibling;
-      end
+        if Assigned(Txt) then
+        begin
+          Txt.AppendData(TDOMText(Child).Data);
+          Txt.FMayBeIgnorable := Txt.FMayBeIgnorable and TDOMText(Child).FMayBeIgnorable;
+        end
+        else
+        begin
+          Txt := TDOMText(Child);
+          Child := Child.NextSibling;
+          Continue;
+        end;
+      end;
+      Child.Free;
+      Child := tmp;
     end
     end
     else
     else
     begin
     begin
@@ -975,6 +973,16 @@ begin
   Result := '';
   Result := '';
 end;
 end;
 
 
+function TDOMNode.GetPrefix: DOMString;
+begin
+  Result := '';
+end;
+
+procedure TDOMNode.SetPrefix(const Value: DOMString);
+begin
+  // do nothing, override for Elements and Attributes
+end;
+
 function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
 function CompareDOMStrings(const s1, s2: DOMPChar; l1, l2: integer): integer;
 var i: integer;
 var i: integer;
 begin
 begin
@@ -1008,6 +1016,28 @@ begin
   Result := TDOMNode(ANode).CompareName(PDOMString(AKey)^);
   Result := TDOMNode(ANode).CompareName(PDOMString(AKey)^);
 end;
 end;
 
 
+type
+  TNodeTypeEnum = ELEMENT_NODE..NOTATION_NODE;
+  TNodeTypeSet = set of TNodeTypeEnum;
+
+const
+  stdChildren = [TEXT_NODE, ENTITY_REFERENCE_NODE, PROCESSING_INSTRUCTION_NODE,
+                 COMMENT_NODE, CDATA_SECTION_NODE, ELEMENT_NODE];
+
+  ValidChildren: array [TNodeTypeEnum] of TNodeTypeSet = (
+   stdChildren, { element }
+   [TEXT_NODE, ENTITY_REFERENCE_NODE], { attribute }
+   [], { text }
+   [], { cdata }
+   stdChildren, { ent ref }
+   stdChildren, { entity }
+   [], { pi }
+   [], { comment }
+   [ELEMENT_NODE, DOCUMENT_TYPE_NODE, PROCESSING_INSTRUCTION_NODE, COMMENT_NODE], { document }
+   [], { doctype }
+   stdChildren, { fragment }
+   []  { notation }
+  );
 
 
 function TDOMNode_WithChildren.GetFirstChild: TDOMNode;
 function TDOMNode_WithChildren.GetFirstChild: TDOMNode;
 begin
 begin
@@ -1030,6 +1060,7 @@ function TDOMNode_WithChildren.InsertBefore(NewChild, RefChild: TDOMNode):
   TDOMNode;
   TDOMNode;
 var
 var
   Tmp: TDOMNode;
   Tmp: TDOMNode;
+  NewChildType: Integer;
 begin
 begin
   Result := NewChild;
   Result := NewChild;
 
 
@@ -1043,15 +1074,24 @@ begin
     raise EDOMWrongDocument.Create('NodeWC.InsertBefore');
     raise EDOMWrongDocument.Create('NodeWC.InsertBefore');
 
 
   if RefChild.ParentNode <> Self then
   if RefChild.ParentNode <> Self then
-    raise EDOMHierarchyRequest.Create('NodeWC.InsertBefore');
+    raise EDOMNotFound.Create('NodeWC.InsertBefore');
 
 
-  Inc(FOwnerDocument.FRevision); // invalidate nodelists
+  NewChildType := NewChild.NodeType;
+  if not (NewChildType in [TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE]) and (NewChild.FirstChild <> nil) then
+  begin
+    Tmp := Self;
+    while Assigned(Tmp) do
+    begin
+      if Tmp = NewChild then
+        raise EDOMHierarchyRequest.Create('NodeWC.InsertBefore (cycle in tree)');
+      Tmp := Tmp.ParentNode;
+    end;
+  end;
 
 
-  if Assigned(NewChild.FParentNode) then
-    NewChild.FParentNode.DetachChild(NewChild);
+  Inc(FOwnerDocument.FRevision); // invalidate nodelists
 
 
   // DONE: Implemented InsertBefore for DocumentFragments (except ChildNodeTree)
   // DONE: Implemented InsertBefore for DocumentFragments (except ChildNodeTree)
-  if NewChild.NodeType = DOCUMENT_FRAGMENT_NODE then
+  if NewChildType = DOCUMENT_FRAGMENT_NODE then
   begin
   begin
     // Is fragment empty?
     // Is fragment empty?
     Tmp := NewChild.FirstChild;
     Tmp := NewChild.FirstChild;
@@ -1068,8 +1108,8 @@ begin
     if (RefChild = nil) or (RefChild.FPreviousSibling = nil) then
     if (RefChild = nil) or (RefChild.FPreviousSibling = nil) then
     begin  // insert at the beginning  <- AppendChild ??? ->
     begin  // insert at the beginning  <- AppendChild ??? ->
       // no, AppendChild will insert after RefChild and we need it before
       // no, AppendChild will insert after RefChild and we need it before
-      if Assigned(FirstChild) then
-        FirstChild.FPreviousSibling := NewChild.LastChild;
+      if Assigned(FFirstChild) then
+        FFirstChild.FPreviousSibling := NewChild.LastChild;
       NewChild.LastChild.FNextSibling := FirstChild;
       NewChild.LastChild.FNextSibling := FirstChild;
       if not Assigned(FLastChild) then
       if not Assigned(FLastChild) then
         FLastChild := NewChild.LastChild;
         FLastChild := NewChild.LastChild;
@@ -1089,6 +1129,12 @@ begin
     Exit;
     Exit;
   end;
   end;
 
 
+  if not (NewChildType in ValidChildren[NodeType]) then
+    raise EDOMHierarchyRequest.Create('NodeWC.InsertBefore');
+
+  if Assigned(NewChild.FParentNode) then
+    NewChild.FParentNode.DetachChild(NewChild);
+
   NewChild.FNextSibling := RefChild;
   NewChild.FNextSibling := RefChild;
   if RefChild = FFirstChild then
   if RefChild = FFirstChild then
     FFirstChild := NewChild
     FFirstChild := NewChild
@@ -1142,25 +1188,28 @@ end;
 function TDOMNode_WithChildren.AppendChild(NewChild: TDOMNode): TDOMNode;
 function TDOMNode_WithChildren.AppendChild(NewChild: TDOMNode): TDOMNode;
 var
 var
   Tmp: TDOMNode;
   Tmp: TDOMNode;
+  NewChildType: Integer;
 begin
 begin
   if NewChild.FOwnerDocument <> FOwnerDocument then
   if NewChild.FOwnerDocument <> FOwnerDocument then
     raise EDOMWrongDocument.Create('NodeWC.AppendChild');
     raise EDOMWrongDocument.Create('NodeWC.AppendChild');
 
 
-  Tmp := Self;
-  while Assigned(Tmp) do
+  // Don't walk the tree if NewChild apriori cannot be our parent.
+  // This saves a lot of CPU cache misses.
+  NewChildType := NewChild.NodeType;
+  if not (NewChildType in [TEXT_NODE, CDATA_SECTION_NODE, COMMENT_NODE, PROCESSING_INSTRUCTION_NODE]) and (NewChild.FirstChild <> nil) then
   begin
   begin
-    if Tmp = NewChild then
-      raise EDOMHierarchyRequest.Create('NodeWC.AppendChild (cycle in tree)');
-    Tmp := Tmp.ParentNode;
+    Tmp := Self;
+    while Assigned(Tmp) do
+    begin
+      if Tmp = NewChild then
+        raise EDOMHierarchyRequest.Create('NodeWC.AppendChild (cycle in tree)');
+      Tmp := Tmp.ParentNode;
+    end;
   end;
   end;
-
   Inc(FOwnerDocument.FRevision); // invalidate nodelists
   Inc(FOwnerDocument.FRevision); // invalidate nodelists
 
 
-  if Assigned(NewChild.FParentNode) then
-    NewChild.FParentNode.DetachChild(NewChild);
-
   // DONE: supported AppendChild for DocumentFragments (except ChildNodeTree)
   // DONE: supported AppendChild for DocumentFragments (except ChildNodeTree)
-  if NewChild.NodeType = DOCUMENT_FRAGMENT_NODE then
+  if NewChildType = DOCUMENT_FRAGMENT_NODE then
   begin
   begin
     Tmp := NewChild.FirstChild;
     Tmp := NewChild.FirstChild;
     // Is fragment empty?
     // Is fragment empty?
@@ -1187,6 +1236,12 @@ begin
   end
   end
   else
   else
   begin
   begin
+    if not (NewChildType in ValidChildren[NodeType]) then
+      raise EDOMHierarchyRequest.Create('NodeWC.AppendChild');
+
+    if Assigned(NewChild.FParentNode) then
+      NewChild.FParentNode.DetachChild(NewChild);
+
     if Assigned(FFirstChild) then
     if Assigned(FFirstChild) then
     begin
     begin
       FLastChild.FNextSibling := NewChild;
       FLastChild.FNextSibling := NewChild;
@@ -1243,7 +1298,7 @@ begin
   begin
   begin
     next := child.NextSibling;
     next := child.NextSibling;
     child.FParentNode := nil;
     child.FParentNode := nil;
-    child.Free;
+    child.Destroy;   // we know it's not nil, so save a call
     child := next;
     child := next;
   end;
   end;
   FFirstChild := nil;
   FFirstChild := nil;
@@ -1369,7 +1424,7 @@ begin
   while Assigned(Child) and (Child <> FNode) do
   while Assigned(Child) and (Child <> FNode) do
   begin
   begin
     if (Child.NodeType = ELEMENT_NODE) and (not UseFilter or (TDOMElement(Child).TagName = filter)) then
     if (Child.NodeType = ELEMENT_NODE) and (not UseFilter or (TDOMElement(Child).TagName = filter)) then
-      FList.Add(Child);
+          FList.Add(Child);
     // recursive track node hierarchy  
     // recursive track node hierarchy  
     if Assigned(Child.FirstChild) then
     if Assigned(Child.FirstChild) then
       Child := Child.FirstChild
       Child := Child.FirstChild
@@ -1395,7 +1450,7 @@ end;
 constructor TDOMNamedNodeMap.Create(AOwner: TDOMNode; ANodeType: Integer);
 constructor TDOMNamedNodeMap.Create(AOwner: TDOMNode; ANodeType: Integer);
 begin
 begin
   inherited Create;
   inherited Create;
-  FOwnerElement := AOwner;
+  FOwner := AOwner;
   FNodeType := ANodeType;
   FNodeType := ANodeType;
   FList := TList.Create;
   FList := TList.Create;
 end;
 end;
@@ -1461,28 +1516,40 @@ function TDOMNamedNodeMap.GetNamedItemNS(const namespaceURI, localName: DOMStrin
 begin
 begin
   // TODO: implement TDOMNamedNodeMap.GetNamedItemNS
   // TODO: implement TDOMNamedNodeMap.GetNamedItemNS
   raise EDOMNotSupported.Create('TDOMNamedNodeMap.GetNamedItemNS');
   raise EDOMNotSupported.Create('TDOMNamedNodeMap.GetNamedItemNS');
-  Result := nil;
+    Result := nil;
+end;
+
+function TDOMNamedNodeMap.ValidateInsert(arg: TDOMNode): Integer;
+var
+  AttrOwner: TDOMNode;
+begin
+  Result := 0;
+  if arg.FOwnerDocument <> FOwner.FOwnerDocument then
+    Result := WRONG_DOCUMENT_ERR
+  else if arg.NodeType <> FNodeType then
+    Result := HIERARCHY_REQUEST_ERR
+  else if (FNodeType = ATTRIBUTE_NODE) then
+  begin
+    AttrOwner := TDOMAttr(arg).ownerElement;
+    if Assigned(AttrOwner) and (AttrOwner <> FOwner) then
+      Result := INUSE_ATTRIBUTE_ERR;
+  end;
 end;
 end;
 
 
 function TDOMNamedNodeMap.SetNamedItem(arg: TDOMNode): TDOMNode;
 function TDOMNamedNodeMap.SetNamedItem(arg: TDOMNode): TDOMNode;
 var
 var
   i: Cardinal;
   i: Cardinal;
-  AttrOwner: TDOMElement;
   Exists: Boolean;
   Exists: Boolean;
+  res: Integer;
 begin
 begin
-  if arg.FOwnerDocument <> FOwnerElement.FOwnerDocument then
-    raise EDOMWrongDocument.Create('NamedNodeMap.SetNamedItem');
-
-  if arg.NodeType <> FNodeType then
-    raise EDOMHierarchyRequest.Create('NamedNodeMap.SetNamedItem');
+  res := ValidateInsert(arg);
+  if res <> 0 then
+    raise EDOMError.Create(res, 'NamedNodeMap.SetNamedItem');
 
 
   if FNodeType = ATTRIBUTE_NODE then
   if FNodeType = ATTRIBUTE_NODE then
   begin
   begin
-    AttrOwner := TDOMAttr(arg).ownerElement;
-    if Assigned(AttrOwner) and (AttrOwner <> FOwnerElement) then
-      raise EDOMInUseAttribute.Create('NamedNodeMap.SetNamedItem');
-    TDOMAttr(arg).FOwnerElement := TDOMElement(FOwnerElement);
-    Exists := Find(TDOMAttr(arg).FName, i); // optimization
+    TDOMAttr(arg).FOwnerElement := TDOMElement(FOwner);
+    Exists := Find(TDOMAttr(arg).Name, i); // optimization
   end
   end
   else
   else
     Exists := Find(arg.NodeName, i);
     Exists := Find(arg.NodeName, i);
@@ -1498,9 +1565,15 @@ begin
 end;
 end;
 
 
 function TDOMNamedNodeMap.SetNamedItemNS(arg: TDOMNode): TDOMNode;
 function TDOMNamedNodeMap.SetNamedItemNS(arg: TDOMNode): TDOMNode;
+var
+  res: Integer;
 begin
 begin
   // TODO: implement TDOMNamedNodeMap.SetNamedItemNS
   // TODO: implement TDOMNamedNodeMap.SetNamedItemNS
-  Result := nil;
+  res := ValidateInsert(arg);
+  if res <> 0 then
+    raise EDOMError.Create(res, 'NamedNodeMap.SetNamedItemNS');
+
+    Result := nil;
 end;
 end;
 
 
 function TDOMNamedNodeMap.InternalRemove(const name: DOMString): TDOMNode;
 function TDOMNamedNodeMap.InternalRemove(const name: DOMString): TDOMNode;
@@ -1601,6 +1674,13 @@ begin
   Result := '#document-fragment';
   Result := '#document-fragment';
 end;
 end;
 
 
+function TDOMDocumentFragment.CloneNode(deep: Boolean; aCloneOwner: TDOMDocument): TDOMNode;
+begin
+  Result := aCloneOwner.CreateDocumentFragment;
+  if deep then
+    CloneChildren(Result, aCloneOwner);
+end;
+
 // -------------------------------------------------------
 // -------------------------------------------------------
 //   DOMImplementation
 //   DOMImplementation
 // -------------------------------------------------------
 // -------------------------------------------------------
@@ -1641,17 +1721,22 @@ var
   Root: TDOMNode;
   Root: TDOMNode;
 begin
 begin
   // TODO: This method is not usable yet due to CreateElementNS...
   // TODO: This method is not usable yet due to CreateElementNS...
-  Result := TDOMDocument.Create;
+  Result := TXMLDocument.Create;
   Result.FImplementation := Self;
   Result.FImplementation := Self;
-  if Assigned(doctype) then
-  begin
-    if Assigned(doctype.OwnerDocument) then
-      raise EDOMWrongDocument.Create('DOMImplementation.CreateDocument');
-    Doctype.FOwnerDocument := Result;
-    Result.AppendChild(doctype);
-  end;  
-  Root := Result.CreateElementNS(NamespaceURI, QualifiedName);
-  Result.AppendChild(Root);
+  try
+    if Assigned(doctype) then
+    begin
+      if Assigned(doctype.OwnerDocument) then
+        raise EDOMWrongDocument.Create('Implementation.CreateDocument');
+      Doctype.FOwnerDocument := Result;
+      Result.AppendChild(doctype);
+    end;
+    Root := Result.CreateElementNS(NamespaceURI, QualifiedName);
+    Result.AppendChild(Root);
+  except
+    Result.Free;
+    raise;
+  end;
 end;
 end;
 
 
 
 
@@ -1867,12 +1952,12 @@ begin
   Result := TDOMElementList.Create(Self, tagname);
   Result := TDOMElementList.Create(Self, tagname);
 end;
 end;
 
 
-function TDOMDocument.GetElementsByTagNameNS(const namespaceURI, localName: DOMString): TDOMNodeList;
+function TDOMDocument.GetElementsByTagNameNS(const nsURI, aLocalName: DOMString): TDOMNodeList;
 begin
 begin
-  Result := TDOMElementList.Create(Self, namespaceURI, localName);
+  Result := TDOMElementList.Create(Self, nsURI, aLocalName);
 end;
 end;
 
 
-function TDOMDocument.CreateAttributeNS(const NamespaceURI,
+function TDOMDocument.CreateAttributeNS(const nsURI,
   QualifiedName: DOMString): TDOMAttr;
   QualifiedName: DOMString): TDOMAttr;
 begin
 begin
   // TODO: Implement TDOMDocument.CreateAttributeNS
   // TODO: Implement TDOMDocument.CreateAttributeNS
@@ -1880,7 +1965,7 @@ begin
   Result := nil;
   Result := nil;
 end;
 end;
 
 
-function TDOMDocument.CreateElementNS(const NamespaceURI,
+function TDOMDocument.CreateElementNS(const nsURI,
   QualifiedName: DOMString): TDOMElement;
   QualifiedName: DOMString): TDOMElement;
 begin
 begin
   // TODO: Implement TDOMDocument.CreateElementNS
   // TODO: Implement TDOMDocument.CreateElementNS
@@ -1895,21 +1980,19 @@ begin
   if Assigned(FIDList) and FindID(ElementID, I) then
   if Assigned(FIDList) and FindID(ElementID, I) then
     Result := PIDItem(FIDList.List^[I])^.Element
     Result := PIDItem(FIDList.List^[I])^.Element
   else
   else
-    Result := nil;
+  Result := nil;
 end;
 end;
 
 
 function TDOMDocument.ImportNode(ImportedNode: TDOMNode;
 function TDOMDocument.ImportNode(ImportedNode: TDOMNode;
   Deep: Boolean): TDOMNode;
   Deep: Boolean): TDOMNode;
 begin
 begin
-  // TODO: Implement TDOMDocument.ImportNode
-  raise EDOMNotSupported.Create('TDOMDocument.ImportNode');
-  Result := nil;
+  Result := ImportedNode.CloneNode(Deep, Self);
 end;
 end;
 
 
 function TDOMDocument.IndexOfNS(const nsURI: DOMString): Integer;
 function TDOMDocument.IndexOfNS(const nsURI: DOMString): Integer;
 begin
 begin
   // TODO: implement
   // TODO: implement
-  Result := -1;
+    Result := -1;
 end;
 end;
 
 
 
 
@@ -1937,6 +2020,7 @@ begin
     raise EDOMError.Create(INVALID_CHARACTER_ERR, 'XMLDocument.CreateEntityReference');
     raise EDOMError.Create(INVALID_CHARACTER_ERR, 'XMLDocument.CreateEntityReference');
   Result := TDOMEntityReference.Create(Self);
   Result := TDOMEntityReference.Create(Self);
   Result.FName := name;
   Result.FName := name;
+  // TODO: if entity is known, its child list must be cloned or so.
 end;
 end;
 
 
 procedure TXMLDocument.SetXMLVersion(const aValue: DOMString);
 procedure TXMLDocument.SetXMLVersion(const aValue: DOMString);
@@ -2024,6 +2108,16 @@ begin
     CloneChildren(Result, ACloneOwner);
     CloneChildren(Result, ACloneOwner);
 end;
 end;
 
 
+procedure TDOMElement.Normalize;
+var
+  I: Integer;
+begin
+  if Assigned(FAttributes) then
+    for I := 0 to FAttributes.Length - 1 do
+      FAttributes[I].Normalize;
+  inherited Normalize;    
+end;
+
 function TDOMElement.GetAttributes: TDOMNamedNodeMap;
 function TDOMElement.GetAttributes: TDOMNamedNodeMap;
 begin
 begin
   if FAttributes=nil then
   if FAttributes=nil then
@@ -2044,14 +2138,14 @@ begin
   end;
   end;
 end;
 end;
 
 
-function TDOMElement.GetAttributeNS(const namespaceURI, localName: DOMString): DOMString;
+function TDOMElement.GetAttributeNS(const nsURI, aLocalName: DOMString): DOMString;
 var
 var
   Attr: TDOMNode;
   Attr: TDOMNode;
 begin
 begin
   SetLength(Result, 0);
   SetLength(Result, 0);
   if Assigned(FAttributes) then
   if Assigned(FAttributes) then
   begin
   begin
-    Attr := FAttributes.GetNamedItemNS(namespaceURI, localName);
+    Attr := FAttributes.GetNamedItemNS(nsURI, aLocalName);
     if Assigned(Attr) then
     if Assigned(Attr) then
       Result := Attr.NodeValue;
       Result := Attr.NodeValue;
   end;
   end;
@@ -2079,22 +2173,22 @@ begin
     FAttributes.InternalRemove(name).Free;
     FAttributes.InternalRemove(name).Free;
 end;
 end;
 
 
-procedure TDOMElement.RemoveAttributeNS(const namespaceURI,
-  localName: DOMString);
+procedure TDOMElement.RemoveAttributeNS(const nsURI,
+  aLocalName: DOMString);
 begin
 begin
   // TODO: Implement TDOMElement.RemoveAttributeNS
   // TODO: Implement TDOMElement.RemoveAttributeNS
   raise EDOMNotSupported.Create('TDOMElement.RemoveAttributeNS');
   raise EDOMNotSupported.Create('TDOMElement.RemoveAttributeNS');
 end;
 end;
 
 
-procedure TDOMElement.SetAttributeNS(const namespaceURI, qualifiedName,
+procedure TDOMElement.SetAttributeNS(const nsURI, qualifiedName,
   value: DOMString);
   value: DOMString);
 var
 var
   Attr: TDOMAttr;
   Attr: TDOMAttr;
 begin
 begin
-  Attr := Attributes.GetNamedItemNS(namespaceURI, qualifiedName) as TDOMAttr;
+  Attr := Attributes.GetNamedItemNS(nsURI, qualifiedName) as TDOMAttr;
   if attr = nil then
   if attr = nil then
   begin
   begin
-    attr := FOwnerDocument.CreateAttributeNS(namespaceURI, qualifiedName);
+    attr := FOwnerDocument.CreateAttributeNS(nsURI, qualifiedName);
     // TODO 5: keep sorted!
     // TODO 5: keep sorted!
     FAttributes.FList.Add(attr);
     FAttributes.FList.Add(attr);
   end;
   end;
@@ -2110,10 +2204,10 @@ begin
     Result := nil;
     Result := nil;
 end;
 end;
 
 
-function TDOMElement.GetAttributeNodeNS(const namespaceURI, localName: DOMString): TDOMAttr;
+function TDOMElement.GetAttributeNodeNS(const nsURI, aLocalName: DOMString): TDOMAttr;
 begin
 begin
   if Assigned(FAttributes) then
   if Assigned(FAttributes) then
-    Result := FAttributes.GetNamedItemNS(namespaceURI, localName) as TDOMAttr
+    Result := FAttributes.GetNamedItemNS(nsURI, aLocalName) as TDOMAttr
   else
   else
     Result := nil;
     Result := nil;
 end;
 end;
@@ -2154,9 +2248,9 @@ begin
   Result := TDOMElementList.Create(Self, name);
   Result := TDOMElementList.Create(Self, name);
 end;
 end;
 
 
-function TDOMElement.GetElementsByTagNameNS(const namespaceURI, localName: DOMString): TDOMNodeList;
+function TDOMElement.GetElementsByTagNameNS(const nsURI, aLocalName: DOMString): TDOMNodeList;
 begin
 begin
-  Result := TDOMElementList.Create(Self, namespaceURI, localName);
+  Result := TDOMElementList.Create(Self, nsURI, aLocalName);
 end;
 end;
 
 
 function TDOMElement.hasAttribute(const name: DOMString): Boolean;
 function TDOMElement.hasAttribute(const name: DOMString): Boolean;
@@ -2165,10 +2259,10 @@ begin
     Assigned(FAttributes.GetNamedItem(name));
     Assigned(FAttributes.GetNamedItem(name));
 end;
 end;
 
 
-function TDOMElement.hasAttributeNS(const namespaceURI, localName: DOMString): Boolean;
+function TDOMElement.hasAttributeNS(const nsURI, aLocalName: DOMString): Boolean;
 begin
 begin
   Result := Assigned(FAttributes) and
   Result := Assigned(FAttributes) and
-    Assigned(FAttributes.getNamedItemNS(namespaceURI, localName));
+    Assigned(FAttributes.getNamedItemNS(nsURI, aLocalName));
 end;
 end;
 
 
 function TDOMElement.HasAttributes: Boolean;
 function TDOMElement.HasAttributes: Boolean;
@@ -2283,16 +2377,6 @@ begin
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
-function TDOMDocumentType.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
-begin
-  Result := TDOMDocumentType.Create(ACloneOwner);
-  TDOMDocumentType(Result).FName := FName;
-  TDOMDocumentType(Result).FSystemID := FSystemID;
-  TDOMDocumentType(Result).FPublicID := FPublicID;
-  // ignore deep - DocumentType cannot have children
-  // TODO: Clone Entities and Notations 
-end;
-
 function TDOMDocumentType.GetEntities: TDOMNamedNodeMap;
 function TDOMDocumentType.GetEntities: TDOMNamedNodeMap;
 begin
 begin
   if FEntities = nil then
   if FEntities = nil then
@@ -2352,6 +2436,15 @@ begin
   Result := FName;
   Result := FName;
 end;
 end;
 
 
+function TDOMEntity.CloneNode(deep: Boolean; aCloneOwner: TDOMDocument): TDOMNode;
+begin
+  Result := TDOMEntity.Create(aCloneOwner);
+  TDOMEntity(Result).FName := FName;
+  TDOMEntity(Result).FSystemID := FSystemID;
+  TDOMEntity(Result).FPublicID := FPublicID;
+  TDOMEntity(Result).FNotationName := FNotationName;  
+end;
+
 // -------------------------------------------------------
 // -------------------------------------------------------
 //   EntityReference
 //   EntityReference
 // -------------------------------------------------------
 // -------------------------------------------------------
@@ -2369,6 +2462,7 @@ end;
 function TDOMEntityReference.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 function TDOMEntityReference.CloneNode(deep: Boolean; ACloneOwner: TDOMDocument): TDOMNode;
 begin
 begin
   Result := ACloneOwner.CreateEntityReference(FName);
   Result := ACloneOwner.CreateEntityReference(FName);
+  // TODO -cConformance: this is done in CreateEntityReference?
   CloneChildren(Result, ACloneOwner);
   CloneChildren(Result, ACloneOwner);
 end;
 end;
 
 

+ 5 - 4
packages/fcl-xml/src/xmlconf.pp

@@ -124,7 +124,7 @@ end;
 
 
 procedure TXMLConfig.Flush;
 procedure TXMLConfig.Flush;
 begin
 begin
-  if Modified then
+  if Modified and (Filename <> '') then
   begin
   begin
     WriteXMLFile(Doc, Filename);
     WriteXMLFile(Doc, Filename);
     FModified := False;
     FModified := False;
@@ -348,14 +348,15 @@ procedure TXMLConfig.DoSetFilename(const AFilename: String; ForceReload: Boolean
 begin
 begin
   if (not ForceReload) and (FFilename = AFilename) then
   if (not ForceReload) and (FFilename = AFilename) then
     exit;
     exit;
+    
+  Flush;
+  FreeAndNil(Doc);
+    
   FFilename := AFilename;
   FFilename := AFilename;
 
 
   if csLoading in ComponentState then
   if csLoading in ComponentState then
     exit;
     exit;
 
 
-  Flush;
-  FreeAndNil(Doc);
-
   if FileExists(AFilename) and not FStartEmpty then
   if FileExists(AFilename) and not FStartEmpty then
     ReadXMLFile(Doc, AFilename);
     ReadXMLFile(Doc, AFilename);
 
 

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


+ 22 - 5
packages/fcl-xml/src/xmlutils.pp

@@ -22,7 +22,8 @@ interface
 uses
 uses
   SysUtils;
   SysUtils;
 
 
-function IsXmlName(const Value: WideString; Xml11: Boolean = False): Boolean;
+function IsXmlName(const Value: WideString; Xml11: Boolean = False): Boolean; overload;
+function IsXmlName(Value: PWideChar; Len: Integer; Xml11: Boolean = False): Boolean; overload;
 function IsXmlNames(const Value: WideString; Xml11: Boolean = False): Boolean;
 function IsXmlNames(const Value: WideString; Xml11: Boolean = False): Boolean;
 function IsXmlNmToken(const Value: WideString; Xml11: Boolean = False): Boolean;
 function IsXmlNmToken(const Value: WideString; Xml11: Boolean = False): Boolean;
 function IsXmlNmTokens(const Value: WideString; Xml11: Boolean = False): Boolean;
 function IsXmlNmTokens(const Value: WideString; Xml11: Boolean = False): Boolean;
@@ -64,7 +65,18 @@ begin
   Result := Xml11Pg;
   Result := Xml11Pg;
 end;
 end;
 
 
-function IsXml11Char(const Value: WideString; var Index: Integer): Boolean;
+function IsXml11Char(Value: PWideChar; var Index: Integer): Boolean; overload;
+begin
+  if (Value[Index] >= #$D800) and (Value[Index] <= #$DB7F) then
+  begin
+    Inc(Index);
+    Result := (Value[Index] >= #$DC00) and (Value[Index] <= #$DFFF);
+  end
+  else
+    Result := False;
+end;
+
+function IsXml11Char(const Value: WideString; var Index: Integer): Boolean; overload;
 begin
 begin
   if (Value[Index] >= #$D800) and (Value[Index] <= #$DB7F) then
   if (Value[Index] >= #$D800) and (Value[Index] <= #$DB7F) then
   begin
   begin
@@ -76,6 +88,11 @@ begin
 end;
 end;
 
 
 function IsXmlName(const Value: WideString; Xml11: Boolean): Boolean;
 function IsXmlName(const Value: WideString; Xml11: Boolean): Boolean;
+begin
+  Result := IsXmlName(PWideChar(Value), Length(Value), Xml11);
+end;
+
+function IsXmlName(Value: PWideChar; Len: Integer; Xml11: Boolean = False): Boolean; overload;
 var
 var
   Pages: PByteArray;
   Pages: PByteArray;
   I: Integer;
   I: Integer;
@@ -86,12 +103,12 @@ begin
   else
   else
     Pages := @NamePages;
     Pages := @NamePages;
 
 
-  I := 1;
-  if (Value = '') or not ((Byte(Value[I]) in NamingBitmap[Pages^[hi(Word(Value[I]))]]) or
+  I := 0;
+  if (Len = 0) or not ((Byte(Value[I]) in NamingBitmap[Pages^[hi(Word(Value[I]))]]) or
     (Xml11 and IsXml11Char(Value, I))) then
     (Xml11 and IsXml11Char(Value, I))) then
       Exit;
       Exit;
   Inc(I);
   Inc(I);
-  while I <= Length(Value) do
+  while I < Len do
   begin
   begin
     if not ((Byte(Value[I]) in NamingBitmap[Pages^[$100+hi(Word(Value[I]))]]) or
     if not ((Byte(Value[I]) in NamingBitmap[Pages^[$100+hi(Word(Value[I]))]]) or
       (Xml11 and IsXml11Char(Value, I))) then
       (Xml11 and IsXml11Char(Value, I))) then

+ 51 - 74
packages/fcl-xml/src/xmlwrite.pp

@@ -56,7 +56,6 @@ type
     procedure DecIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure DecIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure wrtStr(const ws: WideString); {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure wrtStr(const ws: WideString); {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure wrtChr(c: WideChar); {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure wrtChr(c: WideChar); {$IFDEF HAS_INLINE} inline; {$ENDIF}
-    procedure wrtLineEnd; {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure wrtIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure wrtIndent; {$IFDEF HAS_INLINE} inline; {$ENDIF}
     procedure wrtQuotedLiteral(const ws: WideString);
     procedure wrtQuotedLiteral(const ws: WideString);
     procedure ConvWrite(const s: WideString; const SpecialChars: TSetOfChar;
     procedure ConvWrite(const s: WideString; const SpecialChars: TSetOfChar;
@@ -154,7 +153,8 @@ begin
   FCapacity := 512;
   FCapacity := 512;
   // Initialize Indent string
   // Initialize Indent string
   SetLength(FIndent, 100);
   SetLength(FIndent, 100);
-  for I := 1 to 100 do FIndent[I] := ' ';
+  FIndent[1] := #10;
+  for I := 2 to 100 do FIndent[I] := ' ';
   FIndentCount := 0;
   FIndentCount := 0;
   // Later on, this may be put under user control
   // Later on, this may be put under user control
   // for now, take OS setting
   // for now, take OS setting
@@ -175,7 +175,6 @@ var
   pb: PChar;
   pb: PChar;
   wc: Cardinal;
   wc: Cardinal;
   SrcEnd: PWideChar;
   SrcEnd: PWideChar;
-  I: Integer;
 begin
 begin
   pb := FBufPos;
   pb := FBufPos;
   SrcEnd := Src + Length;
   SrcEnd := Src + Length;
@@ -191,30 +190,29 @@ begin
 
 
     wc := Cardinal(Src^);  Inc(Src);
     wc := Cardinal(Src^);  Inc(Src);
     case wc of
     case wc of
-      $0A:  for I := 1 to System.Length(FLineBreak) do
-            begin
-              pb^ := FLineBreak[I]; Inc(pb);
-            end;
+      $0A: pb := StrECopy(pb, PChar(FLineBreak));
 
 
       0..$09, $0B..$7F:  begin
       0..$09, $0B..$7F:  begin
         pb^ := char(wc); Inc(pb);
         pb^ := char(wc); Inc(pb);
       end;
       end;
 
 
       $80..$7FF: begin
       $80..$7FF: begin
-        pb^ := Char($C0 or (wc shr 6));   Inc(pb);
-        pb^ := Char($80 or (wc and $3F)); Inc(pb);
+        pb^ := Char($C0 or (wc shr 6));
+        pb[1] := Char($80 or (wc and $3F));
+        Inc(pb,2);
       end;
       end;
 
 
       $D800..$DBFF: begin
       $D800..$DBFF: begin
         if (Src < SrcEnd) and (Src^ >= #$DC00) and (Src^ <= #$DFFF) then
         if (Src < SrcEnd) and (Src^ >= #$DC00) and (Src^ <= #$DFFF) then
         begin
         begin
-          wc := ((wc - $D7C0) shl 10) + (word(Src^) xor $DC00);
+          wc := ((LongInt(wc) - $D7C0) shl 10) + LongInt(word(Src^) xor $DC00);
           Inc(Src);
           Inc(Src);
 
 
-          pb^ := Char($F0 or (wc shr 18));           Inc(pb);
-          pb^ := Char($80 or ((wc shr 12) and $3F)); Inc(pb);
-          pb^ := Char($80 or ((wc shr 6) and $3F));  Inc(pb);
-          pb^ := Char($80 or (wc and $3F));          Inc(pb);
+          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
         end
         else
         else
           raise EConvertError.Create('High surrogate without low one');
           raise EConvertError.Create('High surrogate without low one');
@@ -223,9 +221,10 @@ begin
         raise EConvertError.Create('Low surrogate without high one');
         raise EConvertError.Create('Low surrogate without high one');
       else   // $800 >= wc > $FFFF, excluding surrogates
       else   // $800 >= wc > $FFFF, excluding surrogates
       begin
       begin
-        pb^ := Char($E0 or (wc shr 12));          Inc(pb);
-        pb^ := Char($80 or ((wc shr 6) and $3F)); Inc(pb);
-        pb^ := Char($80 or (wc and $3F));         Inc(pb);
+        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;
     end;
   end;
   end;
@@ -237,20 +236,16 @@ begin
   wrtChars(PWideChar(ws), Length(ws));
   wrtChars(PWideChar(ws), Length(ws));
 end;
 end;
 
 
+{ No checks here - buffer always has 32 extra bytes }
 procedure TXMLWriter.wrtChr(c: WideChar); { inline }
 procedure TXMLWriter.wrtChr(c: WideChar); { inline }
 begin
 begin
-  wrtChars(@c,1);
-end;
-
-procedure TXMLWriter.wrtLineEnd; { inline }
-begin
-  // line endings now handled in WrtStr!
-  wrtChr(#10);
+  FBufPos^ := char(ord(c));
+  Inc(FBufPos);
 end;
 end;
 
 
 procedure TXMLWriter.wrtIndent; { inline }
 procedure TXMLWriter.wrtIndent; { inline }
 begin
 begin
-  wrtChars(PWideChar(FIndent), FIndentCount*2);
+  wrtChars(PWideChar(FIndent), FIndentCount*2+1);
 end;
 end;
 
 
 procedure TXMLWriter.IncIndent;
 procedure TXMLWriter.IncIndent;
@@ -366,75 +361,56 @@ end;
 procedure TXMLWriter.VisitElement(node: TDOMNode);
 procedure TXMLWriter.VisitElement(node: TDOMNode);
 var
 var
   i: Integer;
   i: Integer;
-  attr, child: TDOMNode;
+  child: TDOMNode;
   SavedInsideTextNode: Boolean;
   SavedInsideTextNode: Boolean;
-  IsLeaf: Boolean;
-  MixedContent: Boolean;
 begin
 begin
   if not FInsideTextNode then
   if not FInsideTextNode then
     wrtIndent;
     wrtIndent;
   wrtChr('<');
   wrtChr('<');
-  wrtStr(node.NodeName);
+  wrtStr(TDOMElement(node).TagName);
   // FIX: Accessing Attributes was causing them to be created for every element :(
   // FIX: Accessing Attributes was causing them to be created for every element :(
   if node.HasAttributes then
   if node.HasAttributes then
     for i := 0 to node.Attributes.Length - 1 do
     for i := 0 to node.Attributes.Length - 1 do
     begin
     begin
-      attr := node.Attributes.Item[i];
-      if TDOMAttr(attr).Specified then
-        VisitAttribute(attr);
+      child := node.Attributes.Item[i];
+      if TDOMAttr(child).Specified then
+        VisitAttribute(child);
     end;
     end;
   Child := node.FirstChild;
   Child := node.FirstChild;
   if Child = nil then
   if Child = nil then
-    wrtStr('/>')
+    wrtChars('/>', 2)
   else
   else
   begin
   begin
     SavedInsideTextNode := FInsideTextNode;
     SavedInsideTextNode := FInsideTextNode;
     wrtChr('>');
     wrtChr('>');
-    MixedContent := False;
-    repeat
-      if Assigned(Child.PreviousSibling) and
-        (Child.PreviousSibling.InheritsFrom(TDOMText) <> Child.InheritsFrom(TDOMText)) then
-        MixedContent := True;
-      Child := Child.NextSibling;
-    until Child = nil;
-    Child := node.FirstChild; // restore
-
-    IsLeaf := (Child = node.LastChild) and (Child.FirstChild = nil);
-    if not (FInsideTextNode or MixedContent or IsLeaf) then
-      wrtLineEnd;
-
-    FInsideTextNode := {FInsideTextNode or} MixedContent or IsLeaf;
+    FInsideTextNode := Child.NodeType in [TEXT_NODE, CDATA_SECTION_NODE];
     IncIndent;
     IncIndent;
     repeat
     repeat
       WriteNode(Child);
       WriteNode(Child);
       Child := Child.NextSibling;
       Child := Child.NextSibling;
     until Child = nil;
     until Child = nil;
     DecIndent;
     DecIndent;
-    if not FInsideTextNode then
+    if not (node.LastChild.NodeType in [TEXT_NODE, CDATA_SECTION_NODE]) then
       wrtIndent;
       wrtIndent;
     FInsideTextNode := SavedInsideTextNode;
     FInsideTextNode := SavedInsideTextNode;
-    wrtStr('</');
-    wrtStr(Node.NodeName);
+    wrtChars('</', 2);
+    wrtStr(TDOMElement(Node).TagName);
     wrtChr('>');
     wrtChr('>');
   end;
   end;
-  if not FInsideTextNode then
-    wrtLineEnd;
 end;
 end;
 
 
 procedure TXMLWriter.VisitText(node: TDOMNode);
 procedure TXMLWriter.VisitText(node: TDOMNode);
 begin
 begin
-  ConvWrite(node.NodeValue, TextSpecialChars, {$IFDEF FPC}@{$ENDIF}TextnodeSpecialCharCallback);
+  ConvWrite(TDOMCharacterData(node).Data, TextSpecialChars, {$IFDEF FPC}@{$ENDIF}TextnodeSpecialCharCallback);
 end;
 end;
 
 
 procedure TXMLWriter.VisitCDATA(node: TDOMNode);
 procedure TXMLWriter.VisitCDATA(node: TDOMNode);
 begin
 begin
   if not FInsideTextNode then
   if not FInsideTextNode then
     wrtIndent;
     wrtIndent;
-  wrtStr('<![CDATA[');
-  wrtStr(node.NodeValue);
-  wrtStr(']]>');
-  if not FInsideTextNode then
-    wrtLineEnd;
+  wrtChars('<![CDATA[', 9);
+  wrtStr(TDOMCharacterData(node).Data);
+  wrtChars(']]>', 3);
 end;
 end;
 
 
 procedure TXMLWriter.VisitEntityRef(node: TDOMNode);
 procedure TXMLWriter.VisitEntityRef(node: TDOMNode);
@@ -452,16 +428,14 @@ begin
   wrtChr(' ');
   wrtChr(' ');
   wrtStr(TDOMProcessingInstruction(node).Data);
   wrtStr(TDOMProcessingInstruction(node).Data);
   wrtStr('?>');
   wrtStr('?>');
-  if not FInsideTextNode then wrtLineEnd;
 end;
 end;
 
 
 procedure TXMLWriter.VisitComment(node: TDOMNode);
 procedure TXMLWriter.VisitComment(node: TDOMNode);
 begin
 begin
   if not FInsideTextNode then wrtIndent;
   if not FInsideTextNode then wrtIndent;
-  wrtStr('<!--');
-  wrtStr(node.NodeValue);
-  wrtStr('-->');
-  if not FInsideTextNode then wrtLineEnd;
+  wrtChars('<!--', 4);
+  wrtStr(TDOMCharacterData(node).Data);
+  wrtChars('-->', 3);
 end;
 end;
 
 
 procedure TXMLWriter.VisitDocument(node: TDOMNode);
 procedure TXMLWriter.VisitDocument(node: TDOMNode);
@@ -486,16 +460,16 @@ begin
     wrtChr('"');
     wrtChr('"');
   end;
   end;
 *)
 *)
-  wrtStr('?>'#10);
+  wrtStr('?>');
 
 
   // 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('<?xml-stylesheet type="');
+    wrtStr(#10'<?xml-stylesheet type="');
     wrtStr(TXMLDocument(node).StylesheetType);
     wrtStr(TXMLDocument(node).StylesheetType);
     wrtStr('" href="');
     wrtStr('" href="');
     wrtStr(TXMLDocument(node).StylesheetHRef);
     wrtStr(TXMLDocument(node).StylesheetHRef);
-    wrtStr('"?>'#10);
+    wrtStr('"?>');
   end;
   end;
 
 
   child := node.FirstChild;
   child := node.FirstChild;
@@ -504,6 +478,7 @@ begin
     WriteNode(Child);
     WriteNode(Child);
     Child := Child.NextSibling;
     Child := Child.NextSibling;
   end;
   end;
+  wrtChars(#10, 1);
 end;
 end;
 
 
 procedure TXMLWriter.VisitAttribute(Node: TDOMNode);
 procedure TXMLWriter.VisitAttribute(Node: TDOMNode);
@@ -511,15 +486,17 @@ var
   Child: TDOMNode;
   Child: TDOMNode;
 begin
 begin
   wrtChr(' ');
   wrtChr(' ');
-  wrtStr(Node.NodeName);
-  wrtStr('="');
+  wrtStr(TDOMAttr(Node).Name);
+  wrtChars('="', 2);
   Child := Node.FirstChild;
   Child := Node.FirstChild;
   while Assigned(Child) do
   while Assigned(Child) do
   begin
   begin
-    if Child.NodeType = ENTITY_REFERENCE_NODE then
-      VisitEntityRef(Child)
-    else
-      ConvWrite(Child.NodeValue, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback);
+    case Child.NodeType of
+      ENTITY_REFERENCE_NODE:
+        VisitEntityRef(Child);
+      TEXT_NODE:
+        ConvWrite(TDOMCharacterData(Child).Data, AttrSpecialChars, {$IFDEF FPC}@{$ENDIF}AttrSpecialCharCallback);
+    end;
     Child := Child.NextSibling;
     Child := Child.NextSibling;
   end;
   end;
   wrtChr('"');
   wrtChr('"');
@@ -527,7 +504,7 @@ end;
 
 
 procedure TXMLWriter.VisitDocumentType(Node: TDOMNode);
 procedure TXMLWriter.VisitDocumentType(Node: TDOMNode);
 begin
 begin
-  wrtStr('<!DOCTYPE ');
+  wrtStr(#10'<!DOCTYPE ');
   wrtStr(Node.NodeName);
   wrtStr(Node.NodeName);
   wrtChr(' ');
   wrtChr(' ');
   with TDOMDocumentType(Node) do
   with TDOMDocumentType(Node) do
@@ -551,7 +528,7 @@ begin
       wrtChr(']');
       wrtChr(']');
     end;
     end;
   end;
   end;
-  wrtStr('>'#10);
+  wrtChr('>');
 end;
 end;
 
 
 procedure TXMLWriter.VisitFragment(Node: TDOMNode);
 procedure TXMLWriter.VisitFragment(Node: TDOMNode);

+ 92 - 0
packages/fcl-xml/tests/README

@@ -33,3 +33,95 @@ two lines at the bottom which reference 'eduni-ns10' and 'eduni-ns11' testsuites
 
 
 </TESTSUITE>
 </TESTSUITE>
 )
 )
+
+
+Testsuite errata
+--------------------------------------------
+The following issues were encountered while testing the parser. Fortunately, none
+of these change the category of any test, but in some cases cause incorrect error
+message and/or postion to be reported.
+
+1) xmltest/not-wf/sa/081.xml
+   xmltest/not-wf/sa/082.xml
+   xmltest/not-wf/sa/083.xml
+   xmltest/not-wf/sa/084.xml
+
+All four reference an external entity with SystemID 'nul', which is a reserved
+name under Windows (you won't be able to create such file). The archive contains
+a file named 'nul.ent' that differs from entity's SystemID, so it won't resolve
+anyway even in non-Windows.
+This issue does not have any effect on FCL parser.
+Additionally, tests 083.xml and 084.xml contain a reference to undefined notation.
+This cause an extra validation error to be reported before the fatal error.
+
+2) oasis/p49fail1.xml
+   oasis/p50fail1.xml
+
+Both tests are missing ']' that should close the internal DTD subset.
+
+3) oasis/p58fail1.xml
+   oasis/p58fail2.xml
+   oasis/p58fail3.xml
+
+All three have a NOTATION attribute declared on EMPTY element. This causes an extra
+validation error to be reported before the fatal one.
+
+4) ibm/xml-1.1/not-wf/p02/ibm02n66.ent
+
+Presumably, missing '<' at start of CDATA. Does not change the diagnostic, though.
+
+5) ibm/not-wf/p23/ibm23n05.xml
+
+Contains encoding name 'ASCII' which is not supported by the parser. As a result, it aborts
+before detecting the illegal XML declaration closing sequence.
+
+6) ibm/not-wf/p72/ibm72n09.xml
+
+Missing whitespace between 'ENTITY' and '%' at line 6 is detected before the bad tag closing
+sequence.
+
+7) ibm/not-wf/p77/ibm77n01.ent
+
+Invalid encoding name 'UTF8' is detected before the wrong token order.
+
+8) sun/invalid/attr03.xml
+   sun/invalid/attr04.xml
+   sun/invalid/attr15.xml
+
+Have a NOTATION attribute is declared on EMPTY element. Diagnostics incorrect.
+
+9) ibm/invalid/p56/ibm56i11.xml
+   ibm/invalid/p56/ibm56i12.xml
+   ibm/invalid/p56/ibm56i14.xml
+   ibm/invalid/p56/ibm56i15.xml
+
+Contain a reference to undeclared notation 'gif'. Diagnostics incorrect.
+
+10) eduni/xml-1.1/052.xml
+    eduni/xml-1.1/053.xml
+
+Intended to test handling of NEL and LSEP chars as element content whitespace, these
+tests enclose NEL and LSEP within ordinary ascii chars ('abc_def') that are clearly not
+a whitespace. A 'correct' error is therefore reported regardless of actual NEL/LSEP handling.
+
+11) ibm/not-wf/p69/ibm69n06.xml
+    ibm/not-wf/p69/ibm69n07.xml
+
+Designed to check parameter entity recursion, both tests contain PE references within entity
+value declarations in internal DTD subset, which is a fatal error by itself.
+
+12) ibm/not-wf/p21/ibm21n01.xml
+
+Tests illegal CDEnd, but has an extra '[' in CDStart, which is detected earlier.
+
+13) ibm/not-wf/p21/ibm21n02.xml
+
+Tests illegal CDEnd, but has lowercase 'cdata' in CDStart, which is detected earlier.
+
+14) ibm/xml-1.1/not-wf/p02/ibm02n58.xml
+
+The first illegal character 0x99 is at position (2, 24), but another one at position (4,7) is
+represented with malformed UTF-8 sequence (0xC1 0xA3, while correct one is 0xC2 0x99).
+An 'xml-unaware' decoder can detect this before processing any 'normal' characters,
+so diagnostics may be wrong.
+

+ 32 - 10
packages/fcl-xml/tests/xmlts.pp

@@ -49,7 +49,6 @@ type
     FPassed, FFailCount: Integer;
     FPassed, FFailCount: Integer;
     FFalsePasses: Integer;
     FFalsePasses: Integer;
     FRootUri: string;
     FRootUri: string;
-    FTemplateName: string;
     FSuiteName: string;
     FSuiteName: string;
     FDoc: TXMLDocument;
     FDoc: TXMLDocument;
     FValidating: Boolean;
     FValidating: Boolean;
@@ -64,12 +63,12 @@ type
     table_informative: TDOMNode;
     table_informative: TDOMNode;
     FValError: string;
     FValError: string;
     FTestID: DOMString;
     FTestID: DOMString;
+    FErrLine, FErrCol: Integer;
     procedure LoadTemplate(const Name: string);
     procedure LoadTemplate(const Name: string);
     procedure HandleTemplatePIs(Element: TDOMNode);
     procedure HandleTemplatePIs(Element: TDOMNode);
     procedure Diagnose(Element, Table: TDOMNode; Category: TDiagCategory; const Error: DOMString);
     procedure Diagnose(Element, Table: TDOMNode; Category: TDiagCategory; const Error: DOMString);
     procedure DiagnoseOut(const ErrorMsg: DOMString);
     procedure DiagnoseOut(const ErrorMsg: DOMString);
     function CompareNodes(actual, correct: TDOMNode; out Msg: string): Boolean;
     function CompareNodes(actual, correct: TDOMNode; out Msg: string): Boolean;
-    procedure Canonicalize(node: TDOMNode);
     procedure ErrorHandler(Error: EXMLReadError);
     procedure ErrorHandler(Error: EXMLReadError);
   public
   public
     constructor Create;
     constructor Create;
@@ -133,10 +132,20 @@ begin
   inherited Create;
   inherited Create;
   FParser := TDOMParser.Create;
   FParser := TDOMParser.Create;
   FParser.Options.PreserveWhitespace := True;
   FParser.Options.PreserveWhitespace := True;
+  FParser.Options.ExpandEntities := True;
+  FParser.Options.IgnoreComments := True;
+  FParser.Options.CDSectionsAsText := True;
 end;
 end;
 
 
 procedure TTestSuite.ErrorHandler(Error: EXMLReadError);
 procedure TTestSuite.ErrorHandler(Error: EXMLReadError);
 begin
 begin
+  // allow fatal error position to override that of validation error
+  if (FErrLine < 0) or (Error.Severity = esFatal) then
+  begin
+    FErrLine := Error.Line;
+    FErrCol := Error.LinePos;
+  end;  
+
   if Error.Severity = esError then
   if Error.Severity = esError then
   begin
   begin
     if FValError = '' then // fetch the _first_ message
     if FValError = '' then // fetch the _first_ message
@@ -345,6 +354,8 @@ var
   I: Integer;
   I: Integer;
   root: UTF8String;
   root: UTF8String;
 begin
 begin
+  FErrLine := -1;
+  FErrCol := -1;
   FTestID := Element['ID'];
   FTestID := Element['ID'];
   TestType := Element['TYPE'];
   TestType := Element['TYPE'];
   root := GetBaseURI(Element, FRootUri);
   root := GetBaseURI(Element, FRootUri);
@@ -382,12 +393,16 @@ begin
   try
   try
     try
     try
       FParser.Options.Validate := FValidating;
       FParser.Options.Validate := FValidating;
+//      FParser.Options.Namespaces := (Element['NAMESPACE'] <> 'no');
       FParser.OnError := {$IFDEF FPC}@{$ENDIF}ErrorHandler;
       FParser.OnError := {$IFDEF FPC}@{$ENDIF}ErrorHandler;
       FParser.ParseUri(s, TempDoc);
       FParser.ParseUri(s, TempDoc);
     except
     except
       on E: Exception do
       on E: Exception do
         if E.ClassType <> EAbort then
         if E.ClassType <> EAbort then
+        begin
           FailMsg := E.Message;
           FailMsg := E.Message;
+          FValError := '';
+        end;
     end;
     end;
 
 
     if table = table_informative then
     if table = table_informative then
@@ -412,12 +427,22 @@ begin
       begin
       begin
         if FailMsg <> '' then  // Fatal error
         if FailMsg <> '' then  // Fatal error
         begin
         begin
-          Inc(FFalsePasses);
-          Diagnose(Element, table, dcPass, FailMsg);
+          { outside not-wf category it is a test failure }
+          if table <> table_not_wf then
+          begin
+            Inc(FFailCount);
+            Diagnose(Element, table, dcFail, FailMsg);
+          end
+          else
+          begin
+            Inc(FFalsePasses);
+            Diagnose(Element, table, dcPass, FailMsg);
+          end;
         end
         end
         else
         else
         begin
         begin
-          if table = table_not_wf then  // validation error here is a test failure!
+          { outside invalid category it is a test failure }
+          if table = table_not_wf then
           begin
           begin
             Inc(FFailCount);
             Inc(FFailCount);
             Diagnose(Element, table, dcFail, FValError);
             Diagnose(Element, table, dcFail, FValError);
@@ -448,7 +473,6 @@ begin
       end;
       end;
 
 
     if outURI = '' then Exit;
     if outURI = '' then Exit;
-    Canonicalize(TempDoc);
     TempDoc.DocumentElement.Normalize;
     TempDoc.DocumentElement.Normalize;
     try
     try
       // reference data must be parsed in non-validating mode because it contains DTDs
       // reference data must be parsed in non-validating mode because it contains DTDs
@@ -627,8 +651,7 @@ begin
   table_output.AppendChild(tr);
   table_output.AppendChild(tr);
 end;
 end;
 
 
-
-procedure TTestSuite.Canonicalize(node: TDOMNode);
+procedure Canonicalize(node: TDOMNode);
 var
 var
   child, work: TDOMNode;
   child, work: TDOMNode;
   Frag: TDOMDocumentFragment;
   Frag: TDOMDocumentFragment;
@@ -772,9 +795,8 @@ begin
   with TTestSuite.Create do
   with TTestSuite.Create do
   try
   try
     FSuiteName := SuiteName;
     FSuiteName := SuiteName;
-    FTemplateName := TemplateName;
     FValidating := Validation;
     FValidating := Validation;
-    LoadTemplate(FTemplateName);
+    LoadTemplate(TemplateName);
     if Assigned(FTemplate) then
     if Assigned(FTemplate) then
     begin
     begin
       Run(FSuiteName);
       Run(FSuiteName);

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